From 580e4f65025c0c0970a3b5f9bb278ce01ec0f0bc Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Mon, 31 Aug 2026 04:47:38 +0200 Subject: [PATCH] chilldkg: Phase 2 - SimplPedPop and EncPedPop layers Port the two sub-protocol layers of the ChillDKG reference implementation (bip-frost-dkg v0.3.0-dev, upstream commit a91896883f85b159415ecf298d5e844879af112d) to C, keeping the same function decomposition as simplpedpop.py / encpedpop.py so the code stays diffable against the reference. simplpedpop.h / simplpedpop_impl.h (mirrors simplpedpop.py): - simplpedpop_participant_step1: VSS coefficient generation and commitment, per-participant shares, proof of possession. The PoP is a BIP-340 signature with custom tag prefix "BIP DKG/pop message" over u32be(participant_id), signed with the constant coefficient f(0), using TH("BIP DKG/simplpedpop aux", simpl_seed) as aux_rand. - simplpedpop_coordinator_step / assemble_sum_coms: echo per-dealer constant-term commitments, sum non-constant-term commitments, collect PoPs (not verified by the coordinator, as in the reference). - simplpedpop_participant_step2: own-commitment echo check, per-dealer infinity rejection and PoP verification against the x-only coms_to_secrets[i], TapTweak applied before share verification (secshare vs pubshare check on tweaked values), eq_input = u32be(t) || sum_coms committing to the UNTWEAKED summed commitment. encpedpop.h / encpedpop_impl.h (mirrors encpedpop.py): - simpl_seed / aux / secnonce derivations from (hostseckey, random, enc_context) via "BIP DKG/encpedpop seed", "BIP DKG/simplpedpop aux" and "BIP DKG/encpedpop secnonce"; pubnonce = pubkey_gen_plain(secnonce). - encaps_multi / encrypt_multi: per-recipient pad context u32be(i) || enc_context, self-pad at the own index (no ECDH), libsecp256k1-style ECDH pad otherwise with sender-first ordering; encryption is additive mod n so the coordinator can sum encrypted shares per recipient. - decaps_multi / decrypt_sum: receiver-side pads, invalid or infinity pubnonce maps to FAULTY_PARTICIPANT_OR_COORDINATOR(sender). - coordinator_step: checked scalar parse of encrypted shares (overflow blames the sender), per-recipient summation. - participant_step2: pubnonce echo check (mismatch -> FAULTY_COORDINATOR), decrypt, delegate to simplpedpop step2, eq_input extended with enckeys || pubnonces. util.h: add SECP256K1_CHILLDKG_MAX_PARTICIPANTS (128, matching frost) and the internal fault enum mirroring the reference's exception taxonomy (FAULTY_COORDINATOR, FAULTY_PARTICIPANT, FAULTY_PARTICIPANT_OR_COORDINATOR, UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR, INVALID_INPUT). The public blame-reporting enum arrives with the Phase 3 API. State structs are fixed-size (no malloc, cap 128 participants). All secret temporaries are cleared; negligible-probability secret- dependent failures are declassified before branching. Documented deviations where the reference crashes with non-protocol errors: infinity sum_coms[0] in invalid_taproot_commit (unreachable after PoP verification) and tweak-hash overflow (negligible) return UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR; a wrong cmsg length maps to FAULTY_COORDINATOR. Investigation procedures are deferred to Phase 5 per the plan. tests_impl.h: byte-exact n=3/t=2 happy-path vectors for both layers generated from the Python reference (pmsg/cmsg/eq_input/enc_secshares and all DKG outputs), plus PoP reject cases (tampered, wrong index, infinity commitment, wrong echo, tampered non-constant-term sum), coordinator blame (bad commitment, overflowing encrypted share), encaps/decaps pad symmetry for all sender/receiver pairs including self-pad, encrypt->sum->decrypt_sum roundtrip, tampered encrypted share, invalid pubnonce blame, and input-validation rejects. Verified: make check 3/3 suites pass (incl. noverify_tests running the module); CMake ctest 361/361; ./tests --target=chilldkg runs all 9 module tests green. --- src/modules/chilldkg/Makefile.am.include | 4 + src/modules/chilldkg/encpedpop.h | 126 +++++++ src/modules/chilldkg/encpedpop_impl.h | 378 +++++++++++++++++++ src/modules/chilldkg/main_impl.h | 2 + src/modules/chilldkg/simplpedpop.h | 117 ++++++ src/modules/chilldkg/simplpedpop_impl.h | 356 ++++++++++++++++++ src/modules/chilldkg/tests_impl.h | 447 +++++++++++++++++++++++ src/modules/chilldkg/util.h | 26 ++ 8 files changed, 1456 insertions(+) create mode 100644 src/modules/chilldkg/encpedpop.h create mode 100644 src/modules/chilldkg/encpedpop_impl.h create mode 100644 src/modules/chilldkg/simplpedpop.h create mode 100644 src/modules/chilldkg/simplpedpop_impl.h diff --git a/src/modules/chilldkg/Makefile.am.include b/src/modules/chilldkg/Makefile.am.include index 2eb5c770..66683950 100644 --- a/src/modules/chilldkg/Makefile.am.include +++ b/src/modules/chilldkg/Makefile.am.include @@ -4,4 +4,8 @@ noinst_HEADERS += src/modules/chilldkg/util.h noinst_HEADERS += src/modules/chilldkg/util_impl.h noinst_HEADERS += src/modules/chilldkg/vss.h noinst_HEADERS += src/modules/chilldkg/vss_impl.h +noinst_HEADERS += src/modules/chilldkg/simplpedpop.h +noinst_HEADERS += src/modules/chilldkg/simplpedpop_impl.h +noinst_HEADERS += src/modules/chilldkg/encpedpop.h +noinst_HEADERS += src/modules/chilldkg/encpedpop_impl.h noinst_HEADERS += src/modules/chilldkg/tests_impl.h diff --git a/src/modules/chilldkg/encpedpop.h b/src/modules/chilldkg/encpedpop.h new file mode 100644 index 00000000..271beb93 --- /dev/null +++ b/src/modules/chilldkg/encpedpop.h @@ -0,0 +1,126 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_CHILLDKG_ENCPEDPOP_H +#define SECP256K1_MODULE_CHILLDKG_ENCPEDPOP_H + +#include "../../../include/secp256k1.h" + +#include "util.h" +#include "simplpedpop.h" +#include "../../group.h" +#include "../../scalar.h" + +/* This file contains the internal EncPedPop sub-protocol of the ChillDKG + * module, mirroring chilldkg_ref/encpedpop.py of the bip-frost-dkg reference + * implementation. EncPedPop wraps SimplPedPop and encrypts the partial secret + * shares towards their recipients with an additively homomorphic "Hashed + * ElGamal" multi-recipient KEM, so that an untrusted coordinator can aggregate + * the encrypted shares per recipient. + * + * Messages are byte strings: + * pmsg (participant -> coordinator): simpl_pmsg (33*t + 64) || pubnonce (33) + * || enc_shares (32*n) + * cmsg (coordinator -> participants): simpl_cmsg (97*n + 33*(t-1)) || + * pubnonces (33*n) + * The EncPedPop contribution to the equality-check input appends to the + * SimplPedPop contribution: + * eq_input = ... || enckeys (33*n) || pubnonces (33*n) */ + +/* Serialize the encryption context (encpedpop.py serialize_enc_context): + * out = u32be(t) || enckeys[0] || ... || enckeys[n-1] + * enckeys33 is an array of n 33-byte compressed host public keys; out must + * hold 4 + 33*n bytes. */ +static void secp256k1_chilldkg_encpedpop_serialize_enc_context(unsigned char *out, uint32_t t, const unsigned char *enckeys33, size_t n); + +/* Compute the pads for encrypting to all n recipients (encpedpop.py + * encaps_multi): for recipient i, the context is u32be(i) || enc_context; the + * pad for our own index is the symmetric self pad (no ECDH), the others are + * ECDH pads between the one-time secnonce and the recipient's host public key + * (sender-side ordering in the pad hash). + * + * enc_context must be at most 4 + 33*SECP256K1_CHILLDKG_MAX_PARTICIPANTS + * bytes. Returns 1 on success and 0 if some host public key is invalid + * (caller input error; the session parameters must be validated beforehand). + * On failure, all pads are cleared. */ +static int secp256k1_chilldkg_encpedpop_encaps_multi(const secp256k1_context *ctx, secp256k1_scalar *pads, const secp256k1_scalar *secnonce, const unsigned char *pubnonce33, const unsigned char *deckey32, const unsigned char *enckeys33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, size_t n); + +/* Encrypt the n plaintext shares towards the n recipients (encpedpop.py + * encrypt_multi): ciphertexts[i] = plaintexts[i] + pad[i]. The encryption is + * additively homomorphic modulo the group order, so the coordinator can sum + * the ciphertexts per recipient. Returns 1 on success, 0 on invalid host + * public keys (see encaps_multi). */ +static int secp256k1_chilldkg_encpedpop_encrypt_multi(const secp256k1_context *ctx, secp256k1_scalar *ciphertexts, const secp256k1_scalar *secnonce, const unsigned char *pubnonce33, const unsigned char *deckey32, const unsigned char *enckeys33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, const secp256k1_scalar *plaintexts, size_t n); + +/* Compute the pads for decrypting the shares received from all n senders + * (encpedpop.py decaps_multi): the context is u32be(participant_id) || + * enc_context for every sender; the pad for our own index is the symmetric + * self pad, the others are ECDH pads between our deckey and the sender's + * pubnonce (receiver-side ordering in the pad hash). deckey32 is our host + * secret key and enckey33 our host public key. + * + * Returns SECP256K1_CHILLDKG_SUCCESS on success, + * SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR (with *fault_index) if + * some sender's pubnonce is invalid, and SECP256K1_CHILLDKG_INVALID_INPUT if + * deckey32 is invalid. On failure, all pads are cleared. */ +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_decaps_multi(const secp256k1_context *ctx, secp256k1_scalar *pads, uint32_t *fault_index, const unsigned char *deckey32, const unsigned char *enckey33, const unsigned char *pubnonces33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, size_t n); + +/* Decrypt the sum of the encrypted shares addressed to us (encpedpop.py + * decrypt_sum): out = sum_ciphertexts - sum of the n pads. Returns like + * decaps_multi. */ +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_decrypt_sum(const secp256k1_context *ctx, secp256k1_scalar *out, uint32_t *fault_index, const unsigned char *deckey32, const unsigned char *enckey33, const unsigned char *pubnonces33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, const secp256k1_scalar *sum_ciphertexts, size_t n); + +/* Participant state (encpedpop.py ParticipantState). The enckeys are kept + * because participant_step2 needs them to recompute the encryption context + * and the pads. */ +typedef struct { + secp256k1_chilldkg_simplpedpop_participant_state simpl_state; + unsigned char pubnonce33[33]; + unsigned char enckeys33[SECP256K1_CHILLDKG_MAX_PARTICIPANTS][33]; +} secp256k1_chilldkg_encpedpop_participant_state; + +/* Participant step 1 (encpedpop.py participant_step1). Derives + * simpl_seed = TH("BIP DKG/encpedpop seed", seed32 || random32 || enc_context) + * aux_rand = TH("BIP DKG/simplpedpop aux", simpl_seed) + * secnonce = TH("BIP DKG/encpedpop secnonce", simpl_seed) + * pubnonce = pubkey_gen_plain(secnonce) + * (in ChillDKG, both seed32 and deckey32 are the host secret key), runs + * SimplPedPop participant_step1 on simpl_seed, encrypts the shares, and + * outputs the participant message pmsg (33*t + 64 + 33 + 32*n bytes). + * + * Returns 1 on success and 0 on invalid input (t < 1, t > n, n exceeding + * SECP256K1_CHILLDKG_MAX_PARTICIPANTS, participant_id >= n, invalid host + * public keys) or on negligible-probability derivation failures. */ +static int secp256k1_chilldkg_encpedpop_participant_step1(const secp256k1_context *ctx, secp256k1_chilldkg_encpedpop_participant_state *state, unsigned char *pmsg, const unsigned char *seed32, const unsigned char *deckey32, const unsigned char *enckeys33, uint32_t t, uint32_t participant_id, const unsigned char *random32, size_t n); + +/* Participant step 2 (encpedpop.py participant_step2): verify that the + * coordinator echoed our pubnonce, decrypt our secshare from the summed + * encrypted secshare, and run SimplPedPop participant_step2. Outputs the DKG + * output and the EncPedPop eq_input (4 + 33*t + 33*n + 33*n bytes). + * + * cmsg must be exactly 97*n + 33*(t-1) + 33*n bytes. Returns + * SECP256K1_CHILLDKG_SUCCESS on success; otherwise a fault code (with + * *fault_index set when applicable): FAULTY_COORDINATOR if cmsg is malformed + * or the pubnonce echo is wrong, FAULTY_PARTICIPANT_OR_COORDINATOR(i) if + * sender i's pubnonce is invalid or their pop/commitment is bad, and + * UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR if the decrypted secshare does + * not match the pubshare. */ +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_participant_step2(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const secp256k1_chilldkg_encpedpop_participant_state *state, const unsigned char *deckey32, const unsigned char *cmsg, size_t cmsg_len, const secp256k1_scalar *enc_secshare); + +/* Coordinator step (encpedpop.py coordinator_step): parse the n participant + * messages (each 33*t + 64 + 33 + 32*n bytes), aggregate SimplPedPop, and sum + * the encrypted shares per recipient (exploiting the additive homomorphism). + * Outputs cmsg (97*n + 33*(t-1) + 33*n bytes), the coordinator's DKG output + * (no secshare), the EncPedPop eq_input (4 + 33*t + 33*n + 33*n bytes) and + * the n summed encrypted secshares. (In pure EncPedPop the coordinator would + * send enc_secshares[i] to participant i only; ChillDKG broadcasts them.) + * + * Returns SECP256K1_CHILLDKG_SUCCESS on success and + * SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with *fault_index) if a participant + * message is malformed (invalid commitment, or an encrypted share that + * overflows the group order). */ +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_coordinator_step(const secp256k1_context *ctx, unsigned char *cmsg, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, secp256k1_scalar *enc_secshares, uint32_t *fault_index, const unsigned char *const *pmsgs, uint32_t t, const unsigned char *enckeys33, size_t n); + +#endif diff --git a/src/modules/chilldkg/encpedpop_impl.h b/src/modules/chilldkg/encpedpop_impl.h new file mode 100644 index 00000000..4c243ccf --- /dev/null +++ b/src/modules/chilldkg/encpedpop_impl.h @@ -0,0 +1,378 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_CHILLDKG_ENCPEDPOP_IMPL_H +#define SECP256K1_MODULE_CHILLDKG_ENCPEDPOP_IMPL_H + +#include + +#include "../../../include/secp256k1.h" + +#include "util.h" +#include "vss.h" +#include "simplpedpop.h" +#include "encpedpop.h" +#include "../../ecmult.h" +#include "../../util.h" + +/* The buffer size for u32be(index) || enc_context. */ +#define SECP256K1_CHILLDKG_ENCPEDPOP_MAX_CONTEXT_LEN (8 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS) + +static void secp256k1_chilldkg_encpedpop_serialize_enc_context(unsigned char *out, uint32_t t, const unsigned char *enckeys33, size_t n) { + secp256k1_write_be32(out, t); + memcpy(out + 4, enckeys33, 33 * n); +} + +static int secp256k1_chilldkg_encpedpop_encaps_multi(const secp256k1_context *ctx, secp256k1_scalar *pads, const secp256k1_scalar *secnonce, const unsigned char *pubnonce33, const unsigned char *deckey32, const unsigned char *enckeys33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, size_t n) { + const secp256k1_hash_ctx *hash_ctx; + unsigned char context_[SECP256K1_CHILLDKG_ENCPEDPOP_MAX_CONTEXT_LEN]; + size_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + VERIFY_CHECK(enc_context_len + 4 <= sizeof(context_)); + hash_ctx = secp256k1_get_hash_context(ctx); + + /* This is effectively the "Hashed ElGamal" multi-recipient KEM described + * in Section 5 of "Multi-recipient encryption, revisited" by Alexandre + * Pinto, Bertram Poettering, Jacob C. N. Schuldt (AsiaCCS 2014). */ + for (i = 0; i < n; i++) { + secp256k1_write_be32(context_, (uint32_t)i); + memcpy(context_ + 4, enc_context, enc_context_len); + if (i == participant_id) { + /* We're encrypting to ourselves, so we use a symmetrically + * derived pad to save the ECDH computation. */ + secp256k1_chilldkg_encpedpop_self_pad(hash_ctx, &pads[i], deckey32, pubnonce33, context_, 4 + enc_context_len); + } else { + secp256k1_ge enckey; + if (!secp256k1_chilldkg_point_load(&enckey, enckeys33 + 33 * i) || secp256k1_ge_is_infinity(&enckey)) { + /* Invalid host public key: caller input error (the session + * parameters must be validated beforehand). */ + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&pads[i]); + } + return 0; + } + secp256k1_chilldkg_encpedpop_ecdh(ctx, &pads[i], secnonce, &enckey, pubnonce33, enckeys33 + 33 * i, context_, 4 + enc_context_len, 1); + } + } + return 1; +} + +static int secp256k1_chilldkg_encpedpop_encrypt_multi(const secp256k1_context *ctx, secp256k1_scalar *ciphertexts, const secp256k1_scalar *secnonce, const unsigned char *pubnonce33, const unsigned char *deckey32, const unsigned char *enckeys33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, const secp256k1_scalar *plaintexts, size_t n) { + secp256k1_scalar pads[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + size_t i; + int ret; + + VERIFY_CHECK(n <= SECP256K1_CHILLDKG_MAX_PARTICIPANTS); + ret = secp256k1_chilldkg_encpedpop_encaps_multi(ctx, pads, secnonce, pubnonce33, deckey32, enckeys33, enc_context, enc_context_len, participant_id, n); + if (ret) { + /* The encryption is additively homomorphic modulo the group order. */ + for (i = 0; i < n; i++) { + secp256k1_scalar_add(&ciphertexts[i], &plaintexts[i], &pads[i]); + } + } + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&pads[i]); + } + return ret; +} + +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_decaps_multi(const secp256k1_context *ctx, secp256k1_scalar *pads, uint32_t *fault_index, const unsigned char *deckey32, const unsigned char *enckey33, const unsigned char *pubnonces33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, size_t n) { + const secp256k1_hash_ctx *hash_ctx; + unsigned char context_[SECP256K1_CHILLDKG_ENCPEDPOP_MAX_CONTEXT_LEN]; + secp256k1_scalar deckey; + size_t i; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + VERIFY_CHECK(enc_context_len + 4 <= sizeof(context_)); + VERIFY_CHECK(participant_id < n); + hash_ctx = secp256k1_get_hash_context(ctx); + + /* The receiver index in the context is our own participant id for every + * sender. */ + secp256k1_write_be32(context_, participant_id); + memcpy(context_ + 4, enc_context, enc_context_len); + + secp256k1_scalar_set_b32(&deckey, deckey32, &overflow); + overflow |= secp256k1_scalar_is_zero(&deckey); + /* deckey32 is the caller's own host secret key; a failure is an input + * error, not a protocol fault. */ + secp256k1_declassify(ctx, &overflow, sizeof(overflow)); + if (overflow) { + return SECP256K1_CHILLDKG_INVALID_INPUT; + } + + for (i = 0; i < n; i++) { + if (i == participant_id) { + /* The pad for the share from ourselves is derived symmetrically + * (the sender side of the pad does the same). */ + secp256k1_chilldkg_encpedpop_self_pad(hash_ctx, &pads[i], deckey32, pubnonces33 + 33 * i, context_, 4 + enc_context_len); + } else { + secp256k1_ge pubnonce; + if (!secp256k1_chilldkg_point_load(&pubnonce, pubnonces33 + 33 * i) || secp256k1_ge_is_infinity(&pubnonce)) { + /* Since deckey and enckey are well-formed, the error must + * have been caused by an invalid public nonce. */ + secp256k1_scalar_clear(&deckey); + *fault_index = (uint32_t)i; + return SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR; + } + secp256k1_chilldkg_encpedpop_ecdh(ctx, &pads[i], &deckey, &pubnonce, enckey33, pubnonces33 + 33 * i, context_, 4 + enc_context_len, 0); + } + } + secp256k1_scalar_clear(&deckey); + return SECP256K1_CHILLDKG_SUCCESS; +} + +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_decrypt_sum(const secp256k1_context *ctx, secp256k1_scalar *out, uint32_t *fault_index, const unsigned char *deckey32, const unsigned char *enckey33, const unsigned char *pubnonces33, const unsigned char *enc_context, size_t enc_context_len, uint32_t participant_id, const secp256k1_scalar *sum_ciphertexts, size_t n) { + secp256k1_scalar pads[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_chilldkg_fault fault; + size_t i; + + VERIFY_CHECK(n <= SECP256K1_CHILLDKG_MAX_PARTICIPANTS); + fault = secp256k1_chilldkg_encpedpop_decaps_multi(ctx, pads, fault_index, deckey32, enckey33, pubnonces33, enc_context, enc_context_len, participant_id, n); + if (fault == SECP256K1_CHILLDKG_SUCCESS) { + secp256k1_scalar_set_int(out, 0); + for (i = 0; i < n; i++) { + secp256k1_scalar_add(out, out, &pads[i]); + } + secp256k1_scalar_negate(out, out); + secp256k1_scalar_add(out, out, sum_ciphertexts); + } + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&pads[i]); + } + return fault; +} + +static int secp256k1_chilldkg_encpedpop_participant_step1(const secp256k1_context *ctx, secp256k1_chilldkg_encpedpop_participant_state *state, unsigned char *pmsg, const unsigned char *seed32, const unsigned char *deckey32, const unsigned char *enckeys33, uint32_t t, uint32_t participant_id, const unsigned char *random32, size_t n) { + const secp256k1_hash_ctx *hash_ctx; + unsigned char enc_context[4 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + unsigned char simpl_seed[32]; + unsigned char simpl_aux_rand[32]; + unsigned char secnonce32[32]; + unsigned char pubnonce33[33]; + secp256k1_scalar secnonce; + secp256k1_scalar shares[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_scalar enc_shares[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_sha256 sha; + uint32_t i; + int overflow; + int ret = 0; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(state != NULL); + ARG_CHECK(pmsg != NULL); + ARG_CHECK(seed32 != NULL); + ARG_CHECK(deckey32 != NULL); + ARG_CHECK(enckeys33 != NULL); + ARG_CHECK(random32 != NULL); + hash_ctx = secp256k1_get_hash_context(ctx); + + if (t < 1 || n < 1 || t > n || participant_id >= n || n > SECP256K1_CHILLDKG_MAX_PARTICIPANTS) { + return 0; + } + + /* Derive an encryption nonce and a seed for SimplPedPop. + * + * SimplPedPop will use its seed to derive the secret shares, which we + * will encrypt using the encryption nonce. That means that all entropy + * used in the derivation of simpl_seed should also be in the derivation + * of the pubnonce, to ensure that we never encrypt different secret + * shares with the same encryption pads. The foolproof way to achieve this + * is to simply derive the nonce from simpl_seed. */ + secp256k1_chilldkg_encpedpop_serialize_enc_context(enc_context, t, enckeys33, n); + secp256k1_chilldkg_sha256_tagged_encpedpop_seed(hash_ctx, &sha); + secp256k1_sha256_write(hash_ctx, &sha, seed32, 32); + secp256k1_sha256_write(hash_ctx, &sha, random32, 32); + secp256k1_sha256_write(hash_ctx, &sha, enc_context, 4 + 33 * n); + secp256k1_sha256_finalize(hash_ctx, &sha, simpl_seed); + + secp256k1_chilldkg_sha256_tagged_simplpedpop_aux(hash_ctx, &sha); + secp256k1_sha256_write(hash_ctx, &sha, simpl_seed, 32); + secp256k1_sha256_finalize(hash_ctx, &sha, simpl_aux_rand); + + secp256k1_chilldkg_sha256_tagged_encpedpop_secnonce(hash_ctx, &sha); + secp256k1_sha256_write(hash_ctx, &sha, simpl_seed, 32); + secp256k1_sha256_finalize(hash_ctx, &sha, secnonce32); + secp256k1_sha256_clear(&sha); + + /* pubnonce = pubkey_gen_plain(secnonce); fails only if secnonce is not in + * range 1..n-1 (negligible probability). */ + secp256k1_scalar_set_b32(&secnonce, secnonce32, &overflow); + overflow |= secp256k1_scalar_is_zero(&secnonce); + secp256k1_declassify(ctx, &overflow, sizeof(overflow)); + if (!overflow) { + secp256k1_gej pubnoncej; + secp256k1_ge pubnonce_ge; + secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &pubnoncej, &secnonce); + secp256k1_ge_set_gej(&pubnonce_ge, &pubnoncej); + secp256k1_chilldkg_point_save(pubnonce33, &pubnonce_ge); + secp256k1_gej_clear(&pubnoncej); + + if (secp256k1_chilldkg_simplpedpop_participant_step1(ctx, &state->simpl_state, pmsg, shares, simpl_seed, t, (uint32_t)n, participant_id, simpl_aux_rand)) { + if (secp256k1_chilldkg_encpedpop_encrypt_multi(ctx, enc_shares, &secnonce, pubnonce33, deckey32, enckeys33, enc_context, 4 + 33 * n, participant_id, shares, n)) { + /* pmsg = ParticipantMsg(simpl_pmsg, pubnonce, enc_shares).to_bytes() */ + memcpy(pmsg + 33 * t + 64, pubnonce33, 33); + for (i = 0; i < n; i++) { + secp256k1_scalar_get_b32(pmsg + 33 * t + 97 + 32 * i, &enc_shares[i]); + } + memcpy(state->pubnonce33, pubnonce33, 33); + memcpy(&state->enckeys33[0][0], enckeys33, 33 * n); + ret = 1; + } + } + } + + secp256k1_scalar_clear(&secnonce); + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&shares[i]); + secp256k1_scalar_clear(&enc_shares[i]); + } + secp256k1_memclear_explicit(simpl_seed, sizeof(simpl_seed)); + secp256k1_memclear_explicit(simpl_aux_rand, sizeof(simpl_aux_rand)); + secp256k1_memclear_explicit(secnonce32, sizeof(secnonce32)); + return ret; +} + +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_participant_step2(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const secp256k1_chilldkg_encpedpop_participant_state *state, const unsigned char *deckey32, const unsigned char *cmsg, size_t cmsg_len, const secp256k1_scalar *enc_secshare) { + const uint32_t t = state->simpl_state.t; + const uint32_t n = state->simpl_state.n; + const uint32_t participant_id = state->simpl_state.participant_id; + const size_t simpl_cmsg_len = 97 * (size_t)n + 33 * (size_t)(t - 1); + const unsigned char *pubnonces; + unsigned char enc_context[4 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_scalar pads[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_scalar padsum; + secp256k1_scalar secshare; + secp256k1_chilldkg_fault fault; + uint32_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(dkg_output != NULL); + ARG_CHECK(eq_input != NULL); + ARG_CHECK(state != NULL); + ARG_CHECK(deckey32 != NULL); + ARG_CHECK(cmsg != NULL); + ARG_CHECK(enc_secshare != NULL); + + /* CoordinatorMsg.from_bytes; the simpl_cmsg prefix is parsed (again) by + * the SimplPedPop participant_step2 below. */ + if (cmsg_len != simpl_cmsg_len + 33 * (size_t)n) { + return SECP256K1_CHILLDKG_FAULTY_COORDINATOR; + } + pubnonces = cmsg + simpl_cmsg_len; + + /* The coordinator must echo our own pubnonce. */ + if (secp256k1_memcmp_var(pubnonces + 33 * participant_id, state->pubnonce33, 33) != 0) { + return SECP256K1_CHILLDKG_FAULTY_COORDINATOR; + } + + /* secshare = enc_secshare - sum of the pads (decrypt_sum). */ + secp256k1_chilldkg_encpedpop_serialize_enc_context(enc_context, t, &state->enckeys33[0][0], n); + fault = secp256k1_chilldkg_encpedpop_decaps_multi(ctx, pads, fault_index, deckey32, state->enckeys33[participant_id], pubnonces, enc_context, 4 + 33 * (size_t)n, participant_id, n); + if (fault != SECP256K1_CHILLDKG_SUCCESS) { + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&pads[i]); + } + return fault; + } + secp256k1_scalar_set_int(&padsum, 0); + for (i = 0; i < n; i++) { + secp256k1_scalar_add(&padsum, &padsum, &pads[i]); + } + secp256k1_scalar_negate(&secshare, &padsum); + secp256k1_scalar_add(&secshare, &secshare, enc_secshare); + + fault = secp256k1_chilldkg_simplpedpop_participant_step2(ctx, dkg_output, eq_input, fault_index, &state->simpl_state, cmsg, simpl_cmsg_len, &secshare); + + secp256k1_scalar_clear(&secshare); + secp256k1_scalar_clear(&padsum); + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&pads[i]); + } + if (fault != SECP256K1_CHILLDKG_SUCCESS) { + return fault; + } + + /* eq_input += enckeys || pubnonces */ + memcpy(eq_input + 4 + 33 * t, &state->enckeys33[0][0], 33 * (size_t)n); + memcpy(eq_input + 4 + 33 * t + 33 * (size_t)n, pubnonces, 33 * (size_t)n); + return SECP256K1_CHILLDKG_SUCCESS; +} + +static secp256k1_chilldkg_fault secp256k1_chilldkg_encpedpop_coordinator_step(const secp256k1_context *ctx, unsigned char *cmsg, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, secp256k1_scalar *enc_secshares, uint32_t *fault_index, const unsigned char *const *pmsgs, uint32_t t, const unsigned char *enckeys33, size_t n) { + const size_t simpl_pmsg_len = 33 * (size_t)t + 64; + const size_t simpl_cmsg_len = 97 * n + 33 * (size_t)(t - 1); + secp256k1_ge scratch; + secp256k1_scalar share; + secp256k1_chilldkg_fault fault; + size_t i; + size_t j; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(cmsg != NULL); + ARG_CHECK(dkg_output != NULL); + ARG_CHECK(eq_input != NULL); + ARG_CHECK(enc_secshares != NULL); + ARG_CHECK(pmsgs != NULL); + ARG_CHECK(enckeys33 != NULL); + + if (t < 1 || n < 1 || t > n || n > SECP256K1_CHILLDKG_MAX_PARTICIPANTS) { + return SECP256K1_CHILLDKG_INVALID_INPUT; + } + + /* Parse the participant messages (ParticipantMsg.from_bytes). A malformed + * message blames its sender. The pubnonces are not parsed (they are plain + * byte strings in the reference); the participants parse them when + * decrypting. */ + for (i = 0; i < n; i++) { + secp256k1_scalar_set_int(&enc_secshares[i], 0); + } + for (i = 0; i < n; i++) { + const unsigned char *pmsg = pmsgs[i]; + /* Validate the SimplPedPop commitment (the actual aggregation happens + * in the SimplPedPop coordinator step below). */ + for (j = 0; j < t; j++) { + if (!secp256k1_chilldkg_point_load(&scratch, pmsg + 33 * j)) { + *fault_index = (uint32_t)i; + return SECP256K1_CHILLDKG_FAULTY_PARTICIPANT; + } + } + /* Collect the pubnonces in the coordinator message. */ + memcpy(cmsg + simpl_cmsg_len + 33 * i, pmsg + simpl_pmsg_len, 33); + /* Sum the encrypted shares per recipient (the encryption is + * additively homomorphic). Wire scalars are parsed checked; an + * overflow blames the sender. */ + for (j = 0; j < n; j++) { + secp256k1_scalar_set_b32(&share, pmsg + simpl_pmsg_len + 33 + 32 * j, &overflow); + if (overflow) { + secp256k1_scalar_clear(&share); + *fault_index = (uint32_t)i; + return SECP256K1_CHILLDKG_FAULTY_PARTICIPANT; + } + secp256k1_scalar_add(&enc_secshares[j], &enc_secshares[j], &share); + } + } + secp256k1_scalar_clear(&share); + + fault = secp256k1_chilldkg_simplpedpop_coordinator_step(ctx, cmsg, dkg_output, eq_input, fault_index, pmsgs, t, (uint32_t)n); + if (fault != SECP256K1_CHILLDKG_SUCCESS) { + return fault; + } + + /* eq_input += enckeys || pubnonces */ + memcpy(eq_input + 4 + 33 * t, enckeys33, 33 * n); + memcpy(eq_input + 4 + 33 * t + 33 * n, cmsg + simpl_cmsg_len, 33 * n); + return SECP256K1_CHILLDKG_SUCCESS; +} + +#endif diff --git a/src/modules/chilldkg/main_impl.h b/src/modules/chilldkg/main_impl.h index ec5eba29..013c5acf 100644 --- a/src/modules/chilldkg/main_impl.h +++ b/src/modules/chilldkg/main_impl.h @@ -10,5 +10,7 @@ #include "util_impl.h" #include "vss_impl.h" +#include "simplpedpop_impl.h" +#include "encpedpop_impl.h" #endif diff --git a/src/modules/chilldkg/simplpedpop.h b/src/modules/chilldkg/simplpedpop.h new file mode 100644 index 00000000..6afad71d --- /dev/null +++ b/src/modules/chilldkg/simplpedpop.h @@ -0,0 +1,117 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_CHILLDKG_SIMPLPEDPOP_H +#define SECP256K1_MODULE_CHILLDKG_SIMPLPEDPOP_H + +#include "../../../include/secp256k1.h" + +#include "util.h" +#include "vss.h" +#include "../../group.h" +#include "../../scalar.h" + +/* This file contains the internal SimplPedPop sub-protocol of the ChillDKG + * module, mirroring chilldkg_ref/simplpedpop.py of the bip-frost-dkg + * reference implementation. Function decomposition and naming follow the + * reference so that the two stay diffable. + * + * SimplPedPop is a non-interactive Pedersen VSS with a proof of possession + * (pop) per participant. Messages are byte strings: + * pmsg (participant -> coordinator): com (33*t) || pop (64) + * cmsg (coordinator -> participants): coms_to_secrets (33*n) || + * sum_coms_to_nonconst_terms (33*(t-1)) || pops (64*n) + * The SimplPedPop contribution to the equality-check input is + * eq_input = u32be(t) || sum_coms (33*t) [sum_coms is UNTWEAKED] + * and is extended by the EncPedPop layer. */ + +/* The message over which a proof of possession is computed is + * u32be(participant_id) (simplpedpop.py pop_msg); the tag prefix is + * "BIP DKG/pop message" (POP_MSG_TAG). */ + +/* Compute a proof of possession (simplpedpop.py pop_prove): a BIP 340 + * signature with tag prefix "BIP DKG/pop message" over u32be(participant_id), + * created with the given 32-byte secret key and aux_rand. Returns 1 on success + * and 0 if the secret key is invalid (negligible probability for VSS + * coefficients). */ +static int secp256k1_chilldkg_simplpedpop_pop_prove(const secp256k1_context *ctx, unsigned char *pop64, const unsigned char *seckey32, uint32_t participant_id, const unsigned char *aux_rand32); + +/* Verify a proof of possession (simplpedpop.py pop_verify) against the x-only + * encoding of the commitment to the secret of the claimed participant. + * Returns 1 if the pop is valid and 0 otherwise. */ +static int secp256k1_chilldkg_simplpedpop_pop_verify(const secp256k1_context *ctx, const unsigned char *pop64, const unsigned char *pubkey32, uint32_t participant_id); + +/* Participant state (simplpedpop.py ParticipantState). */ +typedef struct { + uint32_t t; + uint32_t n; + uint32_t participant_id; + secp256k1_ge com_to_secret; +} secp256k1_chilldkg_simplpedpop_participant_state; + +/* DKG output (simplpedpop.py DKGOutput). secshare32 and the pubshares are the + * TWEAKED values (after invalid_taproot_commit). The coordinator has no + * secshare; in its output secshare32 is zero (None in the reference). Only + * the first n entries of pubshares33 are filled. */ +typedef struct { + unsigned char secshare32[32]; + unsigned char thresh_pk33[33]; + unsigned char pubshares33[SECP256K1_CHILLDKG_MAX_PARTICIPANTS][33]; +} secp256k1_chilldkg_simplpedpop_dkg_output; + +/* Participant step 1 (simplpedpop.py participant_step1): derive the VSS + * coefficients from seed32, compute the commitment and the shares for all n + * participants, and prove possession of the secret (the constant coefficient + * f(0)) with aux_rand32 as aux_rand. + * + * Outputs the state, the participant message msg (33*t + 64 bytes) and the n + * partial secret shares (the caller must clear them after use). Returns 1 on + * success and 0 on invalid input (t < 1, t > n, n or t exceeding + * SECP256K1_CHILLDKG_MAX_PARTICIPANTS, participant_id >= n) or if the + * seed-derived coefficients overflow the group order or the secret is zero + * (negligible probability). */ +static int secp256k1_chilldkg_simplpedpop_participant_step1(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_participant_state *state, unsigned char *msg, secp256k1_scalar *partial_secshares, const unsigned char *seed32, uint32_t t, uint32_t n, uint32_t participant_id, const unsigned char *aux_rand32); + +/* Sum the partial secshares received from all participants + * (simplpedpop.py participant_step2_prepare_secshare). */ +static void secp256k1_chilldkg_simplpedpop_participant_step2_prepare_secshare(secp256k1_scalar *out, const secp256k1_scalar *partial_secshares, size_t n); + +/* Participant step 2 (simplpedpop.py participant_step2): verify the + * coordinator message, i.e., check that our own commitment to the secret was + * echoed correctly, verify the pops of all other participants against the + * x-only encodings of their commitments to the secrets, and check the tweaked + * secshare against the tweaked pubshare (the tweak is applied before the share + * verification). Outputs the DKG output and the SimplPedPop contribution to + * eq_input (4 + 33*t bytes). + * + * cmsg must be exactly 97*n + 33*(t-1) bytes. Returns SECP256K1_CHILLDKG_SUCCESS + * on success; otherwise a fault code (with *fault_index set when applicable): + * - FAULTY_COORDINATOR if cmsg is malformed or the echo of our own + * commitment to the secret is wrong; + * - FAULTY_PARTICIPANT_OR_COORDINATOR(i) if participant i sent the point at + * infinity as commitment to their secret or an invalid pop; + * - UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR if the tweaked secshare does + * not match the tweaked pubshare (the investigation procedure of a later + * phase can attribute the fault). */ +static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_participant_step2(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const secp256k1_chilldkg_simplpedpop_participant_state *state, const unsigned char *cmsg, size_t cmsg_len, const secp256k1_scalar *secshare); + +/* Assemble the summed VSS commitment (simplpedpop.py assemble_sum_coms): + * sum_coms[0] = sum of the n coms_to_secrets, sum_coms[j] = + * sum_coms_to_nonconst_terms[j-1] for j > 0. All inputs are public. */ +static void secp256k1_chilldkg_simplpedpop_assemble_sum_coms(secp256k1_ge *sum_coms, const secp256k1_ge *coms_to_secrets, const secp256k1_ge *sum_coms_to_nonconst_terms, size_t n, size_t t); + +/* Coordinator step (simplpedpop.py coordinator_step): parse the n participant + * messages (each 33*t + 64 bytes), echo the per-participant commitments to + * the secrets, sum the commitments to the non-constant terms, and compute the + * tweaked DKG output (no secshare) and the SimplPedPop contribution to + * eq_input (4 + 33*t bytes). The pops are NOT verified here (participants + * verify them in step 2). + * + * Outputs cmsg (97*n + 33*(t-1) bytes). Returns SECP256K1_CHILLDKG_SUCCESS on + * success and SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with *fault_index) if a + * participant message is malformed. */ +static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_coordinator_step(const secp256k1_context *ctx, unsigned char *cmsg, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const unsigned char *const *pmsgs, uint32_t t, uint32_t n); + +#endif diff --git a/src/modules/chilldkg/simplpedpop_impl.h b/src/modules/chilldkg/simplpedpop_impl.h new file mode 100644 index 00000000..2e9936c0 --- /dev/null +++ b/src/modules/chilldkg/simplpedpop_impl.h @@ -0,0 +1,356 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_CHILLDKG_SIMPLPEDPOP_IMPL_H +#define SECP256K1_MODULE_CHILLDKG_SIMPLPEDPOP_IMPL_H + +#include + +#include "../../../include/secp256k1.h" + +#include "util.h" +#include "vss.h" +#include "simplpedpop.h" +#include "../../ecmult.h" +#include "../../util.h" + +/* The tag prefix of the proof-of-possession signatures (POP_MSG_TAG in + * simplpedpop.py). */ +#define SECP256K1_CHILLDKG_POP_MSG_TAG "BIP DKG/pop message" + +static int secp256k1_chilldkg_simplpedpop_pop_prove(const secp256k1_context *ctx, unsigned char *pop64, const unsigned char *seckey32, uint32_t participant_id, const unsigned char *aux_rand32) { + /* pop_msg: the message is u32be(participant_id). */ + unsigned char msg[4]; + int ret; + + secp256k1_write_be32(msg, participant_id); + ret = secp256k1_chilldkg_schnorrsig_sign(ctx, pop64, msg, sizeof(msg), seckey32, aux_rand32, SECP256K1_CHILLDKG_POP_MSG_TAG); + secp256k1_memclear_explicit(msg, sizeof(msg)); + return ret; +} + +static int secp256k1_chilldkg_simplpedpop_pop_verify(const secp256k1_context *ctx, const unsigned char *pop64, const unsigned char *pubkey32, uint32_t participant_id) { + unsigned char msg[4]; + int ret; + + secp256k1_write_be32(msg, participant_id); + ret = secp256k1_chilldkg_schnorrsig_verify(ctx, pop64, msg, sizeof(msg), pubkey32, SECP256K1_CHILLDKG_POP_MSG_TAG); + secp256k1_memclear_explicit(msg, sizeof(msg)); + return ret; +} + +static int secp256k1_chilldkg_simplpedpop_participant_step1(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_participant_state *state, unsigned char *msg, secp256k1_scalar *partial_secshares, const unsigned char *seed32, uint32_t t, uint32_t n, uint32_t participant_id, const unsigned char *aux_rand32) { + const secp256k1_hash_ctx *hash_ctx; + secp256k1_scalar coeffs[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge coms[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + unsigned char seckey32[32]; + unsigned char pop[64]; + uint32_t i; + size_t j; + int coeffs_ok; + int ret = 0; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(state != NULL); + ARG_CHECK(msg != NULL); + ARG_CHECK(partial_secshares != NULL); + ARG_CHECK(seed32 != NULL); + ARG_CHECK(aux_rand32 != NULL); + hash_ctx = secp256k1_get_hash_context(ctx); + + /* Input validation, mirroring the ValueError/IndexError checks of the + * reference. The reference would fail on t == 0 when accessing the + * constant coefficient; reject it here instead. */ + if (t < 1 || n < 1 || t > n || participant_id >= n || n > SECP256K1_CHILLDKG_MAX_PARTICIPANTS) { + return 0; + } + + /* VSS.generate(seed, t). */ + coeffs_ok = secp256k1_chilldkg_vss_gen_coeffs(hash_ctx, coeffs, t, seed32); + /* The failure depends on the secret seed but occurs with negligible + * probability only (the reference raises OverflowError in this case). */ + secp256k1_declassify(ctx, &coeffs_ok, sizeof(coeffs_ok)); + if (coeffs_ok) { + /* vss.secshares(n): the partial secret shares from us to all + * participants. */ + for (i = 0; i < n; i++) { + secp256k1_chilldkg_vss_secshare_for(&partial_secshares[i], coeffs, t, i); + } + /* com = vss.commit(). */ + secp256k1_chilldkg_vss_commit(ctx, coms, coeffs, t); + /* pop = pop_prove(vss.secret(), participant_id, aux_rand); the secret + * is the constant coefficient f(0) = coeffs[0]. Fails only if the + * secret is zero (negligible probability). */ + secp256k1_scalar_get_b32(seckey32, &coeffs[0]); + if (secp256k1_chilldkg_simplpedpop_pop_prove(ctx, pop, seckey32, participant_id, aux_rand32)) { + /* msg = ParticipantMsg(com, pop).to_bytes() */ + for (j = 0; j < t; j++) { + secp256k1_chilldkg_point_save(msg + 33 * j, &coms[j]); + } + memcpy(msg + 33 * t, pop, 64); + state->t = t; + state->n = n; + state->participant_id = participant_id; + /* com_to_secret = com.commitment_to_secret() */ + state->com_to_secret = coms[0]; + ret = 1; + } + } + + for (j = 0; j < t; j++) { + secp256k1_scalar_clear(&coeffs[j]); + } + secp256k1_memclear_explicit(seckey32, sizeof(seckey32)); + secp256k1_memclear_explicit(pop, sizeof(pop)); + return ret; +} + +static void secp256k1_chilldkg_simplpedpop_participant_step2_prepare_secshare(secp256k1_scalar *out, const secp256k1_scalar *partial_secshares, size_t n) { + size_t i; + + secp256k1_scalar_set_int(out, 0); + for (i = 0; i < n; i++) { + secp256k1_scalar_add(out, out, &partial_secshares[i]); + } +} + +static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_participant_step2(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const secp256k1_chilldkg_simplpedpop_participant_state *state, const unsigned char *cmsg, size_t cmsg_len, const secp256k1_scalar *secshare) { + const uint32_t t = state->t; + const uint32_t n = state->n; + const uint32_t participant_id = state->participant_id; + secp256k1_ge coms_to_secrets[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge sum_coms_to_nonconst_terms[SECP256K1_CHILLDKG_MAX_PARTICIPANTS - 1]; + secp256k1_ge sum_coms[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge sum_coms_tweaked[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge pubtweak; + secp256k1_ge pubshare; + secp256k1_ge pubshare_i; + secp256k1_gej pubsharej; + secp256k1_scalar tweak; + secp256k1_scalar secshare_tweaked; + const unsigned char *pops; + unsigned char buf33[33]; + unsigned char buf33b[33]; + unsigned char xonly32[32]; + uint32_t i; + size_t j; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(dkg_output != NULL); + ARG_CHECK(eq_input != NULL); + ARG_CHECK(state != NULL); + ARG_CHECK(cmsg != NULL); + ARG_CHECK(secshare != NULL); + + /* CoordinatorMsg.from_bytes; parse errors blame the coordinator. */ + if (cmsg_len != 97 * (size_t)n + 33 * (size_t)(t - 1)) { + return SECP256K1_CHILLDKG_FAULTY_COORDINATOR; + } + for (i = 0; i < n; i++) { + if (!secp256k1_chilldkg_point_load(&coms_to_secrets[i], cmsg + 33 * i)) { + return SECP256K1_CHILLDKG_FAULTY_COORDINATOR; + } + } + /* Parse the summed commitments to the non-constant terms. */ + for (j = 1; j < t; j++) { + if (!secp256k1_chilldkg_point_load(&sum_coms_to_nonconst_terms[j - 1], cmsg + 33 * n + 33 * (j - 1))) { + return SECP256K1_CHILLDKG_FAULTY_COORDINATOR; + } + } + pops = cmsg + 33 * n + 33 * (t - 1); + + /* The coordinator must echo our own commitment to the secret. Compare the + * canonical serializations so that the point at infinity (which cannot + * occur here in practice, see participant_step1) is handled, too. */ + secp256k1_chilldkg_point_save(buf33, &state->com_to_secret); + secp256k1_chilldkg_point_save(buf33b, &coms_to_secrets[participant_id]); + if (secp256k1_memcmp_var(buf33, buf33b, 33) != 0) { + return SECP256K1_CHILLDKG_FAULTY_COORDINATOR; + } + + for (i = 0; i < n; i++) { + if (i == participant_id) { + /* No need to check our own pop. */ + continue; + } + if (secp256k1_ge_is_infinity(&coms_to_secrets[i])) { + /* "Participant sent invalid commitment": the x-only serialization + * needed for the pop verification does not exist. */ + *fault_index = i; + return SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR; + } + secp256k1_chilldkg_xonly_save(xonly32, &coms_to_secrets[i]); + if (!secp256k1_chilldkg_simplpedpop_pop_verify(ctx, pops + 64 * i, xonly32, i)) { + /* "Participant sent invalid proof-of-knowledge". */ + *fault_index = i; + return SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR; + } + } + + secp256k1_chilldkg_simplpedpop_assemble_sum_coms(sum_coms, coms_to_secrets, sum_coms_to_nonconst_terms, n, t); + + if (!secp256k1_chilldkg_vss_invalid_taproot_commit(ctx, sum_coms_tweaked, &tweak, &pubtweak, sum_coms, t)) { + /* Unreachable in practice: sum_coms[0] can only be the point at + * infinity if some pop is invalid (checked above), and a tweak hash + * overflow has negligible probability. The reference raises a + * non-protocol error here. */ + *fault_index = UINT32_MAX; + return SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR; + } + + /* Verifying the tweaked secshare against the tweaked pubshare is + * equivalent to verifying the untweaked values, but avoids computing the + * untweaked pubshare in the happy path. */ + secp256k1_chilldkg_vss_pubshare(&pubsharej, sum_coms_tweaked, t, participant_id); + secp256k1_ge_set_gej_var(&pubshare, &pubsharej); + secp256k1_scalar_add(&secshare_tweaked, secshare, &tweak); + if (!secp256k1_chilldkg_vss_verify_secshare(ctx, &secshare_tweaked, &pubshare)) { + /* "Received invalid secshare"; the investigation procedure (a later + * phase) can determine the faulty party. */ + *fault_index = UINT32_MAX; + secp256k1_scalar_clear(&tweak); + secp256k1_scalar_clear(&secshare_tweaked); + return SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR; + } + + /* dkg_output: tweaked secshare, threshold public key (the tweaked + * commitment to the secret), and the tweaked pubshares of all + * participants. */ + secp256k1_scalar_get_b32(dkg_output->secshare32, &secshare_tweaked); + secp256k1_chilldkg_point_save(dkg_output->thresh_pk33, &sum_coms_tweaked[0]); + for (i = 0; i < n; i++) { + if (i != participant_id) { + /* We have computed our own pubshare already. */ + secp256k1_chilldkg_vss_pubshare(&pubsharej, sum_coms_tweaked, t, i); + secp256k1_ge_set_gej_var(&pubshare_i, &pubsharej); + secp256k1_chilldkg_point_save(dkg_output->pubshares33[i], &pubshare_i); + } else { + secp256k1_chilldkg_point_save(dkg_output->pubshares33[i], &pubshare); + } + } + + /* eq_input = u32be(t) || sum_coms.to_bytes() (UNTWEAKED commitment). */ + secp256k1_write_be32(eq_input, t); + for (j = 0; j < t; j++) { + secp256k1_chilldkg_point_save(eq_input + 4 + 33 * j, &sum_coms[j]); + } + + secp256k1_scalar_clear(&tweak); + secp256k1_scalar_clear(&secshare_tweaked); + return SECP256K1_CHILLDKG_SUCCESS; +} + +static void secp256k1_chilldkg_simplpedpop_assemble_sum_coms(secp256k1_ge *sum_coms, const secp256k1_ge *coms_to_secrets, const secp256k1_ge *sum_coms_to_nonconst_terms, size_t n, size_t t) { + secp256k1_gej accj, termj; + size_t i, j; + + /* Sum the commitments to the secrets. */ + secp256k1_gej_set_infinity(&accj); + for (i = 0; i < n; i++) { + secp256k1_gej_set_ge(&termj, &coms_to_secrets[i]); + secp256k1_gej_add_var(&accj, &accj, &termj, NULL); + } + secp256k1_ge_set_gej_var(&sum_coms[0], &accj); + for (j = 1; j < t; j++) { + sum_coms[j] = sum_coms_to_nonconst_terms[j - 1]; + } +} + +static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_coordinator_step(const secp256k1_context *ctx, unsigned char *cmsg, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const unsigned char *const *pmsgs, uint32_t t, uint32_t n) { + secp256k1_ge coms_to_secrets[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge com[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge sum_coms_to_nonconst_terms[SECP256K1_CHILLDKG_MAX_PARTICIPANTS - 1]; + secp256k1_ge sum_coms[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge sum_coms_tweaked[SECP256K1_CHILLDKG_MAX_PARTICIPANTS]; + secp256k1_ge pubtweak; + secp256k1_ge pubshare; + secp256k1_gej pubsharej; + secp256k1_scalar tweak; + uint32_t i; + size_t j; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(cmsg != NULL); + ARG_CHECK(dkg_output != NULL); + ARG_CHECK(eq_input != NULL); + ARG_CHECK(pmsgs != NULL); + + if (t < 1 || n < 1 || t > n || n > SECP256K1_CHILLDKG_MAX_PARTICIPANTS) { + return SECP256K1_CHILLDKG_INVALID_INPUT; + } + + /* Parse the participant messages. A malformed message blames its sender. + * The pops are not verified here; the participants verify them in step 2. */ + for (j = 1; j < t; j++) { + secp256k1_ge_set_infinity(&sum_coms_to_nonconst_terms[j - 1]); + } + for (i = 0; i < n; i++) { + for (j = 0; j < t; j++) { + if (!secp256k1_chilldkg_point_load(&com[j], pmsgs[i] + 33 * j)) { + *fault_index = i; + return SECP256K1_CHILLDKG_FAULTY_PARTICIPANT; + } + } + /* Echo the commitment to the secret ... */ + coms_to_secrets[i] = com[0]; + /* ... and sum the commitments to the non-constant terms (Pedersen, + * Section 5.1 of "Non-Interactive and Information-Theoretic Secure + * Verifiable Secret Sharing"). All inputs are public. */ + for (j = 1; j < t; j++) { + secp256k1_gej aj, bj, sumj; + secp256k1_gej_set_ge(&aj, &sum_coms_to_nonconst_terms[j - 1]); + secp256k1_gej_set_ge(&bj, &com[j]); + secp256k1_gej_add_var(&sumj, &aj, &bj, NULL); + secp256k1_ge_set_gej_var(&sum_coms_to_nonconst_terms[j - 1], &sumj); + } + /* Collect the pops in the coordinator message. */ + memcpy(cmsg + 33 * n + 33 * (t - 1) + 64 * i, pmsgs[i] + 33 * t, 64); + } + + /* cmsg = CoordinatorMsg(coms_to_secrets, sum_coms_to_nonconst_terms, pops).to_bytes() */ + for (i = 0; i < n; i++) { + secp256k1_chilldkg_point_save(cmsg + 33 * i, &coms_to_secrets[i]); + } + for (j = 1; j < t; j++) { + secp256k1_chilldkg_point_save(cmsg + 33 * n + 33 * (j - 1), &sum_coms_to_nonconst_terms[j - 1]); + } + + secp256k1_chilldkg_simplpedpop_assemble_sum_coms(sum_coms, coms_to_secrets, sum_coms_to_nonconst_terms, n, t); + + if (!secp256k1_chilldkg_vss_invalid_taproot_commit(ctx, sum_coms_tweaked, &tweak, &pubtweak, sum_coms, t)) { + /* A participant can make the sum of the commitments to the secrets + * the point at infinity (the coordinator does not verify the pops), + * or the tweak hash overflows (negligible probability). The faulty + * participant cannot be identified without verifying the pops, which + * is the participants' job in step 2. The reference raises a + * non-protocol error here. */ + *fault_index = UINT32_MAX; + secp256k1_scalar_clear(&tweak); + return SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR; + } + + /* The coordinator's DKG output has no secshare (None in the reference). */ + memset(dkg_output->secshare32, 0, 32); + secp256k1_chilldkg_point_save(dkg_output->thresh_pk33, &sum_coms_tweaked[0]); + for (i = 0; i < n; i++) { + secp256k1_chilldkg_vss_pubshare(&pubsharej, sum_coms_tweaked, t, i); + secp256k1_ge_set_gej_var(&pubshare, &pubsharej); + secp256k1_chilldkg_point_save(dkg_output->pubshares33[i], &pubshare); + } + + /* eq_input = u32be(t) || sum_coms.to_bytes() (UNTWEAKED commitment). */ + secp256k1_write_be32(eq_input, t); + for (j = 0; j < t; j++) { + secp256k1_chilldkg_point_save(eq_input + 4 + 33 * j, &sum_coms[j]); + } + + secp256k1_scalar_clear(&tweak); + return SECP256K1_CHILLDKG_SUCCESS; +} + +#endif diff --git a/src/modules/chilldkg/tests_impl.h b/src/modules/chilldkg/tests_impl.h index a6c65ad4..e908c9eb 100644 --- a/src/modules/chilldkg/tests_impl.h +++ b/src/modules/chilldkg/tests_impl.h @@ -14,6 +14,8 @@ #include "util.h" #include "vss.h" +#include "simplpedpop.h" +#include "encpedpop.h" #include "../../group.h" #include "../../hash.h" #include "../../scalar.h" @@ -113,6 +115,92 @@ static const unsigned char vec_vss_coms_tweaked[3][33] = { }; /* all python-side sanity checks passed */ +static const unsigned char vec_simpl_seeds[3][32] = { + { 0xa6, 0xf4, 0xe5, 0xf2, 0x7b, 0xdd, 0xce, 0xa7, 0x26, 0xb2, 0x34, 0xfb, 0x7b, 0xde, 0x82, 0x98, 0xa5, 0x43, 0xc2, 0x51, 0x45, 0xa9, 0x7a, 0xd2, 0x34, 0x3d, 0xb5, 0x22, 0x5b, 0x29, 0x31, 0x14 }, + { 0xb3, 0xa1, 0xef, 0xa6, 0x69, 0xc6, 0x9c, 0xa8, 0xdd, 0x58, 0x0a, 0xfe, 0x92, 0x0b, 0x8c, 0x00, 0xcc, 0x6e, 0x92, 0x2f, 0xd0, 0x7b, 0x88, 0x13, 0x80, 0x20, 0x9b, 0x86, 0x78, 0x2b, 0x3b, 0x9b }, + { 0xe9, 0x9e, 0xe7, 0xeb, 0x84, 0xe2, 0x22, 0xca, 0x0a, 0xcc, 0x8a, 0x88, 0x24, 0x9b, 0x94, 0x28, 0xbd, 0xd9, 0xfd, 0x7e, 0x86, 0x45, 0xb5, 0xb3, 0xb9, 0xc0, 0x90, 0x7d, 0x20, 0x4d, 0x55, 0x61 }, +}; +static const unsigned char vec_simpl_auxs[3][32] = { + { 0x33, 0x0e, 0x6c, 0x54, 0x12, 0x16, 0x2a, 0xc7, 0x24, 0x63, 0x4e, 0x89, 0x6a, 0x05, 0x86, 0xcb, 0x00, 0x7c, 0xa4, 0xf9, 0x87, 0xf7, 0x13, 0x85, 0x5d, 0x29, 0xc4, 0xbb, 0x41, 0x8d, 0x28, 0xdf }, + { 0xf8, 0xfd, 0xb8, 0x21, 0xbc, 0x3d, 0x58, 0x44, 0xea, 0x95, 0x03, 0x73, 0x85, 0xf3, 0x9e, 0xab, 0x9f, 0x31, 0x77, 0xbe, 0x40, 0x81, 0x63, 0x73, 0x85, 0xeb, 0xdd, 0x8d, 0x0e, 0xaf, 0xde, 0xac }, + { 0xe4, 0x6d, 0x0d, 0x20, 0x61, 0x82, 0xee, 0x3c, 0x90, 0xb7, 0xdf, 0xb2, 0x9a, 0x78, 0x5e, 0x8d, 0x6b, 0xb5, 0x37, 0x1d, 0x4f, 0x0d, 0x84, 0xe4, 0xe0, 0xe8, 0x0d, 0xe5, 0x65, 0x04, 0x31, 0x34 }, +}; +static const unsigned char vec_simpl_pmsgs[3][130] = { + { 0x03, 0x8c, 0xb4, 0x5f, 0x49, 0xf0, 0x01, 0x02, 0xf9, 0x84, 0x09, 0x9c, 0xe4, 0x1b, 0x85, 0xed, 0x90, 0x2e, 0xb8, 0x67, 0x69, 0x7a, 0x07, 0x88, 0x62, 0x34, 0xb7, 0x66, 0x32, 0xd2, 0x9b, 0xbe, 0xbc, 0x03, 0x9f, 0x74, 0xfc, 0x67, 0x4f, 0x91, 0xb6, 0x7d, 0xaf, 0x76, 0xb5, 0x25, 0x47, 0xb4, 0x4d, 0x60, 0xf6, 0x06, 0xef, 0x1d, 0x3a, 0xa8, 0x64, 0x8f, 0x68, 0x2d, 0xc6, 0xd3, 0x71, 0x75, 0x63, 0x4c, 0x72, 0x78, 0x3a, 0x94, 0xd6, 0x86, 0x8a, 0x58, 0x61, 0x22, 0x4f, 0xda, 0x99, 0x0d, 0x70, 0x38, 0x88, 0xa8, 0x7b, 0x00, 0xc0, 0x2a, 0x27, 0xca, 0x52, 0x66, 0x4a, 0x99, 0x36, 0xb1, 0x3c, 0xc1, 0xaf, 0xe6, 0x59, 0x12, 0x3c, 0xfe, 0xcb, 0x3b, 0x7a, 0x77, 0x85, 0x0d, 0x96, 0x32, 0x4a, 0x3f, 0x7e, 0xd9, 0x66, 0x22, 0x3b, 0x30, 0x36, 0x73, 0xdf, 0x16, 0x78, 0xd0, 0x76, 0xa6, 0xf8, 0x2f }, + { 0x03, 0xc6, 0x5d, 0x00, 0x8d, 0xd5, 0x1c, 0x5f, 0xe9, 0x64, 0x38, 0x62, 0xbe, 0x0e, 0x7f, 0x6f, 0xcf, 0x08, 0x98, 0x90, 0xe9, 0x2d, 0x9e, 0x6e, 0xc3, 0x3d, 0x62, 0x44, 0xbf, 0x77, 0x7e, 0x7a, 0x1b, 0x03, 0x19, 0xe5, 0x0b, 0xbb, 0x2a, 0xb2, 0xd4, 0x65, 0x36, 0xd9, 0xbf, 0xef, 0x5c, 0x04, 0xa1, 0x09, 0xb3, 0x07, 0x47, 0x17, 0xc4, 0x5c, 0x2f, 0xdf, 0xc8, 0x21, 0x52, 0x1f, 0x3f, 0x84, 0xae, 0x11, 0xe9, 0xbd, 0xcd, 0xb3, 0xba, 0x31, 0x72, 0xe5, 0x04, 0xc1, 0x51, 0xc2, 0x59, 0xfa, 0x8d, 0x05, 0xb7, 0xe1, 0x75, 0x98, 0x35, 0xd3, 0xb9, 0x47, 0xf0, 0x57, 0x5b, 0xe4, 0x37, 0xfc, 0x91, 0x1e, 0x61, 0xe7, 0x4b, 0x1d, 0x5c, 0x07, 0x18, 0x94, 0x5f, 0x33, 0x79, 0x55, 0x64, 0x35, 0x6d, 0x3e, 0x69, 0x92, 0x0f, 0xc0, 0x7a, 0x03, 0xc0, 0x33, 0x7f, 0x26, 0xb5, 0xfd, 0xc3, 0x3e, 0x47, 0x5c }, + { 0x02, 0x99, 0xad, 0x8e, 0x28, 0xa8, 0x4c, 0x91, 0x5e, 0xc8, 0x19, 0x03, 0xc3, 0x1b, 0xb4, 0x0f, 0xc5, 0xa5, 0xd4, 0xad, 0x06, 0xdf, 0xd0, 0xaa, 0x77, 0xd1, 0xf4, 0x62, 0x8e, 0x07, 0xf4, 0x58, 0xee, 0x03, 0x43, 0x6e, 0x96, 0x90, 0x01, 0x45, 0x43, 0xb7, 0x27, 0x3f, 0xa4, 0xd8, 0x86, 0x17, 0x63, 0x70, 0x2b, 0x50, 0x97, 0xaa, 0x1b, 0x8c, 0x71, 0x91, 0x9d, 0x55, 0x5c, 0x59, 0xbe, 0x54, 0x05, 0x9d, 0x54, 0x08, 0x18, 0x4a, 0x80, 0xbd, 0x80, 0x31, 0x49, 0x0d, 0xef, 0x23, 0x98, 0xb2, 0x1a, 0x4f, 0x59, 0x20, 0x29, 0x72, 0xbd, 0x3d, 0xb3, 0xeb, 0x7e, 0x9e, 0xec, 0x9c, 0x4c, 0xe9, 0x56, 0x7a, 0x97, 0xab, 0x0b, 0xc9, 0x90, 0xd9, 0x7f, 0xb9, 0xc5, 0x6f, 0x67, 0x74, 0x29, 0x8b, 0x32, 0x41, 0x05, 0xf5, 0xe0, 0x4f, 0xa2, 0xbe, 0x68, 0x84, 0x43, 0xe6, 0x13, 0xd1, 0xbb, 0xe9, 0x8d, 0x6b }, +}; +static const unsigned char vec_simpl_cmsg[324] = { 0x03, 0x8c, 0xb4, 0x5f, 0x49, 0xf0, 0x01, 0x02, 0xf9, 0x84, 0x09, 0x9c, 0xe4, 0x1b, 0x85, 0xed, 0x90, 0x2e, 0xb8, 0x67, 0x69, 0x7a, 0x07, 0x88, 0x62, 0x34, 0xb7, 0x66, 0x32, 0xd2, 0x9b, 0xbe, 0xbc, 0x03, 0xc6, 0x5d, 0x00, 0x8d, 0xd5, 0x1c, 0x5f, 0xe9, 0x64, 0x38, 0x62, 0xbe, 0x0e, 0x7f, 0x6f, 0xcf, 0x08, 0x98, 0x90, 0xe9, 0x2d, 0x9e, 0x6e, 0xc3, 0x3d, 0x62, 0x44, 0xbf, 0x77, 0x7e, 0x7a, 0x1b, 0x02, 0x99, 0xad, 0x8e, 0x28, 0xa8, 0x4c, 0x91, 0x5e, 0xc8, 0x19, 0x03, 0xc3, 0x1b, 0xb4, 0x0f, 0xc5, 0xa5, 0xd4, 0xad, 0x06, 0xdf, 0xd0, 0xaa, 0x77, 0xd1, 0xf4, 0x62, 0x8e, 0x07, 0xf4, 0x58, 0xee, 0x02, 0x64, 0xcc, 0x1f, 0x7a, 0x2d, 0x27, 0xbb, 0x89, 0xf0, 0x68, 0xb1, 0x80, 0xfe, 0xe6, 0x9a, 0x6c, 0xd6, 0xce, 0x69, 0x93, 0x58, 0x6c, 0x61, 0x2b, 0x8b, 0x2e, 0x31, 0x08, 0xb9, 0x20, 0xf5, 0xac, 0x72, 0x78, 0x3a, 0x94, 0xd6, 0x86, 0x8a, 0x58, 0x61, 0x22, 0x4f, 0xda, 0x99, 0x0d, 0x70, 0x38, 0x88, 0xa8, 0x7b, 0x00, 0xc0, 0x2a, 0x27, 0xca, 0x52, 0x66, 0x4a, 0x99, 0x36, 0xb1, 0x3c, 0xc1, 0xaf, 0xe6, 0x59, 0x12, 0x3c, 0xfe, 0xcb, 0x3b, 0x7a, 0x77, 0x85, 0x0d, 0x96, 0x32, 0x4a, 0x3f, 0x7e, 0xd9, 0x66, 0x22, 0x3b, 0x30, 0x36, 0x73, 0xdf, 0x16, 0x78, 0xd0, 0x76, 0xa6, 0xf8, 0x2f, 0xe9, 0xbd, 0xcd, 0xb3, 0xba, 0x31, 0x72, 0xe5, 0x04, 0xc1, 0x51, 0xc2, 0x59, 0xfa, 0x8d, 0x05, 0xb7, 0xe1, 0x75, 0x98, 0x35, 0xd3, 0xb9, 0x47, 0xf0, 0x57, 0x5b, 0xe4, 0x37, 0xfc, 0x91, 0x1e, 0x61, 0xe7, 0x4b, 0x1d, 0x5c, 0x07, 0x18, 0x94, 0x5f, 0x33, 0x79, 0x55, 0x64, 0x35, 0x6d, 0x3e, 0x69, 0x92, 0x0f, 0xc0, 0x7a, 0x03, 0xc0, 0x33, 0x7f, 0x26, 0xb5, 0xfd, 0xc3, 0x3e, 0x47, 0x5c, 0x54, 0x08, 0x18, 0x4a, 0x80, 0xbd, 0x80, 0x31, 0x49, 0x0d, 0xef, 0x23, 0x98, 0xb2, 0x1a, 0x4f, 0x59, 0x20, 0x29, 0x72, 0xbd, 0x3d, 0xb3, 0xeb, 0x7e, 0x9e, 0xec, 0x9c, 0x4c, 0xe9, 0x56, 0x7a, 0x97, 0xab, 0x0b, 0xc9, 0x90, 0xd9, 0x7f, 0xb9, 0xc5, 0x6f, 0x67, 0x74, 0x29, 0x8b, 0x32, 0x41, 0x05, 0xf5, 0xe0, 0x4f, 0xa2, 0xbe, 0x68, 0x84, 0x43, 0xe6, 0x13, 0xd1, 0xbb, 0xe9, 0x8d, 0x6b }; +static const unsigned char vec_simpl_eq_input[70] = { 0x00, 0x00, 0x00, 0x02, 0x02, 0xd0, 0x90, 0x3c, 0xe9, 0x3d, 0xa3, 0x13, 0x95, 0x1e, 0x45, 0xd4, 0xa1, 0xb3, 0x84, 0x79, 0xbe, 0x50, 0x00, 0xc0, 0xd0, 0x6b, 0xec, 0xbc, 0x9a, 0xb3, 0xdf, 0x19, 0x56, 0x14, 0xd2, 0x15, 0x4c, 0x02, 0x64, 0xcc, 0x1f, 0x7a, 0x2d, 0x27, 0xbb, 0x89, 0xf0, 0x68, 0xb1, 0x80, 0xfe, 0xe6, 0x9a, 0x6c, 0xd6, 0xce, 0x69, 0x93, 0x58, 0x6c, 0x61, 0x2b, 0x8b, 0x2e, 0x31, 0x08, 0xb9, 0x20, 0xf5, 0xac }; +static const unsigned char vec_simpl_untweaked_secshares[3][32] = { + { 0x59, 0x2f, 0xa6, 0x21, 0xe3, 0xf7, 0xb2, 0xd2, 0x8d, 0x65, 0x4b, 0x93, 0xc2, 0xfd, 0x3f, 0xae, 0xe9, 0x58, 0x56, 0xf6, 0xde, 0x20, 0xb4, 0xc9, 0x24, 0xae, 0x60, 0x9a, 0x5f, 0x95, 0xaf, 0x91 }, + { 0x19, 0xd3, 0x87, 0xab, 0x70, 0xd1, 0x17, 0x08, 0x5e, 0xbd, 0x0e, 0xae, 0x69, 0x96, 0xe3, 0x1b, 0x12, 0x93, 0x8b, 0x7b, 0x74, 0xd6, 0xe9, 0xbd, 0xdc, 0xe8, 0x49, 0x12, 0x89, 0xdc, 0xf9, 0x9a }, + { 0xda, 0x77, 0x69, 0x34, 0xfd, 0xaa, 0x7b, 0x3e, 0x30, 0x14, 0xd1, 0xc9, 0x10, 0x30, 0x86, 0x85, 0xf6, 0x7d, 0x9c, 0xe6, 0xba, 0xd5, 0xbe, 0xee, 0x54, 0xf4, 0x90, 0x17, 0x84, 0x5a, 0x84, 0xe4 }, +}; +static const unsigned char vec_simpl_secshares[3][32] = { + { 0x3b, 0x3a, 0x99, 0x94, 0x6a, 0x23, 0x46, 0xb0, 0xd2, 0xf6, 0x9f, 0x20, 0xb0, 0xa6, 0x93, 0x0c, 0x0d, 0x10, 0x96, 0xc7, 0x7b, 0x07, 0xd8, 0x34, 0x5c, 0xc4, 0x54, 0x36, 0x2c, 0xfd, 0xc6, 0x6d }, + { 0xfb, 0xde, 0x7b, 0x1d, 0xf6, 0xfc, 0xaa, 0xe6, 0xa4, 0x4e, 0x62, 0x3b, 0x57, 0x40, 0x36, 0x76, 0xf0, 0xfa, 0xa8, 0x32, 0xc1, 0x06, 0xad, 0x64, 0xd4, 0xd0, 0x9b, 0x3b, 0x27, 0x7b, 0x51, 0xb7 }, + { 0xbc, 0x82, 0x5c, 0xa7, 0x83, 0xd6, 0x0f, 0x1c, 0x75, 0xa6, 0x25, 0x55, 0xfd, 0xd9, 0xd9, 0xe3, 0x1a, 0x35, 0xdc, 0xb7, 0x57, 0xbc, 0xe2, 0x59, 0x8d, 0x0a, 0x83, 0xb3, 0x51, 0xc2, 0x9b, 0xc0 }, +}; +static const unsigned char vec_simpl_thresh_pk[33] = { 0x03, 0x40, 0x04, 0x9c, 0x23, 0x3f, 0xcd, 0xc0, 0xed, 0x61, 0xe5, 0xc0, 0x2b, 0x69, 0xc2, 0xd8, 0x87, 0x6e, 0x78, 0xe9, 0xb3, 0xd4, 0x6d, 0x00, 0x7e, 0x26, 0x99, 0x8e, 0xeb, 0xfe, 0xa0, 0xdd, 0xc2 }; +static const unsigned char vec_simpl_pubshares[3][33] = { + { 0x02, 0xe4, 0x79, 0xc9, 0x2f, 0x41, 0xe8, 0x17, 0xef, 0x02, 0xa0, 0xa7, 0x50, 0x72, 0xb5, 0x9c, 0xce, 0xcd, 0xc5, 0xbf, 0xae, 0x56, 0x23, 0x19, 0x14, 0x97, 0x58, 0x9f, 0x80, 0xcd, 0x8c, 0x0b, 0x66 }, + { 0x03, 0xc2, 0x2e, 0x8e, 0x3b, 0x79, 0xff, 0xa2, 0x98, 0xb3, 0xc6, 0x26, 0xdd, 0x32, 0xdb, 0x4d, 0xad, 0xd6, 0xca, 0xeb, 0xae, 0xb3, 0x21, 0xb2, 0x0e, 0xc3, 0x7e, 0x80, 0xce, 0x55, 0x26, 0xdd, 0xed }, + { 0x03, 0x47, 0xef, 0x6d, 0x6b, 0x08, 0x3c, 0x91, 0xe2, 0x0a, 0xb0, 0x09, 0xa9, 0x1e, 0xa0, 0x40, 0x0c, 0x45, 0x19, 0x04, 0x53, 0x4a, 0xa3, 0x7f, 0x12, 0x1f, 0x22, 0xcb, 0xbf, 0x5c, 0x80, 0xcb, 0xf5 }, +}; +/* simplpedpop n = 3, t = 2 */ +static const unsigned char vec_enc_hostseckeys[3][32] = { + { 0xdb, 0x3d, 0x72, 0xeb, 0x38, 0x03, 0x1d, 0x5e, 0x7e, 0x83, 0xd7, 0x23, 0x30, 0xe2, 0xd0, 0xb9, 0x41, 0x77, 0xcb, 0x4c, 0x35, 0x65, 0x59, 0xee, 0x41, 0xca, 0xd5, 0x61, 0x52, 0x58, 0xd5, 0x00 }, + { 0xe8, 0x12, 0xb0, 0xff, 0x5b, 0xb8, 0xf0, 0x8a, 0xfc, 0x87, 0xd7, 0xc3, 0x5b, 0x92, 0x17, 0x22, 0xab, 0x4d, 0x89, 0x76, 0x87, 0x45, 0x7a, 0x27, 0x2f, 0x71, 0x0c, 0xfb, 0x0a, 0xc9, 0x94, 0x50 }, + { 0xff, 0x64, 0xf0, 0x9b, 0xb5, 0x03, 0xb5, 0x66, 0x29, 0x6e, 0xb6, 0xb2, 0x69, 0x09, 0x60, 0xab, 0x8b, 0x42, 0x13, 0x2d, 0x64, 0xdb, 0x9d, 0x4d, 0x27, 0x74, 0xc1, 0x19, 0x15, 0x74, 0x49, 0xba }, +}; +static const unsigned char vec_enc_hostpubkeys[3][33] = { + { 0x03, 0x30, 0x45, 0x46, 0x1f, 0xd4, 0xb4, 0xe8, 0xbc, 0xd2, 0x88, 0x8b, 0xc0, 0x5d, 0x2f, 0x8c, 0x86, 0x10, 0x53, 0x43, 0x10, 0x2e, 0x42, 0x5f, 0x18, 0xae, 0x64, 0xe0, 0x03, 0x2a, 0xba, 0xe1, 0xe3 }, + { 0x03, 0x5c, 0x1f, 0xe2, 0x1b, 0xce, 0x62, 0x99, 0x2a, 0xe0, 0x66, 0x67, 0x7e, 0xc0, 0x4f, 0x68, 0xfc, 0x1b, 0x25, 0x79, 0x5f, 0x1a, 0x33, 0x62, 0x9a, 0xfb, 0x00, 0x24, 0x90, 0x60, 0x02, 0x85, 0x37 }, + { 0x02, 0x19, 0xd6, 0xb1, 0x40, 0xab, 0x84, 0xa2, 0x5a, 0x49, 0x38, 0xcd, 0x62, 0xb2, 0x63, 0xd1, 0xd9, 0x85, 0x5e, 0xa4, 0x1e, 0x1e, 0xbc, 0x19, 0x56, 0x85, 0x47, 0x7f, 0xed, 0xd6, 0xd6, 0xa1, 0x38 }, +}; +static const unsigned char vec_enc_randoms[3][32] = { + { 0x95, 0xd1, 0xe5, 0x58, 0xb0, 0xba, 0x91, 0x29, 0x56, 0xfc, 0x6a, 0x07, 0xf1, 0xc1, 0xb5, 0x7a, 0x79, 0x98, 0x04, 0x26, 0xf7, 0x62, 0xfd, 0x70, 0x56, 0x46, 0x27, 0xab, 0x00, 0x05, 0xb1, 0x57 }, + { 0x5d, 0xec, 0x98, 0x69, 0xa0, 0xf1, 0x7d, 0xbb, 0x48, 0x07, 0x10, 0x1d, 0x39, 0xe7, 0x77, 0x67, 0x24, 0xeb, 0xb1, 0xfe, 0x67, 0x36, 0x55, 0xd8, 0xd9, 0x29, 0xe1, 0x42, 0x4b, 0x74, 0x78, 0x4c }, + { 0x7a, 0x98, 0x6c, 0xf5, 0x5d, 0xfa, 0xfc, 0x25, 0x0b, 0xa4, 0xb9, 0x51, 0x8a, 0x86, 0x9f, 0xfc, 0xb9, 0x84, 0x5a, 0x02, 0x0e, 0x46, 0xcd, 0xeb, 0xfd, 0xd8, 0x69, 0xf0, 0x96, 0x1b, 0xe1, 0x33 }, +}; +static const unsigned char vec_enc_context[103] = { 0x00, 0x00, 0x00, 0x02, 0x03, 0x30, 0x45, 0x46, 0x1f, 0xd4, 0xb4, 0xe8, 0xbc, 0xd2, 0x88, 0x8b, 0xc0, 0x5d, 0x2f, 0x8c, 0x86, 0x10, 0x53, 0x43, 0x10, 0x2e, 0x42, 0x5f, 0x18, 0xae, 0x64, 0xe0, 0x03, 0x2a, 0xba, 0xe1, 0xe3, 0x03, 0x5c, 0x1f, 0xe2, 0x1b, 0xce, 0x62, 0x99, 0x2a, 0xe0, 0x66, 0x67, 0x7e, 0xc0, 0x4f, 0x68, 0xfc, 0x1b, 0x25, 0x79, 0x5f, 0x1a, 0x33, 0x62, 0x9a, 0xfb, 0x00, 0x24, 0x90, 0x60, 0x02, 0x85, 0x37, 0x02, 0x19, 0xd6, 0xb1, 0x40, 0xab, 0x84, 0xa2, 0x5a, 0x49, 0x38, 0xcd, 0x62, 0xb2, 0x63, 0xd1, 0xd9, 0x85, 0x5e, 0xa4, 0x1e, 0x1e, 0xbc, 0x19, 0x56, 0x85, 0x47, 0x7f, 0xed, 0xd6, 0xd6, 0xa1, 0x38 }; +static const unsigned char vec_enc_secnonces[3][32] = { + { 0xe0, 0x4c, 0x21, 0xfc, 0x3d, 0xe8, 0x14, 0x0b, 0xa5, 0x69, 0x1b, 0xd8, 0x95, 0x74, 0xb5, 0x39, 0x13, 0x62, 0x7e, 0x30, 0x4a, 0x57, 0x9e, 0xa3, 0x78, 0x27, 0x48, 0x67, 0xb2, 0x17, 0x02, 0xd7 }, + { 0x40, 0x32, 0xd6, 0xcc, 0x81, 0x70, 0xca, 0x6c, 0xcd, 0x31, 0x18, 0xa6, 0x07, 0xc0, 0x68, 0x45, 0x6b, 0x3e, 0x91, 0x28, 0xd5, 0x99, 0x65, 0xbe, 0x57, 0x02, 0x11, 0xd4, 0x2a, 0x3f, 0x28, 0xdb }, + { 0x34, 0x10, 0x60, 0x33, 0x60, 0x32, 0xc1, 0xc5, 0x8a, 0x76, 0x9a, 0xcb, 0xcd, 0xee, 0x2d, 0x78, 0xe7, 0x41, 0x89, 0x7f, 0xe0, 0xdb, 0xda, 0x5a, 0x8f, 0xf5, 0xa0, 0x1c, 0x4f, 0x01, 0x26, 0x36 }, +}; +static const unsigned char vec_enc_pubnonces[3][33] = { + { 0x02, 0xb4, 0x37, 0x07, 0x73, 0x50, 0x98, 0x00, 0xf2, 0x22, 0x91, 0x6a, 0x54, 0x79, 0x78, 0x41, 0xd6, 0xcf, 0xc5, 0xb7, 0x00, 0x88, 0x0b, 0xcf, 0xb0, 0x39, 0xfb, 0x00, 0x08, 0x49, 0x41, 0x5f, 0x3f }, + { 0x03, 0x97, 0x21, 0xcb, 0xf9, 0xbe, 0xde, 0xbf, 0x1b, 0xd5, 0x53, 0x0a, 0x04, 0x8d, 0x9d, 0x5b, 0x08, 0xf4, 0x6b, 0xb6, 0x1e, 0x6e, 0x7f, 0xf6, 0xc9, 0x96, 0x12, 0x9b, 0x14, 0x9e, 0xf3, 0xa0, 0x2d }, + { 0x02, 0x12, 0xab, 0xc1, 0x3e, 0x84, 0x05, 0xa3, 0x3f, 0xcb, 0x4c, 0x43, 0x77, 0x43, 0xf8, 0x65, 0x20, 0x37, 0xf1, 0x4b, 0x67, 0x0e, 0x8d, 0x57, 0x42, 0xea, 0x26, 0x33, 0xa6, 0xec, 0x5e, 0x42, 0x37 }, +}; +static const unsigned char vec_enc_pmsgs[3][259] = { + { 0x03, 0xec, 0x16, 0x3c, 0xbf, 0xfc, 0x00, 0x8b, 0xf2, 0x5b, 0x99, 0x89, 0x8f, 0x5c, 0x4c, 0x0b, 0x5b, 0x00, 0xf3, 0xeb, 0xf8, 0xb2, 0xde, 0x73, 0x0d, 0x78, 0xdb, 0xef, 0xcb, 0xad, 0x01, 0xf9, 0x12, 0x03, 0xf2, 0x85, 0xe9, 0xd4, 0x54, 0x84, 0x8b, 0x3e, 0xc0, 0xaa, 0x4b, 0x14, 0xd5, 0x9c, 0x40, 0x8c, 0xd4, 0xa2, 0xed, 0x0a, 0x7d, 0x12, 0xf6, 0x4d, 0xed, 0xa7, 0xb7, 0x46, 0x0b, 0xee, 0xe4, 0xd1, 0xc0, 0x01, 0xd9, 0x1f, 0x98, 0x64, 0x8c, 0xa5, 0x81, 0xe0, 0x48, 0xef, 0x5b, 0xb8, 0xaa, 0x8f, 0x17, 0xe7, 0x05, 0xc8, 0x78, 0x4b, 0xaa, 0xe1, 0xbe, 0xc1, 0x2e, 0x9f, 0xc8, 0x16, 0x79, 0x1d, 0x2e, 0x54, 0xde, 0xef, 0x4b, 0x3a, 0xba, 0xcf, 0x76, 0x71, 0x63, 0x53, 0x42, 0xd5, 0x78, 0xfb, 0x32, 0x18, 0xce, 0x70, 0x27, 0xe1, 0x6e, 0x1e, 0x3c, 0x55, 0x10, 0x11, 0x62, 0xcf, 0xfa, 0x7c, 0x02, 0xb4, 0x37, 0x07, 0x73, 0x50, 0x98, 0x00, 0xf2, 0x22, 0x91, 0x6a, 0x54, 0x79, 0x78, 0x41, 0xd6, 0xcf, 0xc5, 0xb7, 0x00, 0x88, 0x0b, 0xcf, 0xb0, 0x39, 0xfb, 0x00, 0x08, 0x49, 0x41, 0x5f, 0x3f, 0x01, 0x0c, 0xd2, 0xc2, 0xfe, 0xe8, 0x28, 0x59, 0xf3, 0xe8, 0x8c, 0xe9, 0x34, 0x4e, 0x90, 0x48, 0xd2, 0x45, 0xe2, 0xb1, 0xdc, 0x31, 0x06, 0x46, 0x7e, 0xf6, 0xda, 0xc6, 0x9b, 0xa9, 0x7b, 0xfb, 0xd6, 0xf0, 0xef, 0x8e, 0x90, 0x8d, 0xa1, 0x43, 0x18, 0x4b, 0xd1, 0x3a, 0x7d, 0xa1, 0x96, 0x19, 0x34, 0x98, 0xb5, 0xcf, 0x50, 0x0c, 0x96, 0x5b, 0xe3, 0xba, 0x60, 0x75, 0xac, 0x39, 0x79, 0x00, 0xcc, 0x77, 0x7c, 0x7f, 0xb5, 0x43, 0x4f, 0x27, 0x2e, 0x17, 0xdb, 0xf6, 0x2f, 0xcb, 0xe2, 0x1e, 0x39, 0x14, 0x18, 0x24, 0x87, 0x11, 0x8c, 0x5f, 0x82, 0xe0, 0xbc, 0x49, 0xed, 0x36, 0xea, 0xd9 }, + { 0x02, 0xba, 0x32, 0x18, 0xa3, 0xd4, 0xd6, 0x53, 0xcc, 0x8a, 0xb6, 0x1d, 0x4b, 0xea, 0x1f, 0xc9, 0xfd, 0x08, 0xa5, 0x1b, 0x5f, 0xb1, 0x93, 0xba, 0xa8, 0x3d, 0x37, 0x81, 0x16, 0x20, 0x5d, 0x52, 0xb9, 0x02, 0x22, 0xd4, 0x83, 0xee, 0x97, 0x61, 0x5a, 0x68, 0x49, 0x5e, 0x87, 0xaa, 0x1a, 0xad, 0x0d, 0x42, 0x38, 0x8f, 0x6f, 0xd6, 0x93, 0x24, 0xca, 0x13, 0x3a, 0x0b, 0x8f, 0x38, 0xbd, 0x7b, 0xb0, 0x37, 0x71, 0x7d, 0xe1, 0xc6, 0xd8, 0xf7, 0x14, 0x89, 0x65, 0x7c, 0xa4, 0xab, 0x22, 0x6b, 0xfe, 0x00, 0x72, 0x39, 0x03, 0xbf, 0xbc, 0x17, 0x3f, 0x53, 0x06, 0x1a, 0xae, 0x44, 0xba, 0x0d, 0x10, 0xa4, 0x1a, 0x13, 0xf1, 0xcc, 0x25, 0xa6, 0x1a, 0xf0, 0x53, 0xc4, 0x29, 0x66, 0xf9, 0x7f, 0x3e, 0xc6, 0x88, 0x32, 0xac, 0xd5, 0xe8, 0x28, 0x06, 0x47, 0xf1, 0x04, 0x43, 0xae, 0xdb, 0x90, 0x00, 0x3a, 0x03, 0x97, 0x21, 0xcb, 0xf9, 0xbe, 0xde, 0xbf, 0x1b, 0xd5, 0x53, 0x0a, 0x04, 0x8d, 0x9d, 0x5b, 0x08, 0xf4, 0x6b, 0xb6, 0x1e, 0x6e, 0x7f, 0xf6, 0xc9, 0x96, 0x12, 0x9b, 0x14, 0x9e, 0xf3, 0xa0, 0x2d, 0x10, 0x95, 0x16, 0x68, 0xd3, 0x2a, 0xa7, 0x04, 0xcf, 0xf5, 0x60, 0xb9, 0x10, 0x40, 0xc7, 0x67, 0xdd, 0x3d, 0x36, 0x9d, 0x47, 0x18, 0x2e, 0x39, 0xe8, 0xc1, 0x1b, 0x38, 0xb3, 0xc6, 0x86, 0x04, 0x68, 0x83, 0x1f, 0xeb, 0x67, 0x8c, 0xff, 0xca, 0x77, 0x53, 0x51, 0x62, 0x4e, 0x9e, 0x9f, 0x3b, 0x88, 0xe0, 0x51, 0x94, 0xd8, 0x0b, 0xf8, 0x47, 0xe3, 0xe8, 0x8b, 0xe8, 0x32, 0xa4, 0x5e, 0xdc, 0x9d, 0x77, 0x5b, 0x9a, 0x7b, 0x71, 0x96, 0xca, 0xb9, 0x2c, 0x3b, 0xff, 0x7b, 0x94, 0x46, 0x24, 0x4a, 0x08, 0x23, 0x2d, 0x32, 0x8f, 0xb9, 0x4c, 0x30, 0x8c, 0x2f, 0xea, 0x3b, 0x47, 0xf9, 0xc6 }, + { 0x03, 0x58, 0x20, 0x38, 0x53, 0xa3, 0x8b, 0x1c, 0xa3, 0x49, 0x5f, 0x78, 0xfb, 0x4a, 0xcb, 0x67, 0x95, 0x30, 0xa5, 0xe6, 0xe9, 0x12, 0xfc, 0x6f, 0x49, 0x7e, 0x4f, 0x6c, 0x9d, 0x29, 0xf3, 0x1a, 0x5e, 0x03, 0xf4, 0xb1, 0x99, 0xf5, 0x2a, 0x56, 0x5b, 0x7e, 0x06, 0xc8, 0x5b, 0x4c, 0xd9, 0xc4, 0x8f, 0x14, 0xa0, 0x22, 0xbd, 0x95, 0x33, 0x6a, 0x8d, 0xa0, 0xf5, 0x53, 0x5c, 0x62, 0xd6, 0x1d, 0x24, 0x2f, 0x3e, 0xea, 0x96, 0xeb, 0xd9, 0xb0, 0x64, 0x21, 0x6a, 0x1b, 0x3d, 0x4d, 0x34, 0xc0, 0x20, 0xc8, 0x5f, 0x03, 0xf2, 0xe9, 0x3d, 0x13, 0x6d, 0x74, 0x97, 0xe8, 0x26, 0xf9, 0x0c, 0xef, 0x4e, 0xfe, 0xd3, 0x68, 0x5f, 0x01, 0x91, 0x0a, 0x1c, 0x72, 0x4f, 0xbc, 0x97, 0xfc, 0x66, 0x8d, 0x76, 0x62, 0xde, 0x33, 0xdb, 0x70, 0x58, 0x2f, 0x73, 0xd3, 0xc1, 0x19, 0xe1, 0x98, 0xe0, 0x6b, 0x3a, 0x4d, 0x02, 0x12, 0xab, 0xc1, 0x3e, 0x84, 0x05, 0xa3, 0x3f, 0xcb, 0x4c, 0x43, 0x77, 0x43, 0xf8, 0x65, 0x20, 0x37, 0xf1, 0x4b, 0x67, 0x0e, 0x8d, 0x57, 0x42, 0xea, 0x26, 0x33, 0xa6, 0xec, 0x5e, 0x42, 0x37, 0x57, 0x28, 0x52, 0x4e, 0xb1, 0x88, 0xcb, 0xa5, 0xc5, 0xda, 0x24, 0x60, 0x44, 0x7a, 0xa8, 0x1b, 0xc7, 0x3f, 0x6b, 0x53, 0x2e, 0x75, 0xc9, 0xf6, 0xd7, 0x54, 0x9b, 0x7f, 0xdc, 0xe9, 0xee, 0x7f, 0x04, 0xae, 0xb4, 0x62, 0xdd, 0x76, 0x6c, 0x4f, 0x09, 0xaf, 0xa9, 0x8c, 0x91, 0x63, 0x49, 0x4f, 0x3c, 0x97, 0x4a, 0xa6, 0x8c, 0xb7, 0x8a, 0xf3, 0x0b, 0x09, 0xe7, 0xc5, 0x09, 0x93, 0x38, 0x81, 0x2e, 0x5e, 0xa8, 0x9d, 0xeb, 0xc5, 0xfd, 0xf2, 0xfc, 0xae, 0x0c, 0x3a, 0xbf, 0x94, 0x6f, 0x3e, 0xce, 0xd5, 0x8a, 0xa4, 0xf4, 0xfa, 0x39, 0x4a, 0x92, 0xa2, 0xe1, 0xe7, 0x83, 0x9f, 0xd2, 0xc8 }, +}; +static const unsigned char vec_enc_cmsg[423] = { 0x03, 0xec, 0x16, 0x3c, 0xbf, 0xfc, 0x00, 0x8b, 0xf2, 0x5b, 0x99, 0x89, 0x8f, 0x5c, 0x4c, 0x0b, 0x5b, 0x00, 0xf3, 0xeb, 0xf8, 0xb2, 0xde, 0x73, 0x0d, 0x78, 0xdb, 0xef, 0xcb, 0xad, 0x01, 0xf9, 0x12, 0x02, 0xba, 0x32, 0x18, 0xa3, 0xd4, 0xd6, 0x53, 0xcc, 0x8a, 0xb6, 0x1d, 0x4b, 0xea, 0x1f, 0xc9, 0xfd, 0x08, 0xa5, 0x1b, 0x5f, 0xb1, 0x93, 0xba, 0xa8, 0x3d, 0x37, 0x81, 0x16, 0x20, 0x5d, 0x52, 0xb9, 0x03, 0x58, 0x20, 0x38, 0x53, 0xa3, 0x8b, 0x1c, 0xa3, 0x49, 0x5f, 0x78, 0xfb, 0x4a, 0xcb, 0x67, 0x95, 0x30, 0xa5, 0xe6, 0xe9, 0x12, 0xfc, 0x6f, 0x49, 0x7e, 0x4f, 0x6c, 0x9d, 0x29, 0xf3, 0x1a, 0x5e, 0x03, 0x14, 0x72, 0x50, 0xbc, 0x22, 0x95, 0xb2, 0x46, 0x18, 0x25, 0x31, 0x7a, 0x10, 0x01, 0x12, 0x09, 0x32, 0xf4, 0xea, 0x0c, 0x8a, 0xaa, 0xba, 0xc3, 0x24, 0xa4, 0x35, 0x5f, 0x6e, 0xf2, 0x85, 0xb6, 0xc0, 0x01, 0xd9, 0x1f, 0x98, 0x64, 0x8c, 0xa5, 0x81, 0xe0, 0x48, 0xef, 0x5b, 0xb8, 0xaa, 0x8f, 0x17, 0xe7, 0x05, 0xc8, 0x78, 0x4b, 0xaa, 0xe1, 0xbe, 0xc1, 0x2e, 0x9f, 0xc8, 0x16, 0x79, 0x1d, 0x2e, 0x54, 0xde, 0xef, 0x4b, 0x3a, 0xba, 0xcf, 0x76, 0x71, 0x63, 0x53, 0x42, 0xd5, 0x78, 0xfb, 0x32, 0x18, 0xce, 0x70, 0x27, 0xe1, 0x6e, 0x1e, 0x3c, 0x55, 0x10, 0x11, 0x62, 0xcf, 0xfa, 0x7c, 0x71, 0x7d, 0xe1, 0xc6, 0xd8, 0xf7, 0x14, 0x89, 0x65, 0x7c, 0xa4, 0xab, 0x22, 0x6b, 0xfe, 0x00, 0x72, 0x39, 0x03, 0xbf, 0xbc, 0x17, 0x3f, 0x53, 0x06, 0x1a, 0xae, 0x44, 0xba, 0x0d, 0x10, 0xa4, 0x1a, 0x13, 0xf1, 0xcc, 0x25, 0xa6, 0x1a, 0xf0, 0x53, 0xc4, 0x29, 0x66, 0xf9, 0x7f, 0x3e, 0xc6, 0x88, 0x32, 0xac, 0xd5, 0xe8, 0x28, 0x06, 0x47, 0xf1, 0x04, 0x43, 0xae, 0xdb, 0x90, 0x00, 0x3a, 0x3e, 0xea, 0x96, 0xeb, 0xd9, 0xb0, 0x64, 0x21, 0x6a, 0x1b, 0x3d, 0x4d, 0x34, 0xc0, 0x20, 0xc8, 0x5f, 0x03, 0xf2, 0xe9, 0x3d, 0x13, 0x6d, 0x74, 0x97, 0xe8, 0x26, 0xf9, 0x0c, 0xef, 0x4e, 0xfe, 0xd3, 0x68, 0x5f, 0x01, 0x91, 0x0a, 0x1c, 0x72, 0x4f, 0xbc, 0x97, 0xfc, 0x66, 0x8d, 0x76, 0x62, 0xde, 0x33, 0xdb, 0x70, 0x58, 0x2f, 0x73, 0xd3, 0xc1, 0x19, 0xe1, 0x98, 0xe0, 0x6b, 0x3a, 0x4d, 0x02, 0xb4, 0x37, 0x07, 0x73, 0x50, 0x98, 0x00, 0xf2, 0x22, 0x91, 0x6a, 0x54, 0x79, 0x78, 0x41, 0xd6, 0xcf, 0xc5, 0xb7, 0x00, 0x88, 0x0b, 0xcf, 0xb0, 0x39, 0xfb, 0x00, 0x08, 0x49, 0x41, 0x5f, 0x3f, 0x03, 0x97, 0x21, 0xcb, 0xf9, 0xbe, 0xde, 0xbf, 0x1b, 0xd5, 0x53, 0x0a, 0x04, 0x8d, 0x9d, 0x5b, 0x08, 0xf4, 0x6b, 0xb6, 0x1e, 0x6e, 0x7f, 0xf6, 0xc9, 0x96, 0x12, 0x9b, 0x14, 0x9e, 0xf3, 0xa0, 0x2d, 0x02, 0x12, 0xab, 0xc1, 0x3e, 0x84, 0x05, 0xa3, 0x3f, 0xcb, 0x4c, 0x43, 0x77, 0x43, 0xf8, 0x65, 0x20, 0x37, 0xf1, 0x4b, 0x67, 0x0e, 0x8d, 0x57, 0x42, 0xea, 0x26, 0x33, 0xa6, 0xec, 0x5e, 0x42, 0x37 }; +static const unsigned char vec_enc_secshares[3][32] = { + { 0x68, 0xca, 0x3b, 0x7a, 0x83, 0x9b, 0x9b, 0x04, 0x89, 0xb8, 0x12, 0x02, 0x89, 0x09, 0xff, 0xcc, 0x76, 0xc2, 0x84, 0xa2, 0x51, 0xbe, 0xfe, 0x77, 0x3f, 0x0c, 0x91, 0x7f, 0x2c, 0x59, 0xf0, 0x7e }, + { 0x44, 0x22, 0xc3, 0xdc, 0xd5, 0x91, 0x0d, 0x5c, 0x99, 0x4e, 0xcc, 0x29, 0x5d, 0xa3, 0x7e, 0xa5, 0x3f, 0x61, 0x75, 0x24, 0x05, 0x87, 0x79, 0x5b, 0x12, 0xda, 0x75, 0x96, 0x18, 0x3a, 0xcf, 0x1c }, + { 0x98, 0x4d, 0x80, 0xb8, 0x1c, 0x7a, 0xe3, 0xe4, 0xe3, 0xf2, 0x24, 0x30, 0x6a, 0xf4, 0x97, 0x82, 0x97, 0x42, 0xe9, 0x0f, 0xff, 0x52, 0xde, 0xba, 0x86, 0x3d, 0x6f, 0x8e, 0xdb, 0xe8, 0x76, 0x26 }, +}; +static const unsigned char vec_enc_eq_input[268] = { 0x00, 0x00, 0x00, 0x02, 0x02, 0x4a, 0x73, 0xaa, 0xd2, 0xf5, 0x00, 0xc7, 0xca, 0x28, 0x24, 0x5e, 0xfa, 0xd5, 0x55, 0x93, 0x67, 0xcf, 0xfb, 0xaa, 0x01, 0x19, 0x31, 0xa6, 0x9c, 0xe4, 0x11, 0xb3, 0x8e, 0xa7, 0x3d, 0x3f, 0x52, 0x03, 0x14, 0x72, 0x50, 0xbc, 0x22, 0x95, 0xb2, 0x46, 0x18, 0x25, 0x31, 0x7a, 0x10, 0x01, 0x12, 0x09, 0x32, 0xf4, 0xea, 0x0c, 0x8a, 0xaa, 0xba, 0xc3, 0x24, 0xa4, 0x35, 0x5f, 0x6e, 0xf2, 0x85, 0xb6, 0x03, 0x30, 0x45, 0x46, 0x1f, 0xd4, 0xb4, 0xe8, 0xbc, 0xd2, 0x88, 0x8b, 0xc0, 0x5d, 0x2f, 0x8c, 0x86, 0x10, 0x53, 0x43, 0x10, 0x2e, 0x42, 0x5f, 0x18, 0xae, 0x64, 0xe0, 0x03, 0x2a, 0xba, 0xe1, 0xe3, 0x03, 0x5c, 0x1f, 0xe2, 0x1b, 0xce, 0x62, 0x99, 0x2a, 0xe0, 0x66, 0x67, 0x7e, 0xc0, 0x4f, 0x68, 0xfc, 0x1b, 0x25, 0x79, 0x5f, 0x1a, 0x33, 0x62, 0x9a, 0xfb, 0x00, 0x24, 0x90, 0x60, 0x02, 0x85, 0x37, 0x02, 0x19, 0xd6, 0xb1, 0x40, 0xab, 0x84, 0xa2, 0x5a, 0x49, 0x38, 0xcd, 0x62, 0xb2, 0x63, 0xd1, 0xd9, 0x85, 0x5e, 0xa4, 0x1e, 0x1e, 0xbc, 0x19, 0x56, 0x85, 0x47, 0x7f, 0xed, 0xd6, 0xd6, 0xa1, 0x38, 0x02, 0xb4, 0x37, 0x07, 0x73, 0x50, 0x98, 0x00, 0xf2, 0x22, 0x91, 0x6a, 0x54, 0x79, 0x78, 0x41, 0xd6, 0xcf, 0xc5, 0xb7, 0x00, 0x88, 0x0b, 0xcf, 0xb0, 0x39, 0xfb, 0x00, 0x08, 0x49, 0x41, 0x5f, 0x3f, 0x03, 0x97, 0x21, 0xcb, 0xf9, 0xbe, 0xde, 0xbf, 0x1b, 0xd5, 0x53, 0x0a, 0x04, 0x8d, 0x9d, 0x5b, 0x08, 0xf4, 0x6b, 0xb6, 0x1e, 0x6e, 0x7f, 0xf6, 0xc9, 0x96, 0x12, 0x9b, 0x14, 0x9e, 0xf3, 0xa0, 0x2d, 0x02, 0x12, 0xab, 0xc1, 0x3e, 0x84, 0x05, 0xa3, 0x3f, 0xcb, 0x4c, 0x43, 0x77, 0x43, 0xf8, 0x65, 0x20, 0x37, 0xf1, 0x4b, 0x67, 0x0e, 0x8d, 0x57, 0x42, 0xea, 0x26, 0x33, 0xa6, 0xec, 0x5e, 0x42, 0x37 }; +static const unsigned char vec_enc_out_secshares[3][32] = { + { 0xbf, 0x52, 0xb6, 0x81, 0xfb, 0xea, 0x1b, 0x4a, 0xde, 0x2d, 0xe5, 0x45, 0xae, 0x80, 0xf8, 0x79, 0x5a, 0x86, 0x65, 0x85, 0xe2, 0x54, 0xaa, 0x25, 0xf6, 0xf0, 0x38, 0x80, 0x71, 0x82, 0x83, 0x11 }, + { 0x7d, 0x86, 0xbe, 0x5d, 0x04, 0x99, 0xac, 0x5a, 0x02, 0x18, 0x6c, 0xef, 0xf3, 0x26, 0x2f, 0xef, 0x95, 0x8e, 0x71, 0x43, 0xb6, 0xf0, 0x48, 0xa0, 0xc6, 0x13, 0x77, 0x3b, 0xb1, 0x8d, 0xc8, 0xae }, + { 0x3b, 0xba, 0xc6, 0x38, 0x0d, 0x49, 0x3d, 0x69, 0x26, 0x02, 0xf4, 0x9a, 0x37, 0xcb, 0x67, 0x65, 0xd0, 0x96, 0x7d, 0x01, 0x8b, 0x8b, 0xe7, 0x1b, 0x95, 0x36, 0xb5, 0xf6, 0xf1, 0x99, 0x0e, 0x4b }, +}; +static const unsigned char vec_enc_thresh_pk[33] = { 0x03, 0xba, 0x4f, 0x2c, 0x1a, 0x9d, 0x2c, 0xf5, 0x36, 0x9d, 0x7b, 0xd4, 0xff, 0x32, 0x8c, 0x76, 0x92, 0xce, 0x87, 0x32, 0x9e, 0x88, 0x00, 0x05, 0x7b, 0x31, 0xf7, 0x09, 0xd0, 0xa8, 0x88, 0x48, 0x22 }; +static const unsigned char vec_enc_pubshares[3][33] = { + { 0x03, 0xa9, 0x45, 0xce, 0x22, 0xcd, 0x1c, 0x4d, 0x2c, 0x67, 0x24, 0xdb, 0xc1, 0x76, 0x77, 0xe8, 0xd7, 0x38, 0x8b, 0x6f, 0x7c, 0x74, 0x2f, 0xa1, 0x5a, 0x15, 0x76, 0x82, 0xa4, 0x9f, 0xca, 0x56, 0x45 }, + { 0x02, 0xf4, 0x8a, 0xc7, 0x32, 0x0e, 0xb3, 0x40, 0x52, 0x79, 0xa8, 0x63, 0xdf, 0x81, 0xb2, 0x6c, 0x44, 0xe9, 0x5b, 0x0c, 0x1f, 0x7d, 0xe2, 0x8c, 0x7a, 0x68, 0xa8, 0xa8, 0x01, 0x7f, 0x6c, 0xee, 0xf3 }, + { 0x03, 0x2b, 0xf1, 0xf8, 0xdf, 0x4a, 0x28, 0x18, 0xe5, 0xbf, 0xde, 0x30, 0xbe, 0xb0, 0xa9, 0x2e, 0x2e, 0xa2, 0xb0, 0x79, 0x92, 0xab, 0xeb, 0xd5, 0x14, 0xdd, 0x54, 0x44, 0x2e, 0xb7, 0x12, 0xd8, 0xb9 }, +}; +/* encpedpop n = 3, t = 2 */ +/* all python-side sanity checks passed */ + /* The group order n and (2^256 - 1) mod n, for scalar parsing edge cases. */ static const unsigned char vec_scalar_order_n[32] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -513,6 +601,363 @@ static void chilldkg_vss_test(void) { secp256k1_scalar_clear(&tweak); } +static void chilldkg_simplpedpop_test(void) { + const uint32_t n = 3, t = 2; + const size_t pmsg_len = 33 * 2 + 64; + const size_t cmsg_len = 97 * 3 + 33 * 1; + const size_t eq_len = 4 + 33 * 2; + secp256k1_chilldkg_simplpedpop_participant_state states[3]; + secp256k1_scalar shares[3][3]; /* shares[dealer][recipient] */ + secp256k1_scalar partials[3]; + secp256k1_scalar secshare, secshare_tweaked; + secp256k1_ge pubshare; + unsigned char pmsgs[3][130]; + const unsigned char *pmsg_ptrs[3]; + unsigned char cmsg[324]; + unsigned char cmsg_bad[324]; + unsigned char eq_input[70]; + unsigned char buf32[32]; + secp256k1_chilldkg_simplpedpop_dkg_output dkg_outputs[3]; + secp256k1_chilldkg_simplpedpop_dkg_output coord_dkg; + secp256k1_chilldkg_fault fault; + uint32_t fault_index = 0; + uint32_t i, j; + + /* participant_step1: messages are byte-exact against the reference. */ + for (i = 0; i < n; i++) { + CHECK(secp256k1_chilldkg_simplpedpop_participant_step1(CTX, &states[i], pmsgs[i], shares[i], vec_simpl_seeds[i], t, n, i, vec_simpl_auxs[i]) == 1); + CHECK(secp256k1_memcmp_var(pmsgs[i], vec_simpl_pmsgs[i], pmsg_len) == 0); + pmsg_ptrs[i] = pmsgs[i]; + } + /* Invalid inputs are rejected: participant_id >= n, t > n, t == 0, n + * exceeding the participant cap. */ + CHECK(secp256k1_chilldkg_simplpedpop_participant_step1(CTX, &states[0], pmsgs[0], shares[0], vec_simpl_seeds[0], t, n, n, vec_simpl_auxs[0]) == 0); + CHECK(secp256k1_chilldkg_simplpedpop_participant_step1(CTX, &states[0], pmsgs[0], shares[0], vec_simpl_seeds[0], n + 1, n, 0, vec_simpl_auxs[0]) == 0); + CHECK(secp256k1_chilldkg_simplpedpop_participant_step1(CTX, &states[0], pmsgs[0], shares[0], vec_simpl_seeds[0], 0, n, 0, vec_simpl_auxs[0]) == 0); + CHECK(secp256k1_chilldkg_simplpedpop_participant_step1(CTX, &states[0], pmsgs[0], shares[0], vec_simpl_seeds[0], t, SECP256K1_CHILLDKG_MAX_PARTICIPANTS + 1, 0, vec_simpl_auxs[0]) == 0); + + /* coordinator_step: cmsg, eq_input and the DKG output are byte-exact. */ + fault = secp256k1_chilldkg_simplpedpop_coordinator_step(CTX, cmsg, &coord_dkg, eq_input, &fault_index, pmsg_ptrs, t, n); + CHECK(fault == SECP256K1_CHILLDKG_SUCCESS); + CHECK(secp256k1_memcmp_var(cmsg, vec_simpl_cmsg, cmsg_len) == 0); + CHECK(secp256k1_memcmp_var(eq_input, vec_simpl_eq_input, eq_len) == 0); + CHECK(secp256k1_memcmp_var(coord_dkg.thresh_pk33, vec_simpl_thresh_pk, 33) == 0); + for (j = 0; j < 32; j++) { + CHECK(coord_dkg.secshare32[j] == 0); + } + for (i = 0; i < n; i++) { + CHECK(secp256k1_memcmp_var(coord_dkg.pubshares33[i], vec_simpl_pubshares[i], 33) == 0); + } + + /* participant_step2 for all participants (happy path). */ + for (i = 0; i < n; i++) { + unsigned char eq_input_p[70]; + for (j = 0; j < n; j++) { + partials[j] = shares[j][i]; + } + secp256k1_chilldkg_simplpedpop_participant_step2_prepare_secshare(&secshare, partials, n); + secp256k1_scalar_get_b32(buf32, &secshare); + CHECK(secp256k1_memcmp_var(buf32, vec_simpl_untweaked_secshares[i], 32) == 0); + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[i], eq_input_p, &fault_index, &states[i], cmsg, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_SUCCESS); + CHECK(secp256k1_memcmp_var(eq_input_p, vec_simpl_eq_input, eq_len) == 0); + CHECK(secp256k1_memcmp_var(dkg_outputs[i].secshare32, vec_simpl_secshares[i], 32) == 0); + CHECK(secp256k1_memcmp_var(dkg_outputs[i].thresh_pk33, vec_simpl_thresh_pk, 33) == 0); + for (j = 0; j < n; j++) { + CHECK(secp256k1_memcmp_var(dkg_outputs[i].pubshares33[j], vec_simpl_pubshares[j], 33) == 0); + } + /* The tweaked secshare verifies against the tweaked own pubshare. */ + secp256k1_scalar_set_b32(&secshare_tweaked, dkg_outputs[i].secshare32, NULL); + CHECK(secp256k1_chilldkg_point_load(&pubshare, dkg_outputs[i].pubshares33[i]) == 1); + CHECK(secp256k1_chilldkg_vss_verify_secshare(CTX, &secshare_tweaked, &pubshare) == 1); + } + + /* Fault cases (participant 0's view; the untweaked secshare of + * participant 0 is still in `secshare`). */ + for (j = 0; j < n; j++) { + partials[j] = shares[j][0]; + } + secp256k1_chilldkg_simplpedpop_participant_step2_prepare_secshare(&secshare, partials, n); + + /* The coordinator echoes a wrong commitment to our secret. */ + memcpy(cmsg_bad, cmsg, cmsg_len); + { + unsigned char swap[33]; + memcpy(swap, cmsg_bad, 33); + memcpy(cmsg_bad, cmsg_bad + 33, 33); + memcpy(cmsg_bad + 33, swap, 33); + } + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg_bad, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_COORDINATOR); + + /* A tampered pop blames the corresponding participant (or the + * coordinator). */ + memcpy(cmsg_bad, cmsg, cmsg_len); + cmsg_bad[33 * 3 + 33 * 1 + 64 * 1] ^= 1; + fault_index = 0; + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg_bad, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == 1); + + /* A pop valid for participant 2 fails at the position of participant 1 + * (wrong signer for this index). */ + memcpy(cmsg_bad, cmsg, cmsg_len); + { + unsigned char swap[64]; + memcpy(swap, cmsg_bad + 33 * 3 + 33 + 64, 64); + memcpy(cmsg_bad + 33 * 3 + 33 + 64, cmsg_bad + 33 * 3 + 33 + 2 * 64, 64); + memcpy(cmsg_bad + 33 * 3 + 33 + 2 * 64, swap, 64); + } + fault_index = 0; + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg_bad, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == 1); + + /* A point at infinity as commitment to the secret of participant 2. */ + memcpy(cmsg_bad, cmsg, cmsg_len); + memset(cmsg_bad + 33 * 2, 0, 33); + fault_index = 0; + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg_bad, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == 2); + + /* A tampered sum of the commitments to the non-constant terms (flipping + * the prefix byte negates the point, which stays valid) makes the + * secshare verification fail: the fault cannot be attributed without the + * investigation procedure. */ + memcpy(cmsg_bad, cmsg, cmsg_len); + cmsg_bad[33 * 3] ^= 1; + fault_index = 0; + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg_bad, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == UINT32_MAX); + + /* A malformed cmsg (wrong length, invalid point) blames the coordinator. */ + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg, cmsg_len - 1, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_COORDINATOR); + memcpy(cmsg_bad, cmsg, cmsg_len); + cmsg_bad[0] = 0x04; + fault = secp256k1_chilldkg_simplpedpop_participant_step2(CTX, &dkg_outputs[0], eq_input, &fault_index, &states[0], cmsg_bad, cmsg_len, &secshare); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_COORDINATOR); + + /* The coordinator blames the sender of a malformed participant message. */ + { + unsigned char pmsg_bad[130]; + const unsigned char *pmsg_bad_ptrs[3]; + pmsg_bad_ptrs[0] = pmsg_ptrs[0]; + pmsg_bad_ptrs[1] = pmsg_bad; + pmsg_bad_ptrs[2] = pmsg_ptrs[2]; + memcpy(pmsg_bad, pmsgs[1], pmsg_len); + pmsg_bad[0] = 0x04; /* invalid point in the commitment */ + fault_index = 0; + fault = secp256k1_chilldkg_simplpedpop_coordinator_step(CTX, cmsg_bad, &coord_dkg, eq_input, &fault_index, pmsg_bad_ptrs, t, n); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT); + CHECK(fault_index == 1); + /* Invalid session parameters are input errors. */ + fault = secp256k1_chilldkg_simplpedpop_coordinator_step(CTX, cmsg_bad, &coord_dkg, eq_input, &fault_index, pmsg_ptrs, n + 1, n); + CHECK(fault == SECP256K1_CHILLDKG_INVALID_INPUT); + } + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + secp256k1_scalar_clear(&shares[i][j]); + } + secp256k1_scalar_clear(&partials[i]); + } + secp256k1_scalar_clear(&secshare); + secp256k1_scalar_clear(&secshare_tweaked); +} + +static void chilldkg_encpedpop_test(void) { + const uint32_t n = 3, t = 2; + const size_t pmsg_len = 33 * 2 + 64 + 33 + 32 * 3; + const size_t simpl_cmsg_len = 97 * 3 + 33 * 1; + const size_t cmsg_len = simpl_cmsg_len + 33 * 3; + const size_t eq_len = 4 + 33 * 2 + 33 * 3 + 33 * 3; + secp256k1_chilldkg_encpedpop_participant_state states[3]; + secp256k1_scalar enc_secshares[3]; + secp256k1_scalar pads_send[3]; + secp256k1_scalar pads_recv[3]; + secp256k1_scalar scalar_tmp, scalar_tmp2; + secp256k1_ge pubshare; + unsigned char pmsgs[3][259]; + const unsigned char *pmsg_ptrs[3]; + unsigned char cmsg[423]; + unsigned char cmsg_bad[423]; + unsigned char eq_input[268]; + unsigned char enc_context[4 + 33 * 3]; + unsigned char buf32[32]; + secp256k1_chilldkg_simplpedpop_dkg_output dkg_output; + secp256k1_chilldkg_simplpedpop_dkg_output coord_dkg; + secp256k1_chilldkg_fault fault; + uint32_t fault_index = 0; + uint32_t i, j; + int overflow; + + /* serialize_enc_context: u32be(t) || host public keys. */ + secp256k1_chilldkg_encpedpop_serialize_enc_context(enc_context, t, &vec_enc_hostpubkeys[0][0], n); + CHECK(secp256k1_memcmp_var(enc_context, vec_enc_context, sizeof(enc_context)) == 0); + + /* participant_step1: messages (and thus the derivations of simpl_seed, + * secnonce, pubnonce, shares and pads) are byte-exact against the + * reference. */ + for (i = 0; i < n; i++) { + CHECK(secp256k1_chilldkg_encpedpop_participant_step1(CTX, &states[i], pmsgs[i], vec_enc_hostseckeys[i], vec_enc_hostseckeys[i], &vec_enc_hostpubkeys[0][0], t, i, vec_enc_randoms[i], n) == 1); + CHECK(secp256k1_memcmp_var(pmsgs[i], vec_enc_pmsgs[i], pmsg_len) == 0); + CHECK(secp256k1_memcmp_var(states[i].pubnonce33, vec_enc_pubnonces[i], 33) == 0); + pmsg_ptrs[i] = pmsgs[i]; + } + /* Invalid inputs are rejected. */ + CHECK(secp256k1_chilldkg_encpedpop_participant_step1(CTX, &states[0], pmsgs[0], vec_enc_hostseckeys[0], vec_enc_hostseckeys[0], &vec_enc_hostpubkeys[0][0], t, n, vec_enc_randoms[0], n) == 0); + CHECK(secp256k1_chilldkg_encpedpop_participant_step1(CTX, &states[0], pmsgs[0], vec_enc_hostseckeys[0], vec_enc_hostseckeys[0], &vec_enc_hostpubkeys[0][0], n + 1, 0, vec_enc_randoms[0], n) == 0); + + /* Pad symmetry: the pad computed by sender 0 for recipient i equals the + * pad computed by recipient i for sender 0 (for the own index both sides + * use the symmetric self pad). */ + secp256k1_scalar_set_b32(&scalar_tmp, vec_enc_secnonces[0], &overflow); + CHECK(!overflow); + CHECK(secp256k1_chilldkg_encpedpop_encaps_multi(CTX, pads_send, &scalar_tmp, vec_enc_pubnonces[0], vec_enc_hostseckeys[0], &vec_enc_hostpubkeys[0][0], enc_context, sizeof(enc_context), 0, n) == 1); + for (i = 0; i < n; i++) { + fault = secp256k1_chilldkg_encpedpop_decaps_multi(CTX, pads_recv, &fault_index, vec_enc_hostseckeys[i], vec_enc_hostpubkeys[i], &vec_enc_pubnonces[0][0], enc_context, sizeof(enc_context), i, n); + CHECK(fault == SECP256K1_CHILLDKG_SUCCESS); + CHECK(secp256k1_scalar_eq(&pads_send[i], &pads_recv[0])); + } + /* decaps_multi rejects an invalid own host secret key. */ + memset(buf32, 0, 32); + fault = secp256k1_chilldkg_encpedpop_decaps_multi(CTX, pads_recv, &fault_index, buf32, vec_enc_hostpubkeys[0], &vec_enc_pubnonces[0][0], enc_context, sizeof(enc_context), 0, n); + CHECK(fault == SECP256K1_CHILLDKG_INVALID_INPUT); + + /* encrypt -> coordinator sum -> decrypt_sum roundtrip with deterministic + * plaintexts (plaintext from sender i to recipient j is 3*i + j + 1). */ + for (j = 0; j < n; j++) { + secp256k1_scalar_set_int(&enc_secshares[j], 0); + } + for (i = 0; i < n; i++) { + secp256k1_scalar plaintexts[3]; + secp256k1_scalar ciphertexts[3]; + secp256k1_scalar_set_b32(&scalar_tmp, vec_enc_secnonces[i], &overflow); + CHECK(!overflow); + for (j = 0; j < n; j++) { + secp256k1_scalar_set_int(&plaintexts[j], 3 * i + j + 1); + } + CHECK(secp256k1_chilldkg_encpedpop_encrypt_multi(CTX, ciphertexts, &scalar_tmp, vec_enc_pubnonces[i], vec_enc_hostseckeys[i], &vec_enc_hostpubkeys[0][0], enc_context, sizeof(enc_context), i, plaintexts, n) == 1); + for (j = 0; j < n; j++) { + secp256k1_scalar_add(&enc_secshares[j], &enc_secshares[j], &ciphertexts[j]); + secp256k1_scalar_clear(&plaintexts[j]); + secp256k1_scalar_clear(&ciphertexts[j]); + } + } + for (j = 0; j < n; j++) { + fault = secp256k1_chilldkg_encpedpop_decrypt_sum(CTX, &scalar_tmp, &fault_index, vec_enc_hostseckeys[j], vec_enc_hostpubkeys[j], &vec_enc_pubnonces[0][0], enc_context, sizeof(enc_context), j, &enc_secshares[j], n); + CHECK(fault == SECP256K1_CHILLDKG_SUCCESS); + secp256k1_scalar_set_int(&scalar_tmp2, 0); + for (i = 0; i < n; i++) { + secp256k1_scalar p; + secp256k1_scalar_set_int(&p, 3 * i + j + 1); + secp256k1_scalar_add(&scalar_tmp2, &scalar_tmp2, &p); + } + CHECK(secp256k1_scalar_eq(&scalar_tmp, &scalar_tmp2)); + } + + /* coordinator_step: cmsg, eq_input, summed encrypted shares and the DKG + * output are byte-exact. */ + fault = secp256k1_chilldkg_encpedpop_coordinator_step(CTX, cmsg, &coord_dkg, eq_input, enc_secshares, &fault_index, pmsg_ptrs, t, &vec_enc_hostpubkeys[0][0], n); + CHECK(fault == SECP256K1_CHILLDKG_SUCCESS); + CHECK(secp256k1_memcmp_var(cmsg, vec_enc_cmsg, cmsg_len) == 0); + CHECK(secp256k1_memcmp_var(eq_input, vec_enc_eq_input, eq_len) == 0); + for (i = 0; i < n; i++) { + secp256k1_scalar_get_b32(buf32, &enc_secshares[i]); + CHECK(secp256k1_memcmp_var(buf32, vec_enc_secshares[i], 32) == 0); + CHECK(secp256k1_memcmp_var(coord_dkg.pubshares33[i], vec_enc_pubshares[i], 33) == 0); + } + CHECK(secp256k1_memcmp_var(coord_dkg.thresh_pk33, vec_enc_thresh_pk, 33) == 0); + + /* participant_step2 for all participants (happy path): the decrypted + * (tweaked) secshare matches the pubshare, and all outputs are + * byte-exact. */ + for (i = 0; i < n; i++) { + unsigned char eq_input_p[268]; + fault = secp256k1_chilldkg_encpedpop_participant_step2(CTX, &dkg_output, eq_input_p, &fault_index, &states[i], vec_enc_hostseckeys[i], cmsg, cmsg_len, &enc_secshares[i]); + CHECK(fault == SECP256K1_CHILLDKG_SUCCESS); + CHECK(secp256k1_memcmp_var(eq_input_p, vec_enc_eq_input, eq_len) == 0); + CHECK(secp256k1_memcmp_var(dkg_output.secshare32, vec_enc_out_secshares[i], 32) == 0); + CHECK(secp256k1_memcmp_var(dkg_output.thresh_pk33, vec_enc_thresh_pk, 33) == 0); + for (j = 0; j < n; j++) { + CHECK(secp256k1_memcmp_var(dkg_output.pubshares33[j], vec_enc_pubshares[j], 33) == 0); + } + secp256k1_scalar_set_b32(&scalar_tmp, dkg_output.secshare32, NULL); + CHECK(secp256k1_chilldkg_point_load(&pubshare, dkg_output.pubshares33[i]) == 1); + CHECK(secp256k1_chilldkg_vss_verify_secshare(CTX, &scalar_tmp, &pubshare) == 1); + } + + /* Fault cases (participant 0's view). */ + + /* The coordinator echoes a wrong pubnonce for us. */ + memcpy(cmsg_bad, cmsg, cmsg_len); + cmsg_bad[simpl_cmsg_len] ^= 1; + fault = secp256k1_chilldkg_encpedpop_participant_step2(CTX, &dkg_output, eq_input, &fault_index, &states[0], vec_enc_hostseckeys[0], cmsg_bad, cmsg_len, &enc_secshares[0]); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_COORDINATOR); + + /* An invalid pubnonce of sender 1 (bad encoding, or the infinity + * encoding) blames the sender or the coordinator. */ + memcpy(cmsg_bad, cmsg, cmsg_len); + memset(cmsg_bad + simpl_cmsg_len + 33, 0xff, 33); + fault_index = 0; + fault = secp256k1_chilldkg_encpedpop_participant_step2(CTX, &dkg_output, eq_input, &fault_index, &states[0], vec_enc_hostseckeys[0], cmsg_bad, cmsg_len, &enc_secshares[0]); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == 1); + memcpy(cmsg_bad, cmsg, cmsg_len); + memset(cmsg_bad + simpl_cmsg_len + 33, 0, 33); + fault_index = 0; + fault = secp256k1_chilldkg_encpedpop_participant_step2(CTX, &dkg_output, eq_input, &fault_index, &states[0], vec_enc_hostseckeys[0], cmsg_bad, cmsg_len, &enc_secshares[0]); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == 1); + + /* A tampered encrypted secshare decrypts to a wrong secshare: the fault + * cannot be attributed without the investigation procedure. */ + secp256k1_scalar_set_int(&scalar_tmp2, 1); + secp256k1_scalar_add(&scalar_tmp, &enc_secshares[0], &scalar_tmp2); + fault_index = 0; + fault = secp256k1_chilldkg_encpedpop_participant_step2(CTX, &dkg_output, eq_input, &fault_index, &states[0], vec_enc_hostseckeys[0], cmsg, cmsg_len, &scalar_tmp); + CHECK(fault == SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR); + CHECK(fault_index == UINT32_MAX); + + /* A malformed cmsg (wrong length) blames the coordinator. */ + fault = secp256k1_chilldkg_encpedpop_participant_step2(CTX, &dkg_output, eq_input, &fault_index, &states[0], vec_enc_hostseckeys[0], cmsg, cmsg_len - 1, &enc_secshares[0]); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_COORDINATOR); + + /* The coordinator blames the sender of a malformed participant message: + * an encrypted share that overflows the group order, or an invalid + * commitment. */ + { + unsigned char pmsg_bad[259]; + const unsigned char *pmsg_bad_ptrs[3]; + pmsg_bad_ptrs[0] = pmsg_ptrs[0]; + pmsg_bad_ptrs[1] = pmsg_bad; + pmsg_bad_ptrs[2] = pmsg_ptrs[2]; + memcpy(pmsg_bad, pmsgs[1], pmsg_len); + memset(pmsg_bad + 33 * 2 + 64 + 33, 0xff, 32); /* share for recipient 0 */ + fault_index = 0; + fault = secp256k1_chilldkg_encpedpop_coordinator_step(CTX, cmsg_bad, &coord_dkg, eq_input, enc_secshares, &fault_index, pmsg_bad_ptrs, t, &vec_enc_hostpubkeys[0][0], n); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT); + CHECK(fault_index == 1); + memcpy(pmsg_bad, pmsgs[1], pmsg_len); + pmsg_bad[33] = 0x07; /* invalid prefix of the second commitment entry */ + fault_index = 0; + fault = secp256k1_chilldkg_encpedpop_coordinator_step(CTX, cmsg_bad, &coord_dkg, eq_input, enc_secshares, &fault_index, pmsg_bad_ptrs, t, &vec_enc_hostpubkeys[0][0], n); + CHECK(fault == SECP256K1_CHILLDKG_FAULTY_PARTICIPANT); + CHECK(fault_index == 1); + } + + secp256k1_scalar_clear(&scalar_tmp); + secp256k1_scalar_clear(&scalar_tmp2); + for (i = 0; i < n; i++) { + secp256k1_scalar_clear(&enc_secshares[i]); + secp256k1_scalar_clear(&pads_send[i]); + secp256k1_scalar_clear(&pads_recv[i]); + } +} + static const struct tf_test_entry tests_chilldkg[] = { CASE1(chilldkg_tagged_hashes_test), CASE1(chilldkg_params_hash_test), @@ -521,6 +966,8 @@ static const struct tf_test_entry tests_chilldkg[] = { CASE1(chilldkg_schnorrsig_test), CASE1(chilldkg_ecdh_pad_test), CASE1(chilldkg_vss_test), + CASE1(chilldkg_simplpedpop_test), + CASE1(chilldkg_encpedpop_test), }; #endif diff --git a/src/modules/chilldkg/util.h b/src/modules/chilldkg/util.h index 21689497..219078d4 100644 --- a/src/modules/chilldkg/util.h +++ b/src/modules/chilldkg/util.h @@ -12,6 +12,32 @@ #include "../../hash.h" #include "../../scalar.h" +/* Maximum number of participants (and thus the maximum threshold) supported by + * this module. The state objects of the module are fixed-size and do not use + * dynamic allocation, so a compile-time cap is required. This matches the + * frost module's convention; the public header will expose the same constant + * in a later phase. */ +#define SECP256K1_CHILLDKG_MAX_PARTICIPANTS 128 + +/* Return codes of the internal SimplPedPop/EncPedPop functions, mirroring the + * exception taxonomy of chilldkg_ref/util.py. The public API will expose its + * own enum in a later phase. + * + * For SECP256K1_CHILLDKG_FAULTY_PARTICIPANT and + * SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR, the fault_index output + * of the failing function receives the index of the (suspected) faulty + * participant. For SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR + * the faulty party cannot be identified without the investigation procedure + * (a later phase); fault_index is set to UINT32_MAX. */ +typedef enum { + SECP256K1_CHILLDKG_SUCCESS = 0, + SECP256K1_CHILLDKG_FAULTY_COORDINATOR, + SECP256K1_CHILLDKG_FAULTY_PARTICIPANT, + SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR, + SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR, + SECP256K1_CHILLDKG_INVALID_INPUT +} secp256k1_chilldkg_fault; + /* This file contains the internal primitives of the ChillDKG module that * mirror chilldkg_ref/util.py, secp256k1lab/bip340.py and secp256k1lab/ecdh.py * of the bip-frost-dkg reference implementation. Byte-exactness with the