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