Files
secp256k1-zkp/tools/test_vectors_chilldkg_generate.py
Kgothatso Ngako 7afee05de8 tools: add the iceberg test vector generator
src/modules/iceberg/vectors.h opens with regeneration instructions that
name ./tools/test_vectors_iceberg_generate.py, and that script was never
committed. The same header calls the file "the one thing in this
repository that cannot be rebuilt from what the repository contains",
which the missing generator made true twice over: neither the reference
nor the tool that reads it was reachable from a clone.

Recovered from the tree the Iceberg C is vendored in, at
sources/secp256k1-kmp/native/secp256k1 of the benchmark repository
(bitcoin-core/secp256k1 branch iceberg-module, commit 96201552, per that
repository's PINS.txt). Confirmed to be the generator that produced the
checked-in vectors before committing it:

  - its HEADER template reproduces the header of vectors.h exactly,
    including the ICEBERG_VECTOR_MAX_PARTICIPANTS 9 and
    ICEBERG_VECTOR_MAX_SEEDS 126 it computes from the configuration list;
  - its seed rule, sha256("iceberg test vectors|<label>|<rank>"),
    reproduces the checked-in 2of3 seeds byte for byte;
  - the whole of that tree's src/modules/iceberg/ matches this one byte
    for byte except tests_impl.h, which differs only because the tests
    here are ported to the unit_test.h test-module framework.

The script cannot be re-run from a clone alone, by design: it takes the
nkohen/Iceberg Python reference as its argument and that reference is
deliberately not vendored, since it is not ours and pinning a copy would
hide it drifting. Its docstring gives the clone and the pinned commit
(7b55ef6d), which is now the whole of what regenerating requires.

Also restore the executable bit on the frost and chilldkg generators.
Both carry a usage line telling the reader to run them directly, and
every other generator in tools/ is 0755.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 23:37:52 +02:00

725 lines
26 KiB
Python
Executable File

#!/usr/bin/env python3
"""Converts the ChillDKG test vectors of the bip-frost-dkg reference
repository into a C header used by the tests in src/modules/chilldkg/tests_impl.h.
Usage: tools/test_vectors_chilldkg_generate.py <dir>
<dir> must contain the vector files of the bip-frost-dkg repository
(https://github.com/BlockstreamResearch/bip-frost-dkg, directory vectors/)
pinned at commit a91896883f85b159415ecf298d5e844879af112d.
"""
import sys
import json
import textwrap
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
VEC_DIR = sys.argv[1]
FILES = {
"hostpubkey_gen": "hostpubkey_gen_vectors.json",
"params_hash": "params_hash_vectors.json",
"participant_step1": "participant_step1_vectors.json",
"participant_step2": "participant_step2_vectors.json",
"participant_finalize": "participant_finalize_vectors.json",
"participant_investigate": "participant_investigate_vectors.json",
"coordinator_step1": "coordinator_step1_vectors.json",
"coordinator_finalize": "coordinator_finalize_vectors.json",
"coordinator_investigate": "coordinator_investigate_vectors.json",
"recover": "recover_vectors.json",
}
data = {k: json.load(open(VEC_DIR + "/" + f)) for k, f in FILES.items()}
skipped_cases = []
def hexstr_to_intarray(s):
# 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(s)]) or "0"
def byte_array(hex_str):
return "{ %s }" % hexstr_to_intarray(hex_str)
def indent(s, level=1):
return textwrap.indent(s, 4 * level * " ")
def map_error(case, step_name):
"""Maps the reference expectedError of an error case to a
chilldkg_vec_error code and fault index. Returns None if the case is not
expressible in the C API (wrong-length or wrong-count inputs; the C API
uses fixed-size buffers whose lengths derive from the session
parameters)."""
err = case["expectedError"]
etype = err["type"]
if etype == "ValueError":
skipped_cases.append(
"%s tcId %d (%s): not expressible in the C API (wrong input length or count)"
% (step_name, case["tcId"], case.get("comment", "").strip())
)
return None
fault_index = "UINT32_MAX"
if "participantId" in err:
assert etype in ("FaultyParticipantError", "FaultyParticipantOrCoordinatorError", "InvalidHostPubkeyError", "DuplicateHostPubkeyError")
fault_index = str(err["participantId"])
if etype in ("HostSeckeyError", "ThresholdOrCountError", "InvalidHostPubkeyError",
"DuplicateHostPubkeyError", "RandomnessError", "RecoveryDataError"):
return "CHILLDKG_VEC_INVALID_INPUT", "UINT32_MAX"
if etype == "FaultyCoordinatorError":
return "CHILLDKG_VEC_FAULTY_COORDINATOR", fault_index
if etype == "FaultyParticipantError":
return "CHILLDKG_VEC_FAULTY_PARTICIPANT", fault_index
if etype == "FaultyParticipantOrCoordinatorError":
return "CHILLDKG_VEC_FAULTY_PARTICIPANT_OR_COORDINATOR", fault_index
if etype == "UnknownFaultyParticipantOrCoordinatorError":
return "CHILLDKG_VEC_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR", fault_index
sys.exit("Unknown error type %s in %s tcId %d" % (etype, step_name, case["tcId"]))
def groups(d):
return d["testGroups"] if "testGroups" in d else [d]
def all_cases(d):
for g in groups(d):
for c in g.get("validTestCases", []) + g.get("errorTestCases", []):
yield g, c
def get_params(case, group):
return case.get("params", group.get("params"))
# Compute the shared maximum sizes.
max_n = 0
max_t = 0
max_pmsg1 = 0
max_cmsg1 = 0
max_cmsg2 = 0
max_recovery = 0
max_cinv = 0
max_pmsg1_pool = 0
max_pmsg2_pool = 0
max_cmsg1_pool = 0
max_indices = 0
for name, d in data.items():
for g, c in all_cases(d):
p = get_params(c, g)
if p is not None:
max_n = max(max_n, len(p["hostpubkeys"]))
max_t = max(max_t, p["t"])
for key, cur in (("expectedPmsg1", "pmsg1"), ("cmsg1", "cmsg1"), ("cmsg2", "cmsg2"),
("recoveryData", "recovery"), ("cinvMsg", "cinv")):
v = c.get(key)
if isinstance(v, str):
if cur == "pmsg1":
max_pmsg1 = max(max_pmsg1, len(v) // 2)
elif cur == "cmsg1":
max_cmsg1 = max(max_cmsg1, len(v) // 2)
elif cur == "cmsg2":
max_cmsg2 = max(max_cmsg2, len(v) // 2)
elif cur == "recovery":
max_recovery = max(max_recovery, len(v) // 2)
elif cur == "cinv":
max_cinv = max(max_cinv, len(v) // 2)
if "pmsg1Indices" in c:
max_indices = max(max_indices, len(c["pmsg1Indices"]))
if "pmsg2Indices" in c:
max_indices = max(max_indices, len(c["pmsg2Indices"]))
for g in groups(d):
if "pmsg1" in g and isinstance(g["pmsg1"], str):
max_pmsg1 = max(max_pmsg1, len(g["pmsg1"]) // 2)
if "cmsg1" in g and isinstance(g["cmsg1"], str):
max_cmsg1 = max(max_cmsg1, len(g["cmsg1"]) // 2)
if "pmsgs1" in g:
max_n = max(max_n, len(g["pmsgs1"]))
for m in g["pmsgs1"]:
max_pmsg1 = max(max_pmsg1, len(m) // 2)
if "pmsg1Pool" in g:
max_pmsg1_pool = max(max_pmsg1_pool, len(g["pmsg1Pool"]))
for m in g["pmsg1Pool"]:
max_pmsg1 = max(max_pmsg1, len(m) // 2)
if "pmsg2Pool" in g:
max_pmsg2_pool = max(max_pmsg2_pool, len(g["pmsg2Pool"]))
if "cmsg1Pool" in g:
max_cmsg1_pool = max(max_cmsg1_pool, len(g["cmsg1Pool"]))
for m in g["cmsg1Pool"]:
max_cmsg1 = max(max_cmsg1, len(m) // 2)
for c in g.get("validTestCases", []):
if "expectedCinvMsgs" in c:
for m in c["expectedCinvMsgs"]:
max_cinv = max(max_cinv, len(m) // 2)
eo = c.get("expectedOutput")
if eo is not None:
if "recoveryData" in eo:
max_recovery = max(max_recovery, len(eo["recoveryData"]) // 2)
if "cmsg2" in eo:
max_cmsg2 = max(max_cmsg2, len(eo["cmsg2"]) // 2)
for c in g.get("validTestCases", []) + g.get("errorTestCases", []):
if isinstance(c.get("recoveryData"), str):
max_recovery = max(max_recovery, len(c["recoveryData"]) // 2)
s = """/**
* Automatically generated by %s.
*
* The test vectors are from the bip-frost-dkg reference repository
* https://github.com/BlockstreamResearch/bip-frost-dkg, pinned at commit
* a91896883f85b159415ecf298d5e844879af112d (BIP version 0.3.0-dev). They are
* used by the tests in src/modules/chilldkg/tests_impl.h.
*
* Regenerate with:
* tools/test_vectors_chilldkg_generate.py <dir> > src/modules/chilldkg/vectors.h
* where <dir> is the vectors/ directory of the reference repository at the
* commit above. */
#ifndef SECP256K1_MODULE_CHILLDKG_VECTORS_H
#define SECP256K1_MODULE_CHILLDKG_VECTORS_H
#include <stddef.h>
#include <stdint.h>
""" % sys.argv[0]
s += """/* The expected result of an error test case, mapped from the reference
* exception taxonomy; the values coincide with secp256k1_chilldkg_fault.
* CHILLDKG_VEC_INVALID_INPUT covers the local input errors (ValueError
* subclasses that are not protocol faults: HostSeckeyError,
* ThresholdOrCountError, InvalidHostPubkeyError, DuplicateHostPubkeyError,
* RandomnessError, RecoveryDataError). */
enum chilldkg_vec_error {
CHILLDKG_VEC_OK = 0,
CHILLDKG_VEC_FAULTY_COORDINATOR = 1,
CHILLDKG_VEC_FAULTY_PARTICIPANT = 2,
CHILLDKG_VEC_FAULTY_PARTICIPANT_OR_COORDINATOR = 3,
CHILLDKG_VEC_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR = 4,
CHILLDKG_VEC_INVALID_INPUT = 5
};
enum {
CHILLDKG_VEC_MAX_PARTICIPANTS = %d,
CHILLDKG_VEC_MAX_PMSG1_LEN = %d,
CHILLDKG_VEC_MAX_CMSG1_LEN = %d,
CHILLDKG_VEC_MAX_CMSG2_LEN = %d,
CHILLDKG_VEC_MAX_RECOVERY_LEN = %d,
CHILLDKG_VEC_MAX_CINV_LEN = %d,
CHILLDKG_VEC_MAX_PMSG1_POOL = %d,
CHILLDKG_VEC_MAX_PMSG2_POOL = %d,
CHILLDKG_VEC_MAX_CMSG1_POOL = %d,
CHILLDKG_VEC_MAX_INDICES = %d
};
/* Session parameters (hostpubkeys || t). */
struct chilldkg_vec_params {
uint32_t t;
size_t n;
unsigned char hostpubkeys[CHILLDKG_VEC_MAX_PARTICIPANTS][33];
};
/* The DKG output of a participant or the coordinator (has_secshare == 0). */
struct chilldkg_vec_dkg_output {
int has_secshare;
unsigned char secshare[32];
unsigned char thresh_pk[33];
unsigned char pubshares[CHILLDKG_VEC_MAX_PARTICIPANTS][33];
};
""" % (max_n, max_pmsg1, max_cmsg1, max_cmsg2, max_recovery, max_cinv,
max_pmsg1_pool, max_pmsg2_pool, max_cmsg1_pool, max_indices)
def init_params(p):
inner = "\n" + indent(",\n".join(byte_array(h) for h in p["hostpubkeys"]), 2) + "\n"
return "{ %d, %d, {%s} }" % (p["t"], len(p["hostpubkeys"]), inner)
def init_dkg_output(o):
return "{ %d, %s, %s, {\n%s\n} }" % (
0 if o["secshare"] is None else 1,
byte_array(o["secshare"]) if o["secshare"] is not None else "{ 0 }",
byte_array(o["threshPk"]),
indent(",\n".join(byte_array(p) for p in o["pubshares"]), 2),
)
def case_list(d):
"""Returns the (is_valid, case) list of a vector file (flat per-group
files) with inexpressible error cases removed."""
out = []
for g in groups(d):
for c in g.get("validTestCases", []):
out.append((1, c))
for c in g.get("errorTestCases", []):
out.append((0, c))
return out
# hostpubkey_gen vectors
cases = []
for is_valid, c in case_list(data["hostpubkey_gen"]):
if not is_valid and map_error(c, "hostpubkey_gen") is None:
continue
cases.append((is_valid, c))
s += """
struct chilldkg_vec_hostpubkey_gen_case {
int is_valid;
unsigned char hostseckey[32];
unsigned char expected_hostpubkey[33];
};
static const struct chilldkg_vec_hostpubkey_gen_case chilldkg_vec_hostpubkey_gen_cases[%d] = {
""" % len(cases)
for is_valid, c in cases:
s += indent("{ %d, %s, %s },\n" % (
is_valid,
byte_array(c["hostseckey"]),
byte_array(c.get("expectedHostpubkey") or ""),
))
s += "};\n"
# params_hash vectors
cases = []
for is_valid, c in case_list(data["params_hash"]):
if not is_valid and map_error(c, "params_hash") is None:
continue
cases.append((is_valid, c))
s += """
struct chilldkg_vec_params_hash_case {
int is_valid;
struct chilldkg_vec_params params;
unsigned char expected_hash[32];
};
static const struct chilldkg_vec_params_hash_case chilldkg_vec_params_hash_cases[%d] = {
""" % len(cases)
for is_valid, c in cases:
s += indent("{ %d,\n%s,\n%s },\n" % (
is_valid,
indent(init_params(c["params"]) + ",", 1)[:-1],
byte_array(c.get("expectedParamsHash") or ""),
))
s += "};\n"
# participant_step1 vectors
cases = []
for is_valid, c in case_list(data["participant_step1"]):
if not is_valid and map_error(c, "participant_step1") is None:
continue
cases.append((is_valid, c))
s += """
struct chilldkg_vec_step1_case {
int is_valid;
unsigned char hostseckey[32];
struct chilldkg_vec_params params;
unsigned char random[32];
unsigned char expected_pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
};
static const struct chilldkg_vec_step1_case chilldkg_vec_step1_cases[%d] = {
""" % len(cases)
for is_valid, c in cases:
s += indent("{ %d, %s,\n%s,\n%s, %s },\n" % (
is_valid,
byte_array(c["hostseckey"]),
indent(init_params(c["params"]) + ",", 1)[:-1],
byte_array(c["random"]),
byte_array(c.get("expectedPmsg1") or ""),
))
s += "};\n"
# participant_step2 vectors
s += """
struct chilldkg_vec_step2_group {
struct chilldkg_vec_params params;
unsigned char hostseckey[32];
unsigned char random[32];
unsigned char aux_rand[32];
unsigned char pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
};
struct chilldkg_vec_step2_case {
int is_valid;
/* hostseckey override (error cases only); has_hostseckey == 0 means the
* group's hostseckey is used. */
int has_hostseckey;
unsigned char hostseckey[32];
unsigned char cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
/* Expected result: CHILLDKG_VEC_OK or an enum chilldkg_vec_error value,
* mapped to secp256k1_chilldkg_fault / return-0 as in the tests. */
int expected_error;
uint32_t expected_fault_index;
unsigned char expected_pmsg2[64];
};
"""
s += "static const struct chilldkg_vec_step2_group chilldkg_vec_step2_groups[%d] = {\n" % len(groups(data["participant_step2"]))
for g in groups(data["participant_step2"]):
s += indent("{\n%s,\n%s, %s, %s, %s },\n" % (
indent(init_params(g["params"]) + ",", 1)[:-1],
byte_array(g["hostseckey"]),
byte_array(g["random"]),
byte_array(g["auxRand"]),
byte_array(g["pmsg1"]),
))
s += "};\n"
step2_cases = []
for gi, g in enumerate(groups(data["participant_step2"])):
for c in g.get("validTestCases", []):
step2_cases.append((gi, 1, c))
for c in g.get("errorTestCases", []):
if map_error(c, "participant_step2") is None:
continue
step2_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_step2_case chilldkg_vec_step2_cases[%d] = {\n" % len(step2_cases)
for gi, is_valid, c in step2_cases:
if is_valid:
error_code, fault_index = "CHILLDKG_VEC_OK", "UINT32_MAX"
expected = byte_array(c["expectedPmsg2"])
else:
error_code, fault_index = map_error(c, "participant_step2")
expected = "{ 0 }"
has_hsk = 1 if "hostseckey" in c else 0
s += indent("{ %d, %d, %s,\n%s,\n%s, %s, %s },\n" % (
is_valid,
has_hsk,
byte_array(c.get("hostseckey") or "00" * 32),
indent(byte_array(c["cmsg1"]) + ",", 1)[:-1],
error_code, fault_index, expected,
))
# Note: the group index is implicit (cases are emitted group by group).
s += "};\n"
s += "static const size_t chilldkg_vec_step2_group_case_counts[%d] = {\n" % len(groups(data["participant_step2"]))
gi = 0
for g in groups(data["participant_step2"]):
cnt = sum(1 for x in step2_cases if x[0] == gi)
s += indent("%d,\n" % cnt)
gi += 1
s += "};\n"
# participant_finalize vectors
s += """
struct chilldkg_vec_finalize_group {
struct chilldkg_vec_params params;
unsigned char hostseckey[32];
unsigned char random[32];
unsigned char aux_rand[32];
unsigned char pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
unsigned char cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
unsigned char pmsg2[64];
};
struct chilldkg_vec_finalize_case {
int is_valid;
unsigned char cmsg2[CHILLDKG_VEC_MAX_CMSG2_LEN];
/* Expected result: CHILLDKG_VEC_OK or CHILLDKG_VEC_FAULTY_COORDINATOR. */
int expected_error;
struct chilldkg_vec_dkg_output expected_output;
unsigned char expected_recovery[CHILLDKG_VEC_MAX_RECOVERY_LEN];
};
"""
s += "static const struct chilldkg_vec_finalize_group chilldkg_vec_finalize_groups[%d] = {\n" % len(groups(data["participant_finalize"]))
for g in groups(data["participant_finalize"]):
s += indent("{\n%s,\n%s, %s, %s, %s,\n%s,\n%s },\n" % (
indent(init_params(g["params"]) + ",", 1)[:-1],
byte_array(g["hostseckey"]),
byte_array(g["random"]),
byte_array(g["auxRand"]),
byte_array(g["pmsg1"]),
indent(byte_array(g["cmsg1"]) + ",", 1)[:-1],
byte_array(g["pmsg2"]),
))
s += "};\n"
finalize_cases = []
for gi, g in enumerate(groups(data["participant_finalize"])):
for c in g.get("validTestCases", []):
finalize_cases.append((gi, 1, c))
for c in g.get("errorTestCases", []):
if map_error(c, "participant_finalize") is None:
continue
finalize_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_finalize_case chilldkg_vec_finalize_cases[%d] = {\n" % len(finalize_cases)
for gi, is_valid, c in finalize_cases:
if is_valid:
error_code = "CHILLDKG_VEC_OK"
eo = init_dkg_output(c["expectedOutput"]["dkgOutput"])
rec = byte_array(c["expectedOutput"]["recoveryData"])
else:
error_code, _ = map_error(c, "participant_finalize")
eo = "{ 0, { 0 }, { 0 }, { { 0 } } }"
rec = "{ 0 }"
s += indent("{ %d,\n%s,\n%s,\n%s,\n%s },\n" % (
is_valid,
indent(byte_array(c["cmsg2"]) + ",", 1)[:-1],
error_code,
indent(eo + ",", 1)[:-1],
indent(rec, 1),
))
s += "};\n"
s += "static const size_t chilldkg_vec_finalize_group_case_counts[%d] = {\n" % len(groups(data["participant_finalize"]))
for gi in range(len(groups(data["participant_finalize"]))):
cnt = sum(1 for x in finalize_cases if x[0] == gi)
s += indent("%d,\n" % cnt)
s += "};\n"
# participant_investigate vectors
s += """
struct chilldkg_vec_investigate_group {
struct chilldkg_vec_params params;
unsigned char hostseckey[32];
unsigned char random[32];
unsigned char aux_rand[32];
unsigned char pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
size_t n_cmsg1;
unsigned char cmsg1_pool[CHILLDKG_VEC_MAX_CMSG1_POOL][CHILLDKG_VEC_MAX_CMSG1_LEN];
};
struct chilldkg_vec_investigate_case {
size_t cmsg1_index;
unsigned char cinv[CHILLDKG_VEC_MAX_CINV_LEN];
/* Expected result of participant_investigate. */
int expected_error;
uint32_t expected_fault_index;
};
"""
s += "static const struct chilldkg_vec_investigate_group chilldkg_vec_investigate_groups[%d] = {\n" % len(groups(data["participant_investigate"]))
for g in groups(data["participant_investigate"]):
pool = g["cmsg1Pool"]
inner = "\n" + indent(",\n".join(byte_array(m) for m in pool), 2) + "\n"
s += indent("{\n%s,\n%s, %s, %s, %s,\n%s, {%s} },\n" % (
indent(init_params(g["params"]) + ",", 1)[:-1],
byte_array(g["hostseckey"]),
byte_array(g["random"]),
byte_array(g["auxRand"]),
byte_array(g["pmsg1"]),
len(pool),
inner,
))
s += "};\n"
investigate_cases = []
for gi, g in enumerate(groups(data["participant_investigate"])):
for c in g.get("errorTestCases", []):
investigate_cases.append((gi, c))
s += "static const struct chilldkg_vec_investigate_case chilldkg_vec_investigate_cases[%d] = {\n" % len(investigate_cases)
for gi, c in investigate_cases:
error_code, fault_index = map_error(c, "participant_investigate")
s += indent("{ %d,\n%s,\n%s, %s },\n" % (
c["cmsg1Index"],
indent(byte_array(c["cinvMsg"]) + ",", 1)[:-1],
error_code, fault_index,
))
s += "};\n"
s += "static const size_t chilldkg_vec_investigate_group_case_counts[%d] = {\n" % len(groups(data["participant_investigate"]))
for gi in range(len(groups(data["participant_investigate"]))):
cnt = sum(1 for x in investigate_cases if x[0] == gi)
s += indent("%d,\n" % cnt)
s += "};\n"
# coordinator_step1 vectors
s += """
struct chilldkg_vec_coord_step1_group {
size_t n_pmsg1_pool;
unsigned char pmsg1_pool[CHILLDKG_VEC_MAX_PMSG1_POOL][CHILLDKG_VEC_MAX_PMSG1_LEN];
};
struct chilldkg_vec_coord_step1_case {
int is_valid;
size_t n_pmsgs1;
size_t pmsg1_indices[CHILLDKG_VEC_MAX_INDICES];
struct chilldkg_vec_params params;
/* Expected result: CHILLDKG_VEC_OK or an enum chilldkg_vec_error value. */
int expected_error;
uint32_t expected_fault_index;
unsigned char expected_cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
};
"""
s += "static const struct chilldkg_vec_coord_step1_group chilldkg_vec_coord_step1_groups[%d] = {\n" % len(groups(data["coordinator_step1"]))
for g in groups(data["coordinator_step1"]):
pool = g["pmsg1Pool"]
inner = "\n" + indent(",\n".join(byte_array(m) for m in pool), 2) + "\n"
s += indent("{ %d, {%s} },\n" % (len(pool), inner))
s += "};\n"
coord_step1_cases = []
for gi, g in enumerate(groups(data["coordinator_step1"])):
for c in g.get("validTestCases", []):
coord_step1_cases.append((gi, 1, c))
for c in g.get("errorTestCases", []):
if map_error(c, "coordinator_step1") is None:
continue
coord_step1_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_coord_step1_case chilldkg_vec_coord_step1_cases[%d] = {\n" % len(coord_step1_cases)
for gi, is_valid, c in coord_step1_cases:
if is_valid:
error_code, fault_index = "CHILLDKG_VEC_OK", "UINT32_MAX"
expected = byte_array(c["expectedCmsg1"])
else:
error_code, fault_index = map_error(c, "coordinator_step1")
expected = "{ 0 }"
s += indent("{ %d, %d, { %s },\n%s,\n%s, %s,\n%s },\n" % (
is_valid,
len(c["pmsg1Indices"]),
", ".join(map(str, c["pmsg1Indices"])),
indent(init_params(c["params"]) + ",", 1)[:-1],
error_code, fault_index,
indent(expected, 1),
))
s += "};\n"
s += "static const size_t chilldkg_vec_coord_step1_group_case_counts[%d] = {\n" % len(groups(data["coordinator_step1"]))
for gi in range(len(groups(data["coordinator_step1"]))):
cnt = sum(1 for x in coord_step1_cases if x[0] == gi)
s += indent("%d,\n" % cnt)
s += "};\n"
# coordinator_finalize vectors
s += """
struct chilldkg_vec_coord_finalize_group {
struct chilldkg_vec_params params;
unsigned char pmsgs1[CHILLDKG_VEC_MAX_PARTICIPANTS][CHILLDKG_VEC_MAX_PMSG1_LEN];
unsigned char cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
size_t n_pmsg2_pool;
unsigned char pmsg2_pool[CHILLDKG_VEC_MAX_PMSG2_POOL][64];
};
struct chilldkg_vec_coord_finalize_case {
int is_valid;
size_t n_pmsgs2;
size_t pmsg2_indices[CHILLDKG_VEC_MAX_INDICES];
/* Expected result: CHILLDKG_VEC_OK or CHILLDKG_VEC_FAULTY_PARTICIPANT. */
int expected_error;
uint32_t expected_fault_index;
unsigned char expected_cmsg2[CHILLDKG_VEC_MAX_CMSG2_LEN];
struct chilldkg_vec_dkg_output expected_output;
unsigned char expected_recovery[CHILLDKG_VEC_MAX_RECOVERY_LEN];
};
"""
s += "static const struct chilldkg_vec_coord_finalize_group chilldkg_vec_coord_finalize_groups[%d] = {\n" % len(groups(data["coordinator_finalize"]))
for g in groups(data["coordinator_finalize"]):
pool = g["pmsg2Pool"]
p1_inner = "\n" + indent(",\n".join(byte_array(m) for m in g["pmsgs1"]), 2) + "\n"
p2_inner = "\n" + indent(",\n".join(byte_array(m) for m in pool), 2) + "\n"
s += indent("{\n%s,\n{%s},\n%s,\n%s, {%s} },\n" % (
indent(init_params(g["params"]) + ",", 1)[:-1],
p1_inner,
indent(byte_array(g["cmsg1"]) + ",", 1)[:-1],
len(pool),
p2_inner,
))
s += "};\n"
coord_finalize_cases = []
for gi, g in enumerate(groups(data["coordinator_finalize"])):
for c in g.get("validTestCases", []):
coord_finalize_cases.append((gi, 1, c))
for c in g.get("errorTestCases", []):
if map_error(c, "coordinator_finalize") is None:
continue
coord_finalize_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_coord_finalize_case chilldkg_vec_coord_finalize_cases[%d] = {\n" % len(coord_finalize_cases)
for gi, is_valid, c in coord_finalize_cases:
if is_valid:
error_code, fault_index = "CHILLDKG_VEC_OK", "UINT32_MAX"
expected_cmsg2 = byte_array(c["expectedOutput"]["cmsg2"])
eo = init_dkg_output(c["expectedOutput"]["dkgOutput"])
rec = byte_array(c["expectedOutput"]["recoveryData"])
else:
error_code, fault_index = map_error(c, "coordinator_finalize")
expected_cmsg2 = "{ 0 }"
eo = "{ 0, { 0 }, { 0 }, { { 0 } } }"
rec = "{ 0 }"
s += indent("{ %d, %d, { %s }, %s, %s,\n%s,\n%s,\n%s },\n" % (
is_valid,
len(c["pmsg2Indices"]),
", ".join(map(str, c["pmsg2Indices"])),
error_code, fault_index,
indent(expected_cmsg2 + ",", 1)[:-1],
indent(eo + ",", 1)[:-1],
indent(rec, 1),
))
s += "};\n"
s += "static const size_t chilldkg_vec_coord_finalize_group_case_counts[%d] = {\n" % len(groups(data["coordinator_finalize"]))
for gi in range(len(groups(data["coordinator_finalize"]))):
cnt = sum(1 for x in coord_finalize_cases if x[0] == gi)
s += indent("%d,\n" % cnt)
s += "};\n"
# coordinator_investigate vectors
s += """
struct chilldkg_vec_coord_investigate_group {
struct chilldkg_vec_params params;
unsigned char pmsgs1[CHILLDKG_VEC_MAX_PARTICIPANTS][CHILLDKG_VEC_MAX_PMSG1_LEN];
unsigned char expected_cinv[CHILLDKG_VEC_MAX_PARTICIPANTS][CHILLDKG_VEC_MAX_CINV_LEN];
};
"""
s += "static const struct chilldkg_vec_coord_investigate_group chilldkg_vec_coord_investigate_groups[%d] = {\n" % len(groups(data["coordinator_investigate"]))
for g in groups(data["coordinator_investigate"]):
p1_inner = "\n" + indent(",\n".join(byte_array(m) for m in g["pmsgs1"]), 2) + "\n"
cinv_inner = "\n" + indent(",\n".join(byte_array(m) for m in g["validTestCases"][0]["expectedCinvMsgs"]), 2) + "\n"
s += indent("{\n%s,\n{%s},\n{%s} },\n" % (
indent(init_params(g["params"]) + ",", 1)[:-1],
p1_inner,
cinv_inner,
))
s += "};\n"
# recover vectors
s += """
struct chilldkg_vec_recover_case {
int is_valid;
/* Coordinator recovery if has_hostseckey == 0. */
int has_hostseckey;
unsigned char hostseckey[32];
size_t recovery_len;
unsigned char recovery[CHILLDKG_VEC_MAX_RECOVERY_LEN];
/* Expected result: CHILLDKG_VEC_OK or CHILLDKG_VEC_INVALID_INPUT. */
int expected_error;
struct chilldkg_vec_dkg_output expected_output;
struct chilldkg_vec_params expected_params;
};
"""
recover_cases = []
for c in data["recover"]["validTestCases"]:
recover_cases.append((1, c))
for c in data["recover"]["errorTestCases"]:
if map_error(c, "recover") is None:
continue
recover_cases.append((0, c))
s += "static const struct chilldkg_vec_recover_case chilldkg_vec_recover_cases[%d] = {\n" % len(recover_cases)
for is_valid, c in recover_cases:
if is_valid:
error_code = "CHILLDKG_VEC_OK"
eo = init_dkg_output(c["expectedOutput"]["dkgOutput"])
ep = init_params(c["expectedOutput"]["params"])
else:
error_code, _ = map_error(c, "recover")
eo = "{ 0, { 0 }, { 0 }, { { 0 } } }"
ep = "{ 0, 0, { { 0 } } }"
s += indent("{ %d, %d, %s, %d,\n%s,\n%s,\n%s,\n%s },\n" % (
is_valid,
1 if c["hostseckey"] else 0,
byte_array(c["hostseckey"] or "00" * 32),
len(bytes.fromhex(c["recoveryData"])),
indent(byte_array(c["recoveryData"]) + ",", 1)[:-1],
error_code,
indent(eo + ",", 1)[:-1],
indent(ep, 1),
))
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"
s += "\n#endif\n"
print(s)