1*e7be843bSPierre Pronchery#!/usr/bin/env python 2*e7be843bSPierre Pronchery# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. 3*e7be843bSPierre Pronchery# 4*e7be843bSPierre Pronchery# Licensed under the Apache License 2.0 (the "License"). You may not use 5*e7be843bSPierre Pronchery# this file except in compliance with the License. You can obtain a copy 6*e7be843bSPierre Pronchery# in the file LICENSE in the source distribution or at 7*e7be843bSPierre Pronchery# https://www.openssl.org/source/license.html 8*e7be843bSPierre Pronchery 9*e7be843bSPierre Pronchery# A python program written to parse (version 1) of the WYCHEPROOF test vectors for 10*e7be843bSPierre Pronchery# ML_DSA. The 6 files that can be processed by this utility can be downloaded 11*e7be843bSPierre Pronchery# from 12*e7be843bSPierre Pronchery# https://github.com/C2SP/wycheproof/blob/8e7fa6f87e6993d7b613cf48b46512a32df8084a/testvectors_v1/mldsa_*_standard_*_test.json") 13*e7be843bSPierre Pronchery# and output from this utility to 14*e7be843bSPierre Pronchery# test/recipes/30-test_evp_data/evppkey_ml_dsa_44_wycheproof_sign.txt 15*e7be843bSPierre Pronchery# test/recipes/30-test_evp_data/evppkey_ml_dsa_65_wycheproof_sign.txt 16*e7be843bSPierre Pronchery# test/recipes/30-test_evp_data/evppkey_ml_dsa_87_wycheproof_sign.txt 17*e7be843bSPierre Pronchery# test/recipes/30-test_evp_data/evppkey_ml_dsa_44_wycheproof_verify.txt 18*e7be843bSPierre Pronchery# test/recipes/30-test_evp_data/evppkey_ml_dsa_65_wycheproof_verify.txt 19*e7be843bSPierre Pronchery# test/recipes/30-test_evp_data/evppkey_ml_dsa_87_wycheproof_verify.txt 20*e7be843bSPierre Pronchery# 21*e7be843bSPierre Pronchery# e.g. python3 ./test/mldsa_wycheproof_parse.py -alg ML-DSA-44 ./wycheproof/testvectors_v1/mldsa_44_standard_sign_test.json > test/recipes/30-test_evp_data/evppkey_ml_dsa_44_wycheproof_sign.txt 22*e7be843bSPierre Pronchery 23*e7be843bSPierre Proncheryimport json 24*e7be843bSPierre Proncheryimport argparse 25*e7be843bSPierre Proncheryimport datetime 26*e7be843bSPierre Proncheryfrom _ast import Or 27*e7be843bSPierre Pronchery 28*e7be843bSPierre Proncherydef print_label(label, value): 29*e7be843bSPierre Pronchery print(label + " = " + value) 30*e7be843bSPierre Pronchery 31*e7be843bSPierre Proncherydef print_hexlabel(label, tag, value): 32*e7be843bSPierre Pronchery print(label + " = hex" + tag + ":" + value) 33*e7be843bSPierre Pronchery 34*e7be843bSPierre Proncherydef parse_ml_dsa_sig_gen(alg, groups): 35*e7be843bSPierre Pronchery grpId = 1 36*e7be843bSPierre Pronchery for grp in groups: 37*e7be843bSPierre Pronchery keyOnly = False 38*e7be843bSPierre Pronchery first = True 39*e7be843bSPierre Pronchery name = alg.replace('-', '_') 40*e7be843bSPierre Pronchery keyname = name + "_" + str(grpId) 41*e7be843bSPierre Pronchery grpId += 1 42*e7be843bSPierre Pronchery 43*e7be843bSPierre Pronchery for tst in grp['tests']: 44*e7be843bSPierre Pronchery if first: 45*e7be843bSPierre Pronchery first = False 46*e7be843bSPierre Pronchery if 'flags' in tst: 47*e7be843bSPierre Pronchery if 'IncorrectPrivateKeyLength' in tst['flags'] or 'InvalidPrivateKey' in tst['flags']: 48*e7be843bSPierre Pronchery keyOnly = True 49*e7be843bSPierre Pronchery if not keyOnly: 50*e7be843bSPierre Pronchery print("") 51*e7be843bSPierre Pronchery print_label("PrivateKeyRaw", keyname + ":" + alg + ":" + grp['privateKey']) 52*e7be843bSPierre Pronchery testname = name + "_" + str(tst['tcId']) 53*e7be843bSPierre Pronchery print("\n# " + str(tst['tcId']) + " " + tst['comment']) 54*e7be843bSPierre Pronchery 55*e7be843bSPierre Pronchery print_label("FIPSversion", ">=3.5.0") 56*e7be843bSPierre Pronchery if keyOnly: 57*e7be843bSPierre Pronchery print_label("KeyFromData", alg) 58*e7be843bSPierre Pronchery print_hexlabel("Ctrl", "priv", grp['privateKey']) 59*e7be843bSPierre Pronchery print_label("Result", "KEY_FROMDATA_ERROR") 60*e7be843bSPierre Pronchery else: 61*e7be843bSPierre Pronchery print_label("Sign-Message", alg + ":" + keyname) 62*e7be843bSPierre Pronchery print_label("Input", tst['msg']) 63*e7be843bSPierre Pronchery print_label("Output", tst['sig']) 64*e7be843bSPierre Pronchery if 'ctx' in tst: 65*e7be843bSPierre Pronchery print_hexlabel("Ctrl", "context-string", tst['ctx']) 66*e7be843bSPierre Pronchery print_label("Ctrl", "message-encoding:1") 67*e7be843bSPierre Pronchery print_label("Ctrl", "deterministic:1") 68*e7be843bSPierre Pronchery if tst['result'] == "invalid": 69*e7be843bSPierre Pronchery print_label("Result", "PKEY_CTRL_ERROR") 70*e7be843bSPierre Pronchery 71*e7be843bSPierre Proncherydef parse_ml_dsa_sig_ver(alg, groups): 72*e7be843bSPierre Pronchery grpId = 1 73*e7be843bSPierre Pronchery for grp in groups: 74*e7be843bSPierre Pronchery keyOnly = False 75*e7be843bSPierre Pronchery first = True 76*e7be843bSPierre Pronchery name = alg.replace('-', '_') 77*e7be843bSPierre Pronchery keyname = name + "_" + str(grpId) 78*e7be843bSPierre Pronchery grpId += 1 79*e7be843bSPierre Pronchery 80*e7be843bSPierre Pronchery for tst in grp['tests']: 81*e7be843bSPierre Pronchery if first: 82*e7be843bSPierre Pronchery first = False 83*e7be843bSPierre Pronchery if 'flags' in tst: 84*e7be843bSPierre Pronchery if 'IncorrectPublicKeyLength' in tst['flags'] or 'InvalidPublicKey' in tst['flags']: 85*e7be843bSPierre Pronchery keyOnly = True 86*e7be843bSPierre Pronchery if not keyOnly: 87*e7be843bSPierre Pronchery print("") 88*e7be843bSPierre Pronchery print_label("PublicKeyRaw", keyname + ":" + alg + ":" + grp['publicKey']) 89*e7be843bSPierre Pronchery testname = name + "_" + str(tst['tcId']) 90*e7be843bSPierre Pronchery print("\n# " + str(tst['tcId']) + " " + tst['comment']) 91*e7be843bSPierre Pronchery 92*e7be843bSPierre Pronchery print_label("FIPSversion", ">=3.5.0") 93*e7be843bSPierre Pronchery if keyOnly: 94*e7be843bSPierre Pronchery print_label("KeyFromData", alg) 95*e7be843bSPierre Pronchery print_hexlabel("Ctrl", "pub", grp['publicKey']) 96*e7be843bSPierre Pronchery print_label("Result", "KEY_FROMDATA_ERROR") 97*e7be843bSPierre Pronchery else: 98*e7be843bSPierre Pronchery print_label("Verify-Message-Public", alg + ":" + keyname) 99*e7be843bSPierre Pronchery print_label("Input", tst['msg']) 100*e7be843bSPierre Pronchery print_label("Output", tst['sig']) 101*e7be843bSPierre Pronchery if 'ctx' in tst: 102*e7be843bSPierre Pronchery print_hexlabel("Ctrl", "context-string", tst['ctx']) 103*e7be843bSPierre Pronchery print_label("Ctrl", "message-encoding:1") 104*e7be843bSPierre Pronchery print_label("Ctrl", "deterministic:1") 105*e7be843bSPierre Pronchery if tst['result'] == "invalid": 106*e7be843bSPierre Pronchery if 'InvalidContext' in tst['flags']: 107*e7be843bSPierre Pronchery print_label("Result", "PKEY_CTRL_ERROR") 108*e7be843bSPierre Pronchery else: 109*e7be843bSPierre Pronchery print_label("Result", "VERIFY_ERROR") 110*e7be843bSPierre Pronchery 111*e7be843bSPierre Proncheryparser = argparse.ArgumentParser(description="") 112*e7be843bSPierre Proncheryparser.add_argument('filename', type=str) 113*e7be843bSPierre Proncheryparser.add_argument('-alg', type=str) 114*e7be843bSPierre Proncheryargs = parser.parse_args() 115*e7be843bSPierre Pronchery 116*e7be843bSPierre Pronchery# Open and read the JSON file 117*e7be843bSPierre Proncherywith open(args.filename, 'r') as file: 118*e7be843bSPierre Pronchery data = json.load(file) 119*e7be843bSPierre Pronchery 120*e7be843bSPierre Proncheryyear = datetime.date.today().year 121*e7be843bSPierre Proncheryversion = data['generatorVersion'] 122*e7be843bSPierre Proncheryalgorithm = data['algorithm'] 123*e7be843bSPierre Proncherymode = data['testGroups'][0]['type'] 124*e7be843bSPierre Pronchery 125*e7be843bSPierre Proncheryprint("# Copyright " + str(year) + " The OpenSSL Project Authors. All Rights Reserved.") 126*e7be843bSPierre Proncheryprint("#") 127*e7be843bSPierre Proncheryprint("# Licensed under the Apache License 2.0 (the \"License\"). You may not use") 128*e7be843bSPierre Proncheryprint("# this file except in compliance with the License. You can obtain a copy") 129*e7be843bSPierre Proncheryprint("# in the file LICENSE in the source distribution or at") 130*e7be843bSPierre Proncheryprint("# https://www.openssl.org/source/license.html\n") 131*e7be843bSPierre Proncheryprint("# Wycheproof test data for " + algorithm + " " + mode + " generated from") 132*e7be843bSPierre Proncheryprint("# https://github.com/C2SP/wycheproof/blob/8e7fa6f87e6993d7b613cf48b46512a32df8084a/testvectors_v1/mldsa_*_standard_*_test.json") 133*e7be843bSPierre Pronchery 134*e7be843bSPierre Proncheryprint("# [version " + str(version) + "]") 135*e7be843bSPierre Pronchery 136*e7be843bSPierre Proncheryif algorithm == "ML-DSA": 137*e7be843bSPierre Pronchery if mode == 'MlDsaSign': 138*e7be843bSPierre Pronchery parse_ml_dsa_sig_gen(args.alg, data['testGroups']) 139*e7be843bSPierre Pronchery elif mode == 'MlDsaVerify': 140*e7be843bSPierre Pronchery parse_ml_dsa_sig_ver(args.alg, data['testGroups']) 141*e7be843bSPierre Pronchery else: 142*e7be843bSPierre Pronchery print("Unsupported mode " + mode) 143*e7be843bSPierre Proncheryelse: 144*e7be843bSPierre Pronchery print("Unsupported algorithm " + algorithm) 145