#!/usr/bin/env python3 import sys import json import re import textwrap if len(sys.argv) < 2: print( "This script converts the BIP 445 FROST signing test vectors in a given directory to a C file that can be used in the test framework." ) print("Usage: %s " % sys.argv[0]) sys.exit(1) # The curve order; partial signatures >= ORDER fail frost_partial_sig_parse. ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 skipped_cases = [] def hexstr_to_intarray(str): # Always emitted inside brace initializers. C90 forbids empty initializer # braces, so an empty byte string becomes "{ 0 }". The associated length # field is 0, so the padding byte is never read. return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(str)]) or "0" def indent(s, level=1): return textwrap.indent(s, 4 * level * " ") def byte_array(hex_str): return "{ %s }" % hexstr_to_intarray(hex_str) def init_byte_array_maybe(hex_str): """Emits 'has_flag, { bytes }' for a possibly-None byte string.""" return "%d, %s" % ( 0 if hex_str is None else 1, byte_array(hex_str) if hex_str is not None else "{ 0 }", ) def init_var_bytes(hex_str, max_len): """Emits 'has_flag, len, { bytes }' for a possibly-None variable-length byte string.""" has = 0 if hex_str is None else 1 b = bytes.fromhex(hex_str) if hex_str is not None else b"" return "%d, %d, { %s }" % (has, len(b), hexstr_to_intarray(hex_str or "")) def init_indices(array): return "%d, { %s }" % (len(array), ", ".join(map(str, array)) if array else "0") def init_indices_maybe(array): """Emits 'has_flag, { indices }' for a possibly-None index list.""" return "%d, { %s }" % ( 0 if array is None else 1, ", ".join(map(str, array)) if array else "0", ) def init_is_xonly(case, max_tweaks): is_xonly = case.get("is_xonly", []) if len(is_xonly) > 0: return ", ".join("1" if x else "0" for x in is_xonly) return "0" def parse_sign_error(case): """Maps the reference error of a sign/tweak error case to a frost_vec_error code (and a blame index into the case's index lists where applicable).""" err = case["error"] if err["type"] == "InvalidContributionError": assert err["contrib"] == "aggnonce" return "FROST_VEC_ERR_AGGNONCE_PARSE", -1 msg = err["message"] if msg == "The signer's id is missing from the ids list.": return "FROST_VEC_ERR_SIGN", -1 if msg == "The ids list contains duplicate elements.": return "FROST_VEC_ERR_SESSION_INIT", -1 if msg == "The signer's pubshare is missing from the pubshares list.": return "FROST_VEC_ERR_SIGN", -1 if msg.startswith("Invalid pubshare at index"): return "FROST_VEC_ERR_PUBSHARE_PARSE", int(re.search(r"index (\d+)", msg).group(1)) if msg == "The threshold public key must not be the point at infinity.": return "FROST_VEC_ERR_SESSION_INIT", -1 if msg.startswith("Invalid id at index"): return "FROST_VEC_ERR_SESSION_INIT", -1 if msg == "The provided key material is incorrect: the public shares do not match the threshold public key.": return "FROST_VEC_ERR_SESSION_INIT", -1 if msg in ("first secnonce value is out of range.", "second secnonce value is out of range."): return "FROST_VEC_ERR_SECNONCE", -1 if msg == "The number of signers must be between t and n.": return "FROST_VEC_ERR_SESSION_INIT", -1 if msg == "The signer's secret share value is out of range.": return "FROST_VEC_ERR_SIGN", -1 if msg == "The tweak value is out of range.": return "FROST_VEC_ERR_TWEAK_ADD", -1 if msg == "The result of tweaking cannot be infinity.": return "FROST_VEC_ERR_TWEAK_ADD", -1 sys.exit("Unknown sign error: %s" % msg) def parse_det_error(case): """Maps the reference error of a deterministic_sign error case to a frost_vec_error code.""" err = case["error"] if err["type"] == "InvalidContributionError": assert err["contrib"] == "aggothernonce" return "FROST_VEC_ERR_AGGOTHERNONCE", -1 msg = err["message"] if msg == "The tweak value is out of range.": return "FROST_VEC_ERR_TWEAK_ADD", -1 if msg.startswith("Invalid pubshare at index"): return "FROST_VEC_ERR_PUBSHARE_PARSE", int(re.search(r"index (\d+)", msg).group(1)) if msg in ( "The signer's id is missing from the ids list.", "The ids list contains duplicate elements.", "The signer's pubshare is missing from the pubshares list.", "The provided key material is incorrect: the public shares do not match the threshold public key.", "The number of signers must be between t and n.", "The signer's secret share value is out of range.", ) or msg.startswith("Invalid id at index"): return "FROST_VEC_ERR_DET_SIGN", -1 sys.exit("Unknown det_sign error: %s" % msg) def parse_verify_error(case): err = case["error"] if err["type"] == "InvalidContributionError": assert err["contrib"] == "pubnonce" return "FROST_VEC_ERR_PUBNONCE_PARSE", err["signer_index"] msg = err["message"] if msg.startswith("Invalid pubshare at index"): return "FROST_VEC_ERR_PUBSHARE_PARSE", int(re.search(r"index (\d+)", msg).group(1)) sys.exit("Unknown verify error: %s" % err) s = ( """/** * Automatically generated by %s. * * The test vectors are from the BIP 445 reference repository * https://github.com/siv2r/bip-frost-signing, pinned at commit * bb5396f90d45ba5a954cbfd1af80f1b45e02b229 (BIP v0.10.0). They are used by the * tests in src/modules/frost/tests_impl.h. */ """ % sys.argv[0] ) s += """ enum frost_vec_error { /* Valid case. */ FROST_VEC_ERR_NONE, /* frost_session_init returns 0 (invalid session parameters). */ FROST_VEC_ERR_SESSION_INIT, /* frost_sign returns 0. */ FROST_VEC_ERR_SIGN, /* frost_sign triggers the illegal-argument callback when loading the * injected (corrupt) secnonce. */ FROST_VEC_ERR_SECNONCE, /* frost_aggnonce_parse returns 0. */ FROST_VEC_ERR_AGGNONCE_PARSE, /* frost_pubnonce_parse of the blamed pubnonce returns 0. */ FROST_VEC_ERR_PUBNONCE_PARSE, /* secp256k1_ec_pubkey_parse of the blamed pubshare returns 0. */ FROST_VEC_ERR_PUBSHARE_PARSE, /* frost_partial_sig_parse of the blamed partial signature returns 0. */ FROST_VEC_ERR_PSIG_PARSE, /* Applying the tweak chain to the tweak cache fails. */ FROST_VEC_ERR_TWEAK_ADD, /* frost_partial_sig_agg returns 0 (wrong number of partial signatures). */ FROST_VEC_ERR_AGG_LEN, /* frost_deterministic_sign returns 0. */ FROST_VEC_ERR_DET_SIGN, /* The aggothernonce is rejected: frost_aggnonce_parse or * frost_deterministic_sign returns 0. */ FROST_VEC_ERR_AGGOTHERNONCE }; """ # Load all vector files first (to compute the shared maximum sizes). nonce_gen_data = json.load(open(sys.argv[1] + "/nonce_gen_vectors.json")) nonce_agg_data = json.load(open(sys.argv[1] + "/nonce_agg_vectors.json")) sign_verify_data = json.load(open(sys.argv[1] + "/sign_verify_vectors.json")) tweak_data = json.load(open(sys.argv[1] + "/tweak_vectors.json")) sig_agg_data = json.load(open(sys.argv[1] + "/sig_agg_vectors.json")) det_sign_data = json.load(open(sys.argv[1] + "/det_sign_vectors.json")) # Skip tweak error cases that the C API cannot express: the C API applies # tweaks individually via frost_pubkey_{xonly,ec}_tweak_add, so a mismatch # between the number of tweaks and tweak modes cannot occur, and tweaks always # have a fixed length of 32 bytes. tweak_error_tests = [] for group in tweak_data["test_groups"]: for case in group["error_tests"]: if case["error"]["message"] in ( "The tweaks and is_xonly arrays must have the same length.", "The tweak must be a 32-byte array.", ): skipped_cases.append( "tweak_vectors error tc_id %d (%s): not expressible in the C API (tweaks are applied individually and have a fixed length of 32 bytes)" % (case["tc_id"], case["error"]["message"]) ) continue tweak_error_tests.append((tweak_data["test_groups"].index(group), case)) all_groups = ( sign_verify_data["test_groups"] + tweak_data["test_groups"] + sig_agg_data["test_groups"] + det_sign_data["test_groups"] ) max_pubshares = max(len(g["pubshares"]) for g in all_groups) max_secshares = max(len(g.get("secshares", [])) for g in all_groups) max_pubnonces = max(len(g.get("pubnonces", [])) for g in all_groups) max_secnonces = max(len(g.get("secnonces", [])) for g in all_groups) max_tweaks = max(len(g.get("tweaks", [])) for g in all_groups) # A group's tweak pool may contain a tweak that is not 32 bytes long (only # referenced by error cases that the C API cannot express). max_tweak_len = max( len(bytes.fromhex(t)) for g in all_groups for t in g.get("tweaks", []) ) sign_cases = [ c for g in sign_verify_data["test_groups"] for c in g["valid_tests"] + g["sign_error_tests"] ] + [c for g in tweak_data["test_groups"] for c in g["valid_tests"]] + [ c for _, c in tweak_error_tests ] det_cases = [ c for g in det_sign_data["test_groups"] for c in g["valid_tests"] + g["error_tests"] ] verify_cases = [ c for g in sign_verify_data["test_groups"] for c in g["verify_fail_tests"] + g["verify_error_tests"] ] sig_agg_cases = [ c for g in sig_agg_data["test_groups"] for c in g["valid_tests"] + g["error_tests"] ] max_ids = max(len(c["ids"]) for c in sign_cases + det_cases + verify_cases + sig_agg_cases) max_msg = max( len(bytes.fromhex(c["msg"])) for c in sign_cases + det_cases + verify_cases + sig_agg_cases ) max_case_tweaks = max( [len(c.get("tweak_indices", [])) for c in sign_cases + sig_agg_cases] + [len(c["tweaks"]) for c in det_cases] ) max_extra_in = max( len(bytes.fromhex(c["extra_in"] or "")) for c in nonce_gen_data["valid_tests"] ) max_ng_msg = max( len(bytes.fromhex(c["msg"] or "")) for c in nonce_gen_data["valid_tests"] ) max_psigs = max(len(c["psigs"]) for c in sig_agg_cases) max_na_indices = max( len(c["pubnonce_indices"]) for c in nonce_agg_data["valid_tests"] + nonce_agg_data["error_tests"] ) s += """ enum { FROST_VEC_MAX_PUBSHARES = %d, FROST_VEC_MAX_SECSHARES = %d, FROST_VEC_MAX_PUBNONCES = %d, FROST_VEC_MAX_SECNONCES = %d, FROST_VEC_MAX_GROUP_TWEAKS = %d, FROST_VEC_MAX_TWEAK_LEN = %d, FROST_VEC_MAX_SIGNERS = %d, FROST_VEC_MAX_TWEAKS = %d, FROST_VEC_MAX_MSGLEN = %d, FROST_VEC_MAX_PSIGS = %d }; """ % ( max_pubshares, max_secshares, max_pubnonces, max_secnonces, max_tweaks, max_tweak_len, max_ids, max_case_tweaks, max(max_msg, max_ng_msg, 1), max(max_psigs, 1), ) s += """ /* A test group's shared key material. Entry i of each array belongs to the * participant with id i; entries at indices >= n are deliberately bad values * that only error cases select. */ struct frost_vec_group { uint32_t threshold; uint32_t n_participants; unsigned char thresh_pk[33]; size_t n_pubshares; unsigned char pubshares[FROST_VEC_MAX_PUBSHARES][33]; size_t n_secshares; unsigned char secshares[FROST_VEC_MAX_SECSHARES][32]; size_t n_pubnonces; unsigned char pubnonces[FROST_VEC_MAX_PUBNONCES][66]; size_t n_secnonces; unsigned char secnonces[FROST_VEC_MAX_SECNONCES][64]; size_t n_tweaks; unsigned char tweaks[FROST_VEC_MAX_GROUP_TWEAKS][FROST_VEC_MAX_TWEAK_LEN]; }; """ def init_group(group): def counted(key): values = group.get(key, []) # C90 forbids empty initializer braces; an empty group emits "{ { 0 } }" # and its count field is 0, so the padding entry is never read. inner = " { 0 } " if values: inner = "\n" + indent(",\n".join(byte_array(x) for x in values), 1) + "\n" return "%d, {%s}" % (len(values), inner) return "{ %d, %d, %s, %s, %s, %s, %s, %s }," % ( group["t"], group["n"], byte_array(group["thresh_pk"]), counted("pubshares"), counted("secshares"), counted("pubnonces"), counted("secnonces"), counted("tweaks"), ) def init_groups(name, data): out = "static const struct frost_vec_group frost_vec_%s_groups[%d] = {\n" % ( name, len(data["test_groups"]), ) for group in data["test_groups"]: out += indent(init_group(group) + "\n") out += "};\n" return out # nonce_gen vectors num_cases = len(nonce_gen_data["valid_tests"]) s += """ struct frost_vec_nonce_gen_case { unsigned char rand[32]; int has_secshare; unsigned char secshare[32]; int has_pubshare; unsigned char pubshare[33]; int has_thresh_pk; unsigned char thresh_pk_xonly[32]; int has_msg; size_t msg_len; unsigned char msg[FROST_VEC_MAX_MSGLEN]; int has_extra_in; size_t extra_in_len; unsigned char extra_in[%d]; unsigned char expected_secnonce[64]; unsigned char expected_pubnonce[66]; }; """ % max( max_extra_in, 1 ) s += "static const struct frost_vec_nonce_gen_case frost_vec_nonce_gen_cases[%d] = {\n" % num_cases for case in nonce_gen_data["valid_tests"]: s += indent( "{ %s, %s, %s, %s, %s, %s, %s },\n" % ( byte_array(case["rand"]), init_byte_array_maybe(case["secshare"]), init_byte_array_maybe(case["pubshare"]), init_byte_array_maybe(case["thresh_pk_xonly"]), init_var_bytes(case["msg"], max_ng_msg), init_var_bytes(case["extra_in"], max_extra_in), byte_array(case["expected"][0]) + ", " + byte_array(case["expected"][1]), ) ) s += "};\n" # nonce_agg vectors num_pubnonces = len(nonce_agg_data["pubnonces"]) s += """ struct frost_vec_nonce_agg_case { size_t n_pubnonces; size_t pubnonce_indices[%d]; /* -1 for valid cases; otherwise the position (in pubnonce_indices) of the * pubnonce that must fail frost_pubnonce_parse. */ int error_index; unsigned char expected[66]; }; """ % max_na_indices s += "static const unsigned char frost_vec_nonce_agg_pubnonces[%d][66] = {\n" % num_pubnonces s += indent(",\n".join(byte_array(x) for x in nonce_agg_data["pubnonces"])) + "\n" s += "};\n" for name, cases in (("valid", nonce_agg_data["valid_tests"]), ("error", nonce_agg_data["error_tests"])): s += "static const struct frost_vec_nonce_agg_case frost_vec_nonce_agg_%s_cases[%d] = {\n" % (name, len(cases)) for case in cases: s += indent( "{ %d, { %s }, %d, %s },\n" % ( len(case["pubnonce_indices"]), ", ".join(map(str, case["pubnonce_indices"])), case["error"]["signer_index"] if "error" in case else -1, byte_array(case["expected"]) if "expected" in case else "{ 0 }", ) ) s += "};\n" # Shared sign case struct (used by sign_verify and tweak vectors) s += """ struct frost_vec_sign_case { size_t group; uint32_t my_id; size_t n_ids; uint32_t ids[FROST_VEC_MAX_SIGNERS]; int has_pubshares; size_t pubshare_indices[FROST_VEC_MAX_SIGNERS]; size_t secshare_index; size_t secnonce_index; unsigned char aggnonce[66]; size_t msg_len; unsigned char msg[FROST_VEC_MAX_MSGLEN]; size_t n_tweaks; size_t tweak_indices[FROST_VEC_MAX_TWEAKS]; int is_xonly[FROST_VEC_MAX_TWEAKS]; /* FROST_VEC_ERR_NONE for valid cases, otherwise an enum frost_vec_error value. */ int error; /* For FROST_VEC_ERR_PUBSHARE_PARSE: position in pubshare_indices. */ int error_index; unsigned char expected[32]; }; """ def init_sign_case(group_idx, case, error): if error: error_code, error_index = parse_sign_error(case) expected = "{ 0 }" else: error_code, error_index = "FROST_VEC_ERR_NONE", -1 expected = byte_array(case["expected"]) return "{ %d, %d, %s, %s, %d, %d, %s, %s, %s, %s, %s, %d, %s }," % ( group_idx, case["my_id"], init_indices(case["ids"]), init_indices_maybe(case["pubshare_indices"]), case["secshare_index"], case["secnonce_index"], byte_array(case["aggnonce"]), "%d, { %s }" % (len(bytes.fromhex(case["msg"])), hexstr_to_intarray(case["msg"])), init_indices(case.get("tweak_indices", [])), "{ %s }" % init_is_xonly(case, max_case_tweaks), error_code, error_index, expected, ) # sign_verify vectors s += init_groups("sign", sign_verify_data) for name, key, is_error in ( ("sign_valid", "valid_tests", False), ("sign_error", "sign_error_tests", True), ): flat = [] for gi, g in enumerate(sign_verify_data["test_groups"]): for c in (g["valid_tests"] if not is_error else g["sign_error_tests"]): flat.append((gi, c)) s += "static const struct frost_vec_sign_case frost_vec_%s_cases[%d] = {\n" % (name, len(flat)) for gi, c in flat: s += indent(init_sign_case(gi, c, is_error) + "\n") s += "};\n" s += """ struct frost_vec_verify_case { size_t group; unsigned char psig[32]; size_t n_ids; uint32_t ids[FROST_VEC_MAX_SIGNERS]; size_t pubshare_indices[FROST_VEC_MAX_SIGNERS]; size_t pubnonce_indices[FROST_VEC_MAX_SIGNERS]; size_t signer_index; size_t msg_len; unsigned char msg[FROST_VEC_MAX_MSGLEN]; /* 0 if the psig is not a valid scalar (frost_partial_sig_parse fails). */ int psig_parses; /* 0 for verify_fail cases; otherwise an enum frost_vec_error value. */ int error; /* Position in pubnonce_indices/pubshare_indices of the blamed value. */ int error_index; }; """ def init_verify_case(group_idx, case, is_error): psig = bytes.fromhex(case["psig"]) psig_parses = 1 if int.from_bytes(psig, "big") < ORDER else 0 if is_error: error_code, error_index = parse_verify_error(case) else: error_code, error_index = "FROST_VEC_ERR_NONE", -1 return "{ %d, %s, %s, { %s }, { %s }, %d, %s, %d, %s, %d }," % ( group_idx, byte_array(case["psig"]), init_indices(case["ids"]), ", ".join(map(str, case["pubshare_indices"])), ", ".join(map(str, case["pubnonce_indices"])), case["signer_index"], "%d, { %s }" % (len(bytes.fromhex(case["msg"])), hexstr_to_intarray(case["msg"])), psig_parses, error_code, error_index, ) for name, key, is_error in ( ("verify_fail", "verify_fail_tests", False), ("verify_error", "verify_error_tests", True), ): flat = [] for gi, g in enumerate(sign_verify_data["test_groups"]): for c in g[key]: flat.append((gi, c)) s += "static const struct frost_vec_verify_case frost_vec_%s_cases[%d] = {\n" % (name, len(flat)) for gi, c in flat: s += indent(init_verify_case(gi, c, is_error) + "\n") s += "};\n" # tweak vectors (reuse the sign case struct; tweaks are indexed into the group) s += init_groups("tweak", tweak_data) flat_valid = [] for gi, g in enumerate(tweak_data["test_groups"]): for c in g["valid_tests"]: flat_valid.append((gi, c)) s += "static const struct frost_vec_sign_case frost_vec_tweak_valid_cases[%d] = {\n" % len(flat_valid) for gi, c in flat_valid: s += indent(init_sign_case(gi, c, False) + "\n") s += "};\n" s += "static const struct frost_vec_sign_case frost_vec_tweak_error_cases[%d] = {\n" % len(tweak_error_tests) for gi, c in tweak_error_tests: s += indent(init_sign_case(gi, c, True) + "\n") s += "};\n" # sig_agg vectors s += init_groups("sig_agg", sig_agg_data) s += """ struct frost_vec_sig_agg_case { size_t group; size_t n_ids; uint32_t ids[FROST_VEC_MAX_SIGNERS]; int has_pubshares; size_t pubshare_indices[FROST_VEC_MAX_SIGNERS]; unsigned char aggnonce[66]; size_t n_tweaks; size_t tweak_indices[FROST_VEC_MAX_TWEAKS]; int is_xonly[FROST_VEC_MAX_TWEAKS]; size_t n_psigs; unsigned char psigs[FROST_VEC_MAX_PSIGS][32]; size_t msg_len; unsigned char msg[FROST_VEC_MAX_MSGLEN]; /* FROST_VEC_ERR_NONE for valid cases, otherwise an enum frost_vec_error value. */ int error; /* For FROST_VEC_ERR_PSIG_PARSE: position in psigs. */ int error_index; unsigned char expected[64]; }; """ def init_sig_agg_case(group_idx, case, is_error): if is_error: err = case["error"] if err["type"] == "InvalidContributionError": assert err["contrib"] == "psig" error_code, error_index = "FROST_VEC_ERR_PSIG_PARSE", err["signer_index"] elif err["message"] == "The psigs and ids lists must have the same length.": error_code, error_index = "FROST_VEC_ERR_AGG_LEN", -1 else: sys.exit("Unknown sig_agg error: %s" % err) expected = "{ 0 }" else: error_code, error_index = "FROST_VEC_ERR_NONE", -1 expected = byte_array(case["expected"]) return "{ %d, %s, %s, %s, %s, { %s }, %s, %s, %s, %d, %s }," % ( group_idx, init_indices(case["ids"]), init_indices_maybe(case["pubshare_indices"]), byte_array(case["aggnonce"]), init_indices(case["tweak_indices"]), init_is_xonly(case, max_case_tweaks), "%d, %s" % ( len(case["psigs"]), "{ %s }" % ", ".join(byte_array(p) for p in case["psigs"]) if case["psigs"] else "{ { 0 } }", ), "%d, { %s }" % (len(bytes.fromhex(case["msg"])), hexstr_to_intarray(case["msg"])), error_code, error_index, expected, ) for name, key, is_error in ( ("sig_agg_valid", "valid_tests", False), ("sig_agg_error", "error_tests", True), ): flat = [] for gi, g in enumerate(sig_agg_data["test_groups"]): for c in g[key]: flat.append((gi, c)) s += "static const struct frost_vec_sig_agg_case frost_vec_%s_cases[%d] = {\n" % (name, len(flat)) for gi, c in flat: s += indent(init_sig_agg_case(gi, c, is_error) + "\n") s += "};\n" # det_sign vectors s += init_groups("det_sign", det_sign_data) s += """ struct frost_vec_det_sign_case { size_t group; uint32_t my_id; size_t n_ids; uint32_t ids[FROST_VEC_MAX_SIGNERS]; int has_pubshares; size_t pubshare_indices[FROST_VEC_MAX_SIGNERS]; size_t secshare_index; int has_aggothernonce; unsigned char aggothernonce[66]; int has_aux_rand; unsigned char aux_rand[32]; size_t msg_len; unsigned char msg[FROST_VEC_MAX_MSGLEN]; size_t n_tweaks; unsigned char tweaks[FROST_VEC_MAX_TWEAKS][32]; int is_xonly[FROST_VEC_MAX_TWEAKS]; /* FROST_VEC_ERR_NONE for valid cases, otherwise an enum frost_vec_error value. */ int error; /* For FROST_VEC_ERR_PUBSHARE_PARSE: position in pubshare_indices. */ int error_index; unsigned char expected_pubnonce[66]; unsigned char expected_psig[32]; }; """ def init_det_case(group_idx, case, is_error): if is_error: error_code, error_index = parse_det_error(case) expected = "{ 0 }, { 0 }" else: error_code, error_index = "FROST_VEC_ERR_NONE", -1 expected = byte_array(case["expected"][0]) + ", " + byte_array(case["expected"][1]) return "{ %d, %d, %s, %s, %d, %s, %s, %s, %s, %s, %s, %d, %s }," % ( group_idx, case["my_id"], init_indices(case["ids"]), init_indices_maybe(case["pubshare_indices"]), case["secshare_index"], init_byte_array_maybe(case["aggothernonce"]), init_byte_array_maybe(case["aux_rand"]), "%d, { %s }" % (len(bytes.fromhex(case["msg"])), hexstr_to_intarray(case["msg"])), "%d, %s" % ( len(case["tweaks"]), "{ %s }" % ", ".join(byte_array(t) for t in case["tweaks"]) if case["tweaks"] else "{ { 0 } }", ), "{ %s }" % init_is_xonly(case, max_case_tweaks), error_code, error_index, expected, ) for name, key, is_error in ( ("det_sign_valid", "valid_tests", False), ("det_sign_error", "error_tests", True), ): flat = [] for gi, g in enumerate(det_sign_data["test_groups"]): for c in g[key]: flat.append((gi, c)) s += "static const struct frost_vec_det_sign_case frost_vec_%s_cases[%d] = {\n" % (name, len(flat)) for gi, c in flat: s += indent(init_det_case(gi, c, is_error) + "\n") s += "};\n" if skipped_cases: s += "\n/* Skipped test cases (not expressible in the C API):\n" for case in skipped_cases: s += " * - %s\n" % case s += " */\n" print(s)