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.
6.6 KiB
Notes on the chilldkg module API
This module implements ChillDKG, a distributed key generation (DKG) protocol
for FROST, as specified by the bip-frost-dkg BIP draft
(https://github.com/BlockstreamResearch/bip-frost-dkg, version 0.3.0-dev). In
a ChillDKG session, n participants jointly generate a threshold key with the
help of an untrusted coordinator, such that no party (including the
coordinator) learns more than its own secret share, and every participant
obtains the threshold public key and the public shares of all participants.
The output is designed to feed directly into the frost module
(include/secp256k1_frost.h), which implements FROST signing (BIP 445) and
explicitly leaves DKG out of scope.
The implementation and its test vectors
(tools/test_vectors_chilldkg_generate.py) are validated against the pinned
reference commit a91896883f85b159415ecf298d5e844879af112d (v0.3.0-dev).
This module is experimental. Do not use it in production. The API should not be considered stable. The underlying BIP is still a draft (v0.3.0-dev): tagged hashes, wire formats, and protocol details may change in future BIP versions.
The module depends on the schnorrsig module (for the CertEq certificate and proofs of possession) and the ecdh module (for encrypted share distribution).
A usage example can be found in examples/chilldkg.c.
Protocol summary
Participants are identified by identifiers 0..n-1; participant i sits at
polynomial x-coordinate i + 1 (the same convention as the frost module). The
threshold t is the number of participants required to sign. The number of
participants must not exceed SECP256K1_CHILLDKG_MAX_PARTICIPANTS (128); the
state objects are fixed-size and the library does not allocate memory, so all
message buffers are caller-allocated (use the secp256k1_chilldkg_*_msg1_len,
secp256k1_chilldkg_*_msg2_len, secp256k1_chilldkg_recovery_data_len and
secp256k1_chilldkg_investigation_msg_len helpers to size them).
The coordinator is untrusted: it routes and aggregates messages but learns nothing about the secret shares, and it cannot forge the outcome — all its messages are verified by the participants. All coordinator sends are broadcasts to all participants.
| Step | Function | Message | Size | Contents |
|---|---|---|---|---|
| participant step 1 | secp256k1_chilldkg_participant_step1 |
pmsg1 | 33t + 32n + 97 | VSS commitment, PoP, pubnonce, encrypted shares |
| coordinator step 1 | secp256k1_chilldkg_coordinator_step1 |
cmsg1 | 162n + 33(t-1) | commitments, pops, pubnonces, summed encrypted shares |
| participant step 2 | secp256k1_chilldkg_participant_step2 |
pmsg2 | 64 | CertEq signature over the transcript |
| coordinator finalize | secp256k1_chilldkg_coordinator_finalize |
cmsg2 | 64n | certificate (the n CertEq signatures) |
| participant finalize | secp256k1_chilldkg_participant_finalize |
— | — | DKG output + recovery data |
The resulting threshold public key is tweaked with a TapTweak-style tweak such that the BIP 341 script path is unspendable; the tweak is already included in the (secret and public) shares output by this module, so the outputs can be used with the frost module directly (initialize the frost tweak cache from the threshold public key and apply no further tweaks, unless the application wants additional tweaks).
Blame taxonomy
Protocol functions report faults via the secp256k1_chilldkg_fault enum plus
a fault_index output:
SECP256K1_CHILLDKG_FAULTY_COORDINATOR: the coordinator deviated from the protocol (detected by a participant).SECP256K1_CHILLDKG_FAULTY_PARTICIPANT(i): participant i deviated from the protocol (detected by the coordinator, or an invalid recovery acknowledgment of participant i).SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR(i): participant i appears to be faulty, but the coordinator may have framed them (detected by a participant).SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR: a fault was detected but cannot be attributed without the investigation procedure; callsecp256k1_chilldkg_coordinator_investigate(coordinator) andsecp256k1_chilldkg_participant_investigate(participant, with theinv_dataoutput bysecp256k1_chilldkg_participant_step2) to attribute the fault.SECP256K1_CHILLDKG_INVALID_INPUT: the caller provided invalid input (bad host secret key, invalid session parameters, invalid recovery data).
Recovery
secp256k1_chilldkg_participant_finalize and
secp256k1_chilldkg_coordinator_finalize output the recovery data
(eq_input || certificate). It is self-delimiting and self-authenticating:
anyone holding it can verify that the session succeeded, and a participant
can recover its DKG output at any time from the recovery data and its host
secret key (secp256k1_chilldkg_participant_recover; the coordinator uses
secp256k1_chilldkg_coordinator_recover). This also covers the case where
some participants never receive cmsg2: they can be convinced of the success
of the session later by being presented with the recovery data.
Callers SHOULD ensure that all participants deem the session successful
before using the threshold public key (e.g., before sending funds to it). The
recommended way is to collect recovery acknowledgments from all participants
(secp256k1_chilldkg_recovery_ack_sign and
secp256k1_chilldkg_recovery_acks_verify).
Security notes
- The host secret key is the participant's long-term identity and the basis of all session secrets. The same host key pair may be reused in multiple sessions. Do NOT erase the host secret key after a session, even a failed one: another participant may have succeeded, and this participant can recover its output later from the recovery data.
random32insecp256k1_chilldkg_participant_step1must be FRESH cryptographically secure randomness for every session; reusing it across sessions can leak the host secret key. The function rejects all-zero randomness as a guard against a malfunctioning RNG.state2andinv_datacontain secret key material (the secret share and decryption pads); keep them secret and do not copy them.state1and the coordinator state contain no secrets and can be copied (e.g., to persist them).- Each state object must not be reused: pass
state1to at most oneparticipant_step2call,state2to at most oneparticipant_finalizecall, and the coordinator state to at most onecoordinator_finalizecall. - The recovery data does not reveal the secret shares (they are encrypted), but it commits to the session's participants and outcome; treat it as sensitive metadata.