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.
|