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 42) of the ACVP test vectors for 10# ML_DSA. The 3 files that can be processed by this utility can be downloaded 11# from 12# https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/ML-DSA-keyGen-FIPS204/internalProjection.json 13# https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/ML-DSA-sigGen-FIPS204/internalProjection.json 14# https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/ML-DSA-sigVer-FIPS204/internalProjection.json 15# and output from this utility to 16# test/recipes/30-test_evp_data/evppkey_ml_dsa_keygen.txt 17# test/recipes/30-test_evp_data/evppkey_ml_dsa_siggen.txt 18# test/recipes/30-test_evp_data/evppkey_ml_dsa_sigver.txt 19# 20# e.g. python3 mldsa_parse.py ~/Downloads/keygen.json > ./test/recipes/30-test_evp_data/evppkey_ml_dsa_keygen.txt 21# 22import json 23import argparse 24import datetime 25 26def print_label(label, value): 27 print(label + " = " + value) 28 29def print_hexlabel(label, tag, value): 30 print(label + " = hex" + tag + ":" + value) 31 32def parse_ml_dsa_key_gen(groups): 33 for grp in groups: 34 for tst in grp['tests']: 35 print(""); 36 print_label("FIPSversion", ">=3.5.0") 37 print_label("KeyGen", grp['parameterSet']) 38 print_label("KeyName", "tcId" + str(tst['tcId'])) 39 print_hexlabel("Ctrl", "seed", tst['seed']) 40 print_hexlabel("CtrlOut", "pub", tst['pk']) 41 print_hexlabel("CtrlOut", "priv", tst['sk']) 42 43def parse_ml_dsa_sig_gen(groups): 44 for grp in groups: 45 deter = grp['deterministic'] # Boolean 46 externalMu = grp['externalMu'] # Boolean 47 signInterfaceExternal = (grp['signatureInterface'] == "External") 48 signPreHash = (grp['preHash'] == "preHash") 49 signPure = (grp['preHash'] == "pure") 50 includeMu = True # Flag flips to only include the Ctrl mu:0 half the time 51 52 if signPreHash: 53 continue 54 if not externalMu and not signPure: 55 continue 56 57 name = grp['parameterSet'].replace('-', '_') 58 for tst in grp['tests']: 59 testname = name + "_" + str(tst['tcId']) 60 print(""); 61 print_label("PrivateKeyRaw", testname + ":" + grp['parameterSet'] + ":" + tst['sk']) 62 print(""); 63 print_label("FIPSversion", ">=3.5.0") 64 print_label("Sign-Message", grp['parameterSet'] + ":" + testname) 65 print_label("Input", tst['mu' if externalMu else 'message']) 66 print_label("Output", tst['signature']) 67 print_label("Ctrl", "message-encoding:1") 68 if not externalMu: 69 print_label("Ctrl", "hexcontext-string:" + tst["context"]) 70 includeMu = not includeMu 71 if externalMu or includeMu: 72 print_label("Ctrl", "mu:" + ("1" if externalMu else "0")) 73 print_label("Ctrl", "deterministic:" + ("1" if deter else "0")) 74 if not deter: 75 print_label("Ctrl", "hextest-entropy:" + tst["rnd"]) 76 77def parse_ml_dsa_sig_ver(groups): 78 for grp in groups: 79 externalMu = grp["externalMu"] # Boolean 80 signInterfaceExternal = (grp['signatureInterface'] == "External") 81 signPreHash = (grp['preHash'] == "preHash") 82 signPure = (grp['preHash'] == "pure") 83 includeMu = True # Flag flips to only include the Ctrl mu:0 half the time 84 85 if signPreHash: 86 continue 87 if not externalMu and not signPure: 88 continue 89 90 name = grp['parameterSet'].replace('-', '_') 91 for tst in grp['tests']: 92 testname = name + "_" + str(tst['tcId']) 93 print(""); 94 print_label("PublicKeyRaw", testname + ":" + grp['parameterSet'] + ":" + tst['pk']) 95 print(""); 96 if "reason" in tst: 97 print("# " + tst['reason']) 98 print_label("FIPSversion", ">=3.5.0") 99 print_label("Verify-Message-Public", grp['parameterSet'] + ":" + testname) 100 print_label("Input", tst['mu' if externalMu else 'message']) 101 print_label("Output", tst['signature']) 102 print_label("Ctrl", "message-encoding:1") 103 if not externalMu: 104 print_label("Ctrl", "hexcontext-string:" + tst["context"]) 105 includeMu = not includeMu 106 if externalMu or includeMu: 107 print_label("Ctrl", "mu:" + ("1" if externalMu else "0")) 108 if not tst['testPassed']: 109 print_label("Result", "VERIFY_ERROR") 110 111parser = argparse.ArgumentParser(description="") 112parser.add_argument('filename', type=str) 113args = parser.parse_args() 114 115# Open and read the JSON file 116with open(args.filename, 'r') as file: 117 data = json.load(file) 118 119year = datetime.date.today().year 120version = data['vsId'] 121algorithm = data['algorithm'] 122mode = data['mode'] 123 124print("# Copyright " + str(year) + " The OpenSSL Project Authors. All Rights Reserved.") 125print("#") 126print("# Licensed under the Apache License 2.0 (the \"License\"). You may not use") 127print("# this file except in compliance with the License. You can obtain a copy") 128print("# in the file LICENSE in the source distribution or at") 129print("# https://www.openssl.org/source/license.html\n") 130print("# ACVP test data for " + algorithm + " " + mode + " generated from") 131print("# https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/" 132 "ML-DSA-" + mode + "-FIPS204/internalProjection.json") 133print("# [version " + str(version) + "]") 134 135if algorithm == "ML-DSA": 136 if mode == 'sigVer': 137 parse_ml_dsa_sig_ver(data['testGroups']) 138 elif mode == 'sigGen': 139 parse_ml_dsa_sig_gen(data['testGroups']) 140 elif mode == 'keyGen': 141 parse_ml_dsa_key_gen(data['testGroups']) 142 else: 143 print("Unsupported mode " + mode) 144else: 145 print("Unsupported algorithm " + algorithm) 146