Files
secp256k1-zkp/src/modules/chilldkg/chilldkg.md
Kgothatso Ngako d1c817b528 chilldkg: Phase 6 - test vectors, FROST integration, docs, example
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.
2026-08-31 06:52:58 +02:00

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; call secp256k1_chilldkg_coordinator_investigate (coordinator) and secp256k1_chilldkg_participant_investigate (participant, with the inv_data output by secp256k1_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.
  • random32 in secp256k1_chilldkg_participant_step1 must 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.
  • state2 and inv_data contain secret key material (the secret share and decryption pads); keep them secret and do not copy them. state1 and the coordinator state contain no secrets and can be copied (e.g., to persist them).
  • Each state object must not be reused: pass state1 to at most one participant_step2 call, state2 to at most one participant_finalize call, and the coordinator state to at most one coordinator_finalize call.
  • 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.