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.
This commit is contained in:
Kgothatso Ngako
2026-08-31 05:33:42 +02:00
parent 2a0e14d076
commit 5409aae813
3 changed files with 418 additions and 4 deletions

View File

@@ -32,12 +32,13 @@ extern "C" {
* not exceed SECP256K1_CHILLDKG_MAX_PARTICIPANTS. The message flow is:
* 1. Every participant runs secp256k1_chilldkg_participant_step1 and sends
* the resulting pmsg1 to the coordinator.
* 2. The coordinator aggregates the pmsg1s into a single cmsg1 broadcast
* to all participants (coordinator API is not available yet).
* 2. The coordinator runs secp256k1_chilldkg_coordinator_step1 on all
* pmsg1s and broadcasts the resulting cmsg1 to all participants.
* 3. Every participant runs secp256k1_chilldkg_participant_step2 and sends
* the resulting signature (pmsg2) to the coordinator.
* 4. The coordinator collects the n signatures into a certificate (cmsg2)
* broadcast to all participants.
* 4. The coordinator runs secp256k1_chilldkg_coordinator_finalize on all
* pmsg2s and broadcasts the resulting certificate (cmsg2) to all
* participants.
* 5. Every participant runs secp256k1_chilldkg_participant_finalize to
* obtain the DKG output and the recovery data.
*
@@ -111,6 +112,19 @@ typedef struct secp256k1_chilldkg_participant_state2 {
unsigned char data[12 + 4 + 131 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS + 32 + 33 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
} secp256k1_chilldkg_participant_state2;
/** Opaque data structure that holds the coordinator's session state after
* secp256k1_chilldkg_coordinator_step1, to be passed to
* secp256k1_chilldkg_coordinator_finalize (it must not be reused).
*
* This structure contains no secret key material; it can be copied freely
* (e.g., to persist it between the two coordinator steps).
*
* Guaranteed to be 21041 bytes in size.
*/
typedef struct secp256k1_chilldkg_coordinator_state {
unsigned char data[12 + 4 + 131 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS + 33 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
} secp256k1_chilldkg_coordinator_state;
/** Compute the participant's host public key from the host secret key.
*
* The host public key is the long-term cryptographic identity of the
@@ -307,6 +321,85 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_ch
const unsigned char *cmsg2
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
/** Perform the coordinator's first step of a ChillDKG session.
*
* Parses all n participant messages and aggregates them into the message to
* broadcast to all participants. The proofs of possession contained in the
* pmsg1s are NOT verified here; the participants verify them in step 2 (this
* mirrors the reference implementation).
*
* Returns: SECP256K1_CHILLDKG_OK on success,
* SECP256K1_CHILLDKG_INVALID_INPUT on invalid session parameters,
* or SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with fault_index set to
* the sender) if a participant message is malformed (invalid
* commitment encoding, or an encrypted share that overflows the
* group order). On failure, cmsg1 and the state are set to zero.
* Args: ctx: pointer to a context object
* Out: state: pointer to a coordinator_state object to be passed
* to secp256k1_chilldkg_coordinator_finalize (must not
* be reused)
* cmsg1: pointer to a 162*n + 33*(t-1) byte array (see
* secp256k1_chilldkg_coordinator_msg1_len) to store
* the message to be broadcast to all participants
* fault_index: pointer to a uint32 that receives the identifier of
* the faulty participant where applicable, and
* UINT32_MAX otherwise
* In: pmsgs1: array of n pointers to the participants' first
* messages (33*t + 32*n + 97 bytes each)
* hostpubkeys33: pointer to an array of n host public keys (33 bytes
* each); must be identical (in content and order) to
* the arrays used by the participants
* n_participants: total number of participants n
* threshold: threshold t
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_coordinator_step1(
const secp256k1_context *ctx,
secp256k1_chilldkg_coordinator_state *state,
unsigned char *cmsg1,
uint32_t *fault_index,
const unsigned char *const *pmsgs1,
const unsigned char *hostpubkeys33,
size_t n_participants,
uint32_t threshold
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
/** Perform the coordinator's final step of a ChillDKG session.
*
* Collects the n CertEq signatures into the certificate and verifies all of
* them. If this function returns SECP256K1_CHILLDKG_OK, the coordinator
* deems the DKG session successful.
*
* Returns: SECP256K1_CHILLDKG_OK on success, or
* SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with fault_index set to
* the signer) if a CertEq signature is invalid. On failure, all
* outputs are set to zero.
* Args: ctx: pointer to a context object
* Out: cmsg2: pointer to a 64*n byte array to store the
* certificate, to be broadcast to all participants
* thresh_pk33: pointer to a 33-byte array to store the threshold
* public key (compressed serialization)
* pubshares33: pointer to an array of n 33-byte elements to store
* the public shares of all participants
* recovery: pointer to a 4 + 33*t + 162*n byte array (see
* secp256k1_chilldkg_recovery_data_len) to store the
* recovery data
* fault_index: pointer to a uint32 (see above)
* In: state: pointer to the coordinator_state object output by
* secp256k1_chilldkg_coordinator_step1
* pmsgs2: array of n pointers to the participants' second
* messages (64 bytes each)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_coordinator_finalize(
const secp256k1_context *ctx,
unsigned char *cmsg2,
unsigned char *thresh_pk33,
unsigned char *pubshares33,
unsigned char *recovery,
uint32_t *fault_index,
const secp256k1_chilldkg_coordinator_state *state,
const unsigned char *const *pmsgs2
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
#ifdef __cplusplus
}
#endif