Home | History | Annotate | Line # | Download | only in test
      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