4 Commits

Author SHA1 Message Date
Kgothatso Ngako
d48a1579cf chilldkg: Phase 5 - recovery, recovery acks, investigation
Complete the ChillDKG protocol surface with the recovery and blame-
attribution procedures (bip-frost-dkg v0.3.0-dev, reference commit
a91896883f85b159415ecf298d5e844879af112d).

Recovery:
- secp256k1_chilldkg_participant_recover / _coordinator_recover: parse
  the self-delimiting recovery layout u32be(t) || sum_coms(33t) ||
  hostpubkeys(33n) || pubnonces(33n) || enc_secshares(32n, checked) ||
  cert(64n), deriving n = (len-4-33t)/162 exactly as the reference's
  deserialize_recovery_data; re-verify the certificate, recompute the
  TapTweak and the receiver's ECDH/self pads from (hostseckey,
  pubnonces, enc_context), recompute the tweaked secshare, and
  sanity-check secshare*G == pubshares[own]. Also return hostpubkeys,
  n and t so callers can re-derive session params. RecoveryDataError /
  HostSeckeyError / params failures map to INVALID_INPUT (no index,
  as in the reference); an invalid pubnonce during decrypt passes
  through as FAULTY_PARTICIPANT_OR_COORDINATOR(i), matching the
  reference leaking that exception from recover().

Recovery acks:
- secp256k1_chilldkg_recovery_ack_sign / _acks_verify: BIP-340
  (standard BIP0340 tags) over pad33("BIP DKG/recovery acknowledgment")
  || u32be(i) || recovery_data. Verification failure maps to
  FAULTY_PARTICIPANT(i) (InvalidRecoveryAckError subclasses
  FaultyParticipantError in the reference).

Investigation:
- secp256k1_chilldkg_coordinator_investigate: builds one 65n-byte
  per-participant message (per-dealer encrypted partial secshares
  (32n) + partial pubshares (33n)) per call; the reference returns all
  n at once -- equivalent, the caller iterates.
- secp256k1_chilldkg_participant_investigate: the reference's
  three-step blame attribution -- sum-of-pubshares check ->
  FAULTY_COORDINATOR; sum-of-secshares check -> FAULTY_COORDINATOR
  (covers the reference's SecshareSumError translation); per-dealer
  decrypted share vs commitment -> FAULTY_PARTICIPANT_OR_COORDINATOR(i)
  (or FAULTY_COORDINATOR for the own index); all-consistent ->
  INVALID_INPUT (the reference's uncaught RuntimeError).
- Investigation data is transported via a new opaque, secret-bearing
  secp256k1_chilldkg_participant_inv_data object (4205 bytes,
  magic-validated save/load, secret-cleared) filled by
  participant_step2 on the UNKNOWN_FAULT paths. This amends the
  Phase 3 participant_step2 signature with a nullable inv_data
  out-param -- required because recomputing inside
  participant_investigate would duplicate step2's decrypt/verify
  logic.
- New length helper secp256k1_chilldkg_investigation_msg_len (65n).

tests_impl.h: chilldkg_recovery_test (recover roundtrips byte-exact
vs the session outputs and reference vectors, tampered/truncated/
over-long recovery data, unknown/invalid hostseckey, ack sign
byte-exact + verify with wrong-index and tampered-ack blame,
params/recovery mismatch rejects, misuse) and
chilldkg_investigate_test (two end-to-end public-API scenarios
generated from the reference: dealer corrupting a participant's
encrypted share, and coordinator tampering with an enc_secshare;
cmsg1/cinv/inv_data byte-exact, blame codes and indices matching the
reference's exception type and index; malformed cinv ->
FAULTY_COORDINATOR; malformed pmsg1 -> FAULTY_PARTICIPANT(j); misuse).
The all-consistent investigate path is not constructible without
discrete logs and matches the reference's unreachable RuntimeError.

Verified: make check 3/3 (incl. noverify); CMake ctest 369/369;
make distdir clean.
2026-08-31 06:09:03 +02:00
Kgothatso Ngako
5409aae813 chilldkg: Phase 4 - public coordinator API
Add the coordinator side of the ChillDKG protocol
(bip-frost-dkg v0.3.0-dev, reference commit
a91896883f85b159415ecf298d5e844879af112d), as thin wrappers over the
Phase 2 encpedpop coordinator internals (whose cmsg1 output was
already verified byte-identical to the reference coordinator_step1).

Public API:
- secp256k1_chilldkg_coordinator_step1: takes an array of pointers to
  the n participant pmsg1 messages (musig/frost-style convention),
  parses each with checked scalar parse, aggregates SimplPedPop and
  EncPedPop, builds eq_input (including the enc_secshares suffix,
  mirroring the reference) and emits cmsg1 (162n + 33(t-1) bytes).
  PoPs are not verified coordinator-side, exactly as the reference.
- secp256k1_chilldkg_coordinator_finalize: concatenates the n CertEq
  pmsg2 signatures into the 64n-byte certificate, verifies all of them
  via certeq_verify (hostpubkeys recovered from eq_input at offset
  4+33t), and outputs the coordinator-side DKG result: threshold
  pubkey, pubshares and recovery data -- no secshare.
- secp256k1_chilldkg_coordinator_state: opaque, 21041 bytes,
  magic-validated, holds only t, n, eq_input, thresh_pk and pubshares
  -- no secret material, documented as freely copyable/persistable so
  a stateless coordinator is possible.

Blame mapping (verified against chilldkg.py):
- malformed pmsg1 (bad commitment encoding, overflowing encrypted
  share) -> FAULTY_PARTICIPANT(sender index),
- invalid CertEq signature -> FAULTY_PARTICIPANT(failing index)
  (deliberately different from participant_finalize, which maps the
  same failure to FAULTY_COORDINATOR -- matching the reference),
- invalid session params -> INVALID_INPUT; all outputs zeroed on
  failure.

tests_impl.h: chilldkg_coordinator_api_test runs a full n=3,t=2
session through only the public APIs on both sides, byte-exact
against the Python reference vectors and cross-checked against every
participant's finalize outputs; blame cases (malformed pmsg1 and
overflowing share -> FAULTY_PARTICIPANT with the right index, bad
CertEq sig -> FAULTY_PARTICIPANT(2), zeroed outputs); misuse coverage
(NULL args, corrupted state magic).

Verified: make check 3/3 (incl. noverify); CMake ctest 365/365;
./tests --target=chilldkg green.
2026-08-31 05:33:42 +02:00
Kgothatso Ngako
2a0e14d076 chilldkg: Phase 3 - public participant API and CertEq
Add the public participant-facing ChillDKG API to
include/secp256k1_chilldkg.h and the CertEq sub-protocol, completing
the participant side of the protocol (bip-frost-dkg v0.3.0-dev,
reference pinned at a91896883f85b159415ecf298d5e844879af112d).

New module files:
- certeq.h / certeq_impl.h: CertEq sub-protocol. Participants sign
  pad33("BIP DKG/certeq message") || u32be(i) || eq_input with plain
  BIP0340-tagged Schnorr signatures under their host key
  (certeq_participant_step); verification is per-index against the
  x-only hostpubkeys[i][1:33] exactly as the reference
  (certeq_verify). The coordinator side reuses certeq_verify in
  Phase 4.

Public API (all no-malloc, caller-allocated buffers, outputs zeroed on
failure, secret paths cleared):
- secp256k1_chilldkg_hostpubkey_gen: plain compressed host pubkey
  generation; rejects zero / >= group order seckeys.
- secp256k1_chilldkg_params_hash: validates session params (participant
  and threshold ranges, strictly compressed non-infinity pubkeys, no
  duplicates) and computes TH("BIP DKG/params_hash", u32be(t) ||
  hostpubkeys).
- Message-length helpers so callers can size buffers:
  participant_msg1_len (33t+32n+97), coordinator_msg1_len
  (162n+33(t-1)), participant_msg2_len (64), coordinator_msg2_len
  (64n), recovery_data_len (4+33t+162n).
- secp256k1_chilldkg_participant_step1: full EncPedPop step1 with
  seed=deckey=hostseckey; rejects zero randomness and hostseckeys not
  matching the claimed hostpubkey (input errors, not protocol faults).
- secp256k1_chilldkg_participant_step2: parses and verifies cmsg1 via
  the Phase 2 encpedpop/simplpedpop participant path, computes the
  tweaked secshare/pubshares/threshold pubkey, appends enc_secshares
  to eq_input (matching the reference for recovery consistency), and
  emits the 64-byte CertEq signature.
- secp256k1_chilldkg_participant_finalize: re-verifies all n CertEq
  signatures in the certificate, then outputs the 32-byte secshare,
  33-byte threshold pubkey, n pubshares and the self-delimiting
  recovery data (eq_input || cert).

Blame reporting without exceptions: public enum
secp256k1_chilldkg_fault (OK / FAULTY_COORDINATOR /
FAULTY_PARTICIPANT / FAULTY_PARTICIPANT_OR_COORDINATOR /
UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR / INVALID_INPUT) plus an out
fault_index, mapping the reference's exception taxonomy:
- hostseckey invalid/mismatch -> INVALID_INPUT (HostSeckeyError),
- cmsg1 scalar overflow/parse -> FAULTY_COORDINATOR (MsgParseError),
- pubnonce/commitment/PoP faults -> FAULTY_PARTICIPANT_OR_COORDINATOR(i),
- share-vs-pubshare mismatch -> UNKNOWN with fault_index = UINT32_MAX,
- certificate signature failure -> FAULTY_COORDINATOR (documented
  deviation: fault_index carries the failing signature index as
  diagnostic info; the reference discards it).
Enum-returning functions use a local CHILLDKG_ARG_CHECK that fires the
illegal-argument callback and returns INVALID_INPUT (ARG_CHECK would
return 0 = OK).

Opaque state objects with magic-validated save/load (frost idiom):
participant_state1 (4306 bytes, no secrets) and participant_state2
(21073 bytes, contains the secshare; documented keep-secret/no-copy).
Fixed-size at SECP256K1_CHILLDKG_MAX_PARTICIPANTS = 128.

Also fixes a noverify-build bug: state1_load ran point_load inside
VERIFY_CHECK, which compiles out in noverify builds and left the
commitment uninitialized; now called unconditionally.

tests_impl.h: participant_api_test with full-session reference vectors
(n=3, t=2; coordinator aggregation simulated through the internal
Phase 2 coordinator step and verified byte-identical to the
reference's coordinator_step1): msglen helpers, hostpubkey_gen and
params_hash vectors incl. duplicate/invalid/infinity rejection,
byte-exact pmsg1/cmsg1/CertEq sigs/secshare/thresh_pk/pubshares/
recovery, blame cases (tampered enc_secshare -> UNKNOWN, invalid
pubnonce -> FAULTY_PARTICIPANT_OR_COORDINATOR(1), overflowing
enc_secshare -> FAULTY_COORDINATOR, corrupted cert sig ->
FAULTY_COORDINATOR with fault_index and zeroed outputs), NULL-arg
misuse and bad-magic state rejection.

Verified: make check 3/3 (incl. noverify); CMake ctest 363/363;
make distdir includes all new files.
2026-08-31 05:22:53 +02:00
Kgothatso Ngako
49f3eba8f5 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