Files
secp256k1-zkp/src/modules/chilldkg/chilldkg.md

125 lines
6.6 KiB
Markdown
Raw Normal View History

chilldkg: Phase 0 - module scaffolding and build wiring Add an empty, experimental `chilldkg` module as the foundation for a ChillDKG implementation (distributed key generation for FROST) per the bip-frost-dkg BIP draft (v0.3.0-dev): https://github.com/BlockstreamResearch/bip-frost-dkg The module lives in src/modules/chilldkg/ (separate from the frost module, per the implementation plan in .idea/docs/ chilldkg-implementation-plan.md: FROST signing (BIP 445) and ChillDKG are separate BIPs with separate reference repos, test vectors and review cycles; the dependency between them is one-way bytes). New files: - include/secp256k1_chilldkg.h: public header skeleton with the same "EXTREMELY DANGEROUS / work in progress" warning style as secp256k1_frost.h, plus a note that the BIP is a draft and tagged hashes/wire formats may change. No API yet (Phase 3+). - src/modules/chilldkg/main_impl.h: implementation skeleton including the public header. - src/modules/chilldkg/tests_impl.h: trivial scaffolding unit test (chilldkg_scaffolding_test) registered via the tests_chilldkg[] CASE1 array used by this repo's unit-test framework. - src/modules/chilldkg/Makefile.am.include: autotools file list, mirroring the frost module's. - src/modules/chilldkg/chilldkg.md: module doc stub (purpose, draft status, dependency on the schnorrsig and ecdh modules). Build wiring (mirrors the frost module exactly): - configure.ac: --enable-module-chilldkg (default no, experimental gate), dependency errors when schnorrsig or ecdh are explicitly disabled, AM_CONDITIONAL(ENABLE_MODULE_CHILLDKG), summary line. - Makefile.am: include src/modules/chilldkg/Makefile.am.include under ENABLE_MODULE_CHILLDKG. - src/secp256k1.c: guarded include of modules/chilldkg/main_impl.h after the frost module. - src/tests.c: guarded include of tests_impl.h and MAKE_TEST_MODULE(chilldkg) registration. - CMakeLists.txt: SECP256K1_ENABLE_MODULE_CHILLDKG option (OFF) + summary line. - src/CMakeLists.txt: dependency checks on SECP256K1_ENABLE_MODULE_SCHNORRSIG and SECP256K1_ENABLE_MODULE_ECDH, ENABLE_MODULE_CHILLDKG=1 compile definition, public header export. Verified: - ./autogen.sh && ./configure --enable-experimental --enable-module-chilldkg --enable-module-schnorrsig --enable-module-ecdh && make check: PASS 3/3 (tests, noverify_tests, exhaustive_tests). - configure fails with a clear error when schnorrsig or ecdh are disabled, or when experimental is not enabled. - CMake build with SECP256K1_ENABLE_MODULE_CHILLDKG=ON: ctest 345/345 passed; dependency errors fire correctly when schnorrsig/ecdh OFF.
2026-08-31 01:37:22 +02:00
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.
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
The implementation and its test vectors
(`tools/test_vectors_chilldkg_generate.py`) are validated against the pinned
reference commit `a91896883f85b159415ecf298d5e844879af112d` (v0.3.0-dev).
chilldkg: Phase 0 - module scaffolding and build wiring Add an empty, experimental `chilldkg` module as the foundation for a ChillDKG implementation (distributed key generation for FROST) per the bip-frost-dkg BIP draft (v0.3.0-dev): https://github.com/BlockstreamResearch/bip-frost-dkg The module lives in src/modules/chilldkg/ (separate from the frost module, per the implementation plan in .idea/docs/ chilldkg-implementation-plan.md: FROST signing (BIP 445) and ChillDKG are separate BIPs with separate reference repos, test vectors and review cycles; the dependency between them is one-way bytes). New files: - include/secp256k1_chilldkg.h: public header skeleton with the same "EXTREMELY DANGEROUS / work in progress" warning style as secp256k1_frost.h, plus a note that the BIP is a draft and tagged hashes/wire formats may change. No API yet (Phase 3+). - src/modules/chilldkg/main_impl.h: implementation skeleton including the public header. - src/modules/chilldkg/tests_impl.h: trivial scaffolding unit test (chilldkg_scaffolding_test) registered via the tests_chilldkg[] CASE1 array used by this repo's unit-test framework. - src/modules/chilldkg/Makefile.am.include: autotools file list, mirroring the frost module's. - src/modules/chilldkg/chilldkg.md: module doc stub (purpose, draft status, dependency on the schnorrsig and ecdh modules). Build wiring (mirrors the frost module exactly): - configure.ac: --enable-module-chilldkg (default no, experimental gate), dependency errors when schnorrsig or ecdh are explicitly disabled, AM_CONDITIONAL(ENABLE_MODULE_CHILLDKG), summary line. - Makefile.am: include src/modules/chilldkg/Makefile.am.include under ENABLE_MODULE_CHILLDKG. - src/secp256k1.c: guarded include of modules/chilldkg/main_impl.h after the frost module. - src/tests.c: guarded include of tests_impl.h and MAKE_TEST_MODULE(chilldkg) registration. - CMakeLists.txt: SECP256K1_ENABLE_MODULE_CHILLDKG option (OFF) + summary line. - src/CMakeLists.txt: dependency checks on SECP256K1_ENABLE_MODULE_SCHNORRSIG and SECP256K1_ENABLE_MODULE_ECDH, ENABLE_MODULE_CHILLDKG=1 compile definition, public header export. Verified: - ./autogen.sh && ./configure --enable-experimental --enable-module-chilldkg --enable-module-schnorrsig --enable-module-ecdh && make check: PASS 3/3 (tests, noverify_tests, exhaustive_tests). - configure fails with a clear error when schnorrsig or ecdh are disabled, or when experimental is not enabled. - CMake build with SECP256K1_ENABLE_MODULE_CHILLDKG=ON: ctest 345/345 passed; dependency errors fire correctly when schnorrsig/ecdh OFF.
2026-08-31 01:37:22 +02:00
**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).
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
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.