Final phase of the ChillDKG module: upstream test vectors, a DKG->FROST integration test, boundary tests, full module documentation and a runnable example. Test vectors: - tools/test_vectors_chilldkg_generate.py converts all 10 upstream bip-frost-dkg JSON vector files into src/modules/chilldkg/vectors.h (modeled on tools/test_vectors_frost_generate.py; takes the vectors directory as an argument; upstream pinned to commit a91896883f85b159415ecf298d5e844879af112d, recorded in the generated header with the exact regeneration invocation; regeneration is reproducible byte-for-byte). - tests_impl.h vector runners execute 191 of 241 upstream cases through the public API: hostpubkey_gen, params_hash, participant_step1/step2/finalize/investigate, coordinator_step1/finalize/investigate, recover. Happy paths are byte-exact (pmsg1/cmsg1/pmsg2/cmsg2/dkg_output/recovery/cinv); error cases assert both the fault enum and fault_index against expectedError.participantId. The 50 skipped cases are wrong-length/wrong-count inputs not expressible with the fixed-size C API; each skip is documented in vectors.h. Boundary/robustness tests: t=1, t=n, n=2, a full n=128/t=2 session end-to-end with per-participant secshare*G == pubshare checks and a recovery roundtrip, and a state1 memcpy roundtrip (step2 from a copied state object). DKG->FROST integration test (guarded by ENABLE_MODULE_FROST): a full ChillDKG session (n=3, t=2) feeds (secshare, thresh_pk, pubshares) directly into the frost module. ChillDKG's thresh_pk is already TapTweak'ed, so frost_tweak_cache_init is called with no further tweaks (frost's tweaked x-only key asserted equal to the x-only part of the ChillDKG thresh_pk); signers 0 and 2 run nonce_gen, nonce_agg, session_init with the shared x = id+1 convention, frost_sign, partial_sig_verify and partial_sig_agg; the aggregate signature verifies as a plain BIP-340 signature against the threshold key. Example: examples/chilldkg.c runs a full 2-of-3 DKG session (host key generation, params hash, participant/coordinator steps, finalize, and a recovery roundtrip via participant_recover) with fixed-size buffers and secret erasure. Wired into Makefile.am and examples/CMakeLists.txt exactly like frost_example (runs as a TEST); chilldkg_example binary added to .gitignore. Docs: src/modules/chilldkg/chilldkg.md now documents the protocol summary, message-flow table with exact byte sizes, blame taxonomy, recovery workflow, security notes (host key reuse/retention, fresh randomness per session, state secrecy, recovery-data sensitivity) and the pinned reference commit; src/modules/frost/frost.md points at the new module as the intended DKG. Bug fix found by the vector runner (recover tcId 9): the internal recover() passed a possibly-NULL fault_index from coordinator_recover to certeq_verify, which dereferences it on failure; now uses a local. Verified: make check 10/10 (3 test suites + 7 examples incl. chilldkg_example, exit 0 when run); CMake ctest 428/428 with chilldkg + frost, and a no-frost build confirms the ENABLE_MODULE_FROST guard; make distdir includes vectors.h, the example and the generator. The module is feature-complete against bip-frost-dkg v0.3.0-dev at a91896883f85b159415ecf298d5e844879af112d. The BIP is still a draft; tagged hashes and wire formats may change upstream.
725 lines
26 KiB
Python
725 lines
26 KiB
Python
#!/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)
|