chilldkg: Phase 1 - internal primitives (util, vss)

Add the byte-exact internal primitives for the ChillDKG module,
mirroring the Python reference implementation of the bip-frost-dkg
draft (v0.3.0-dev), pinned to upstream commit
a91896883f85b159415ecf298d5e844879af112d.

util.h / util_impl.h (mirrors chilldkg_ref/util.py):
- Point (de)serialization with explicit point-at-infinity support:
  33 zero bytes <-> infinity, otherwise SEC compressed. Checked parse
  rejects invalid encodings and out-of-range x coordinates
  (point_save/point_load, xonly_save/xonly_load).
- Internal parameterized-tag BIP-340 Schnorr sign/verify
  (chilldkg_schnorrsig_sign/_verify): tag prefix selects the
  <prefix>/aux, /nonce, /challenge subtags ("BIP DKG/pop message" for
  proofs of possession, "BIP0340" for CertEq signatures and recovery
  acks), arbitrary-length messages, pad33 zero-padding helper. The
  public schnorrsig API hardcodes BIP0340/32-byte messages, so the
  algorithm is replicated from secp256k1_schnorrsig_sign_internal with
  a custom tag; cross-checked against secp256k1_schnorrsig_sign32.
- Tagged hashes via secp256k1_sha256_initialize_tagged:
  "BIP DKG/params_hash", "BIP DKG/encpedpop seed",
  "BIP DKG/simplpedpop aux", "BIP DKG/encpedpop secnonce",
  "BIP DKG/encpedpop ecdh", "BIP DKG/encaps_multi self_pad",
  "BIP DKG/vss coeffs", and BIP-341 "TapTweak" (32-byte x-only input).
- params_hash = TH("BIP DKG/params_hash", u32be(t) || hostpubkeys)
  (note: plan had the operand order reversed; the reference hashes t
  first).
- ECDH pads: reuses the ecdh module's SHA256-of-compressed-shared-
  point hash, then TH("BIP DKG/encpedpop ecdh", ecdh ||
  sender_pubnonce || receiver_hostpubkey || context) with a sending
  flag fixing the sender|receiver order; self_pad for the own index.
  Pads are parsed wrapping (mod-n reduction); wire scalars, VSS
  coefficients and the TapTweak are parsed checked.

vss.h / vss_impl.h (mirrors chilldkg_ref/vss.py):
- vss_gen_coeffs: per-coefficient TH("BIP DKG/vss coeffs", seed ||
  u32be(j)), checked parse with bitwise error accumulation.
- vss_poly_eval (Horner) and vss_secshare_for with the x = id+1
  convention (safe at UINT32_MAX).
- vss_commit (constant-time ecmult_gen, zero coefficient -> infinity),
  vss_pubshare (powers-of-x over commitments, skips infinity),
  vss_commitment_add, vss_verify_secshare.
- vss_invalid_taproot_commit: TapTweak applied to the x-only constant
  term so the Taproot script path is unspendable; returns tweak and
  pubtweak.

tests_impl.h: 7 vector tests (tagged hashes, params_hash, point
serialization incl. infinity roundtrip and parity prefixes, checked
vs wrapping scalar parse at the group order boundary, custom-tag
schnorrsig incl. wrong-tag/key/msg rejection, ECDH pad sender/receiver
symmetry, VSS coeff derivation/Horner/commitment/pubshare/tweak) with
expected values generated once from the Python reference
(committed into the test file, reference commit recorded).

Verified: make check 3/3 suites pass; CMake ctest all pass;
./tests --target=chilldkg runs all 7 new tests green in both verify
and noverify builds.
This commit is contained in:
Kgothatso Ngako
2026-08-31 04:05:15 +02:00
parent 49f3eba8f5
commit e462f7c1ac
7 changed files with 1246 additions and 6 deletions

View File

@@ -1,3 +1,7 @@
include_HEADERS += include/secp256k1_chilldkg.h
noinst_HEADERS += src/modules/chilldkg/main_impl.h
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/tests_impl.h

View File

@@ -8,4 +8,7 @@
#include "../../../include/secp256k1_chilldkg.h"
#include "util_impl.h"
#include "vss_impl.h"
#endif

View File

@@ -6,16 +6,521 @@
#ifndef SECP256K1_MODULE_CHILLDKG_TESTS_IMPL_H
#define SECP256K1_MODULE_CHILLDKG_TESTS_IMPL_H
#include "../../../include/secp256k1_chilldkg.h"
#include <string.h>
/* Placeholder test for the chilldkg module scaffolding. Real tests are added
* in later implementation phases. */
static void chilldkg_scaffolding_test(void) {
CHECK(1);
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_chilldkg.h"
#include "../../../include/secp256k1_schnorrsig.h"
#include "util.h"
#include "vss.h"
#include "../../group.h"
#include "../../hash.h"
#include "../../scalar.h"
#include "../../util.h"
/* All expected values below were generated from the Python reference
* implementation of bip-frost-dkg (chilldkg_ref and the vendored
* secp256k1lab), commit a91896883f85b159415ecf298d5e844879af112d, using the
* tagged hashes, serializations and algorithms of the reference directly. */
static const unsigned char vec_th_encpedpop_seed_msg[96] = { 0x5c, 0x6f, 0x26, 0x18, 0xc5, 0xcd, 0x99, 0xce, 0x44, 0xd0, 0x27, 0xf4, 0x24, 0xc5, 0x6b, 0xa7, 0x91, 0xd6, 0xe0, 0x01, 0xfd, 0xe7, 0xe4, 0x12, 0x09, 0x17, 0xed, 0x40, 0x0e, 0xc0, 0x7e, 0x4d, 0xe2, 0x41, 0xa0, 0x0a, 0x4d, 0xcf, 0x7e, 0xb2, 0x58, 0x47, 0xbd, 0x35, 0xc1, 0xb9, 0x24, 0x23, 0x9a, 0x25, 0xe2, 0x74, 0x87, 0xbd, 0x3b, 0x6a, 0xee, 0x45, 0xd3, 0x38, 0x4f, 0xe2, 0x09, 0x91, 0xe6, 0x81, 0x7d, 0x9a, 0x5e, 0xa8, 0x5f, 0x8f, 0xef, 0x9f, 0x92, 0x6e, 0x08, 0x27, 0xb8, 0xda, 0x3f, 0x99, 0xb4, 0xc8, 0xd3, 0xb5, 0xe2, 0x14, 0xdd, 0x82, 0x8f, 0x0c, 0x0c, 0xd1, 0x3d, 0x55 };
static const unsigned char vec_th_encpedpop_seed[32] = { 0xca, 0x51, 0x72, 0x9c, 0x93, 0x61, 0x90, 0x75, 0x11, 0xb0, 0x97, 0x44, 0x34, 0xfd, 0xde, 0x00, 0xac, 0x38, 0x50, 0xcf, 0x58, 0xef, 0x76, 0x4f, 0x72, 0x33, 0x75, 0x48, 0xc2, 0x6f, 0x56, 0xc0 };
static const unsigned char vec_th_simplpedpop_aux_msg[32] = { 0xc3, 0x10, 0x9e, 0x35, 0x1b, 0x35, 0xbb, 0xc9, 0x49, 0x73, 0x47, 0x8c, 0x63, 0xae, 0x47, 0xef, 0xda, 0xc1, 0x80, 0x8e, 0x0e, 0x7e, 0x66, 0x95, 0x0b, 0xe5, 0x2b, 0xa3, 0xb5, 0x40, 0x90, 0x17 };
static const unsigned char vec_th_simplpedpop_aux[32] = { 0x98, 0x2e, 0x55, 0xfb, 0x84, 0x1b, 0x61, 0x72, 0xbb, 0x63, 0xd5, 0xae, 0x6a, 0x0a, 0xa2, 0x7c, 0x17, 0x83, 0xa3, 0x98, 0x56, 0xe0, 0xaf, 0x60, 0x0b, 0x6e, 0x22, 0xb2, 0xe6, 0xa4, 0x8a, 0x78 };
static const unsigned char vec_th_encpedpop_secnonce_msg[32] = { 0xc5, 0xdf, 0x8c, 0x18, 0x85, 0x6b, 0xa3, 0xc4, 0xe8, 0xef, 0xd7, 0xe5, 0xf3, 0xf8, 0x34, 0x1b, 0x43, 0xd9, 0x31, 0xbe, 0x7b, 0xaf, 0xd9, 0xa0, 0x06, 0xc6, 0x82, 0x0b, 0xe6, 0x14, 0xce, 0xe3 };
static const unsigned char vec_th_encpedpop_secnonce[32] = { 0x52, 0xbb, 0x8a, 0x83, 0x6f, 0xb6, 0x4e, 0x46, 0x46, 0x79, 0x42, 0x77, 0xe4, 0xf4, 0x49, 0x77, 0x24, 0xbb, 0xbc, 0x0e, 0x5c, 0xaf, 0x20, 0x3b, 0x55, 0xa2, 0x79, 0x22, 0xa4, 0x45, 0x19, 0x3c };
static const unsigned char vec_th_encpedpop_ecdh_msg[110] = { 0x40, 0x2c, 0xc3, 0xde, 0x0d, 0xe6, 0x25, 0xf7, 0x8b, 0x9f, 0xf9, 0x69, 0xb6, 0x69, 0x7b, 0xae, 0x70, 0x2d, 0x2f, 0x0b, 0x91, 0x9c, 0x3f, 0x1f, 0xc9, 0x54, 0x18, 0x22, 0x95, 0xe0, 0x9a, 0x81, 0x52, 0x63, 0x4e, 0x58, 0xdc, 0xbb, 0x77, 0xe4, 0x63, 0xcd, 0x17, 0x94, 0xf4, 0x8b, 0xce, 0xa9, 0x31, 0xcd, 0xb8, 0xeb, 0xca, 0x85, 0x8b, 0xc3, 0x9c, 0x88, 0x34, 0x45, 0x90, 0x8f, 0x9c, 0x4a, 0xc7, 0x74, 0xf5, 0xe5, 0x3a, 0x1e, 0x8b, 0x59, 0x84, 0x97, 0x10, 0x14, 0x55, 0xa9, 0xfb, 0x2b, 0x00, 0xa4, 0x6c, 0xdf, 0x0a, 0x3b, 0x82, 0x68, 0xee, 0x05, 0xd0, 0x09, 0xdd, 0x82, 0x08, 0xbc, 0x2b, 0x12, 0x6d, 0x38, 0x24, 0xdf, 0x3d, 0xa5, 0xf2, 0x68, 0x3a, 0xd2, 0x64, 0x54 };
static const unsigned char vec_th_encpedpop_ecdh[32] = { 0x10, 0xf5, 0xeb, 0xfd, 0x78, 0x7e, 0x82, 0x6e, 0x9c, 0x3b, 0xf8, 0xdc, 0x19, 0x29, 0x04, 0xfb, 0x8d, 0xb0, 0xde, 0xf4, 0x70, 0x26, 0xac, 0xc5, 0xbd, 0x2e, 0xf0, 0xbd, 0xec, 0x92, 0xb9, 0xc9 };
static const unsigned char vec_th_encaps_multi_self_pad_msg[77] = { 0x04, 0x08, 0x06, 0x85, 0x44, 0xaa, 0x7c, 0xe9, 0xe9, 0x03, 0x9a, 0x13, 0x0d, 0x07, 0xfb, 0xe7, 0x17, 0xa9, 0x18, 0xe7, 0x77, 0x9b, 0x5f, 0xc1, 0x69, 0xc4, 0x8b, 0x51, 0xd7, 0x62, 0x39, 0xec, 0xf5, 0x6d, 0x5e, 0xc3, 0xaf, 0xcd, 0x66, 0x52, 0x27, 0x42, 0xf3, 0x37, 0xce, 0xa2, 0xda, 0x8a, 0x32, 0x2f, 0xd0, 0xf2, 0x00, 0xa6, 0x73, 0xfd, 0x85, 0xf5, 0xc5, 0xc4, 0xc9, 0x28, 0xe1, 0xc1, 0xe9, 0x43, 0xe3, 0x67, 0xf3, 0x59, 0x03, 0x87, 0x5b, 0xcf, 0x4a, 0xb6, 0x2a };
static const unsigned char vec_th_encaps_multi_self_pad[32] = { 0xf1, 0x7f, 0x82, 0x28, 0x50, 0x82, 0xda, 0x3c, 0x56, 0xf7, 0x3b, 0x18, 0xa5, 0x81, 0xaf, 0xff, 0x42, 0x7b, 0x89, 0x9e, 0xce, 0xf7, 0x92, 0x32, 0x8b, 0x85, 0x86, 0x53, 0x4e, 0x41, 0x2b, 0x87 };
static const unsigned char vec_th_vss_coeffs_msg[36] = { 0xc3, 0x7b, 0x8b, 0xd8, 0x94, 0x59, 0xf9, 0xc5, 0xb8, 0xcb, 0xc8, 0x4e, 0xe2, 0xd6, 0x68, 0x81, 0x3a, 0xcb, 0x36, 0x82, 0x3d, 0x51, 0xd9, 0x8a, 0xf7, 0xfe, 0xcb, 0xe9, 0x66, 0x00, 0x98, 0xdb, 0x67, 0x81, 0x41, 0x42 };
static const unsigned char vec_th_vss_coeffs[32] = { 0xb6, 0x63, 0x8b, 0x82, 0x4d, 0x68, 0xe0, 0xc7, 0x00, 0x63, 0xfb, 0xf2, 0x1a, 0x7d, 0xcd, 0x30, 0xbd, 0x3e, 0x47, 0xa2, 0x44, 0xb0, 0x9e, 0x60, 0x51, 0x15, 0x15, 0xf4, 0xc9, 0xcf, 0xcb, 0x89 };
static const unsigned char vec_taptweak_msg[32] = { 0xc6, 0x04, 0x7f, 0x94, 0x41, 0xed, 0x7d, 0x6d, 0x30, 0x45, 0x40, 0x6e, 0x95, 0xc0, 0x7c, 0xd8, 0x5c, 0x77, 0x8e, 0x4b, 0x8c, 0xef, 0x3c, 0xa7, 0xab, 0xac, 0x09, 0xb9, 0x5c, 0x70, 0x9e, 0xe5 };
static const unsigned char vec_taptweak[32] = { 0x04, 0xb2, 0x6d, 0xeb, 0xa6, 0x6b, 0x94, 0xdb, 0x39, 0x9b, 0xbf, 0x0c, 0x31, 0x02, 0x9d, 0x16, 0x8c, 0x5f, 0xeb, 0x56, 0x64, 0xcd, 0x7a, 0x21, 0x22, 0x48, 0x32, 0x21, 0x71, 0x34, 0x5a, 0xde };
static const unsigned char vec_params_hash_hostpubkeys[3][33] = {
{ 0x02, 0x0d, 0xce, 0x0a, 0x57, 0x0a, 0x10, 0x96, 0xce, 0x63, 0xdb, 0x07, 0xf2, 0xbe, 0x37, 0x28, 0x13, 0xbc, 0x92, 0xa1, 0xc4, 0xa0, 0xe0, 0x5a, 0x92, 0x94, 0xe8, 0x99, 0x9a, 0xb1, 0x46, 0xf0, 0xd2 },
{ 0x03, 0xfa, 0x40, 0x88, 0xaa, 0x3e, 0xd9, 0x4e, 0xdc, 0x40, 0x03, 0x59, 0x78, 0xca, 0xf0, 0x22, 0x99, 0x1c, 0x0f, 0xd3, 0xc4, 0xfc, 0xd0, 0x2e, 0x5b, 0xfb, 0x00, 0x7c, 0x3c, 0x44, 0xcb, 0xff, 0x06 },
{ 0x02, 0xcc, 0x4e, 0xe5, 0x03, 0x48, 0x9b, 0xf1, 0xbf, 0x88, 0xb0, 0xe6, 0xbf, 0x98, 0xc9, 0x44, 0xe6, 0x0b, 0x59, 0x4b, 0x45, 0x6a, 0xfa, 0x75, 0x10, 0xe2, 0x05, 0x16, 0xbd, 0xfd, 0xba, 0x87, 0xc0 },
};
static const unsigned char vec_params_hash[32] = { 0x99, 0x23, 0x3b, 0x82, 0x2a, 0xe9, 0x49, 0x24, 0xbd, 0x43, 0x1d, 0x4f, 0xad, 0x04, 0x47, 0x48, 0x8a, 0xc9, 0xf4, 0xbb, 0x9d, 0x8c, 0x21, 0x6e, 0x39, 0xdb, 0xab, 0x02, 0x15, 0xdd, 0x1e, 0x96 };
/* params_hash t = 2 */
static const unsigned char vec_point_0[33] = { 0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98 };
/* point_0 = 1*G, y odd = False */
static const unsigned char vec_point_1[33] = { 0x02, 0xc6, 0x04, 0x7f, 0x94, 0x41, 0xed, 0x7d, 0x6d, 0x30, 0x45, 0x40, 0x6e, 0x95, 0xc0, 0x7c, 0xd8, 0x5c, 0x77, 0x8e, 0x4b, 0x8c, 0xef, 0x3c, 0xa7, 0xab, 0xac, 0x09, 0xb9, 0x5c, 0x70, 0x9e, 0xe5 };
/* point_1 = 2*G, y odd = False */
static const unsigned char vec_point_2[33] = { 0x02, 0xf9, 0x30, 0x8a, 0x01, 0x92, 0x58, 0xc3, 0x10, 0x49, 0x34, 0x4f, 0x85, 0xf8, 0x9d, 0x52, 0x29, 0xb5, 0x31, 0xc8, 0x45, 0x83, 0x6f, 0x99, 0xb0, 0x86, 0x01, 0xf1, 0x13, 0xbc, 0xe0, 0x36, 0xf9 };
/* point_2 = 3*G, y odd = False */
static const unsigned char vec_pop_seckey[32] = { 0x8a, 0xda, 0x60, 0x76, 0xb8, 0xfd, 0x7c, 0x17, 0xf9, 0x3d, 0x04, 0xf5, 0x3e, 0xb9, 0x79, 0x9a, 0xfd, 0xdd, 0x0a, 0x52, 0x29, 0x16, 0xb1, 0xb3, 0x73, 0x6a, 0x85, 0xa4, 0xfc, 0x7c, 0x63, 0xca };
static const unsigned char vec_pop_aux[32] = { 0x18, 0xa6, 0x54, 0xb7, 0x17, 0xd3, 0xcd, 0xa8, 0xaf, 0x10, 0x7e, 0x9c, 0xbb, 0xbe, 0x35, 0x35, 0xd7, 0x51, 0x3a, 0xea, 0xda, 0x47, 0x65, 0xcb, 0x96, 0x58, 0x24, 0x5a, 0x75, 0x7b, 0x93, 0x97 };
static const unsigned char vec_pop_pubkey[32] = { 0x43, 0x20, 0xdf, 0x02, 0xf3, 0xa5, 0x13, 0xd3, 0xb0, 0x30, 0x1d, 0x23, 0x5f, 0xe3, 0xb7, 0x44, 0x84, 0xd1, 0xbe, 0xe1, 0xfb, 0x50, 0x22, 0xe5, 0x1c, 0x7f, 0x3c, 0xce, 0x24, 0x82, 0x60, 0xe9 };
static const unsigned char vec_pop_sig[64] = { 0x0c, 0xd9, 0x96, 0x33, 0x52, 0x7d, 0xa9, 0xec, 0x9b, 0xb2, 0x8d, 0xaa, 0x12, 0xb5, 0xf7, 0x41, 0x37, 0xa3, 0x62, 0xa2, 0xc9, 0x3b, 0x5c, 0x54, 0xbd, 0x98, 0x6b, 0x07, 0x0b, 0x4f, 0x46, 0xee, 0xe3, 0x32, 0xa2, 0xf0, 0x53, 0x99, 0xbb, 0x2f, 0xba, 0x2d, 0xe4, 0xd4, 0xe3, 0x8a, 0x1d, 0x1a, 0xaf, 0x2c, 0x3f, 0x48, 0xc0, 0xe2, 0xe8, 0x4b, 0x27, 0x81, 0x99, 0xdd, 0x87, 0x62, 0xb6, 0x87 };
/* pop participant_id = 5 */
static const unsigned char vec_certeq_seckey[32] = { 0x61, 0xf9, 0x3a, 0xd4, 0x0b, 0xd1, 0xe0, 0x97, 0x9e, 0xfc, 0x8f, 0x11, 0x99, 0xb1, 0x9b, 0xac, 0xee, 0xa2, 0x61, 0xcb, 0x9e, 0xe7, 0x36, 0x4b, 0x27, 0xde, 0xeb, 0x5d, 0xc7, 0x75, 0xc8, 0xd3 };
static const unsigned char vec_certeq_aux[32] = { 0x88, 0x3c, 0xa1, 0x82, 0x29, 0x39, 0x2a, 0x95, 0x8c, 0xff, 0x53, 0xd8, 0x72, 0x22, 0x0f, 0x73, 0xdd, 0x65, 0x2a, 0x6b, 0x54, 0xef, 0xc3, 0x1d, 0x6a, 0xfc, 0x64, 0x65, 0x58, 0x0d, 0x21, 0x37 };
static const unsigned char vec_certeq_eq_input[64] = { 0x62, 0x46, 0xc7, 0x4b, 0x5e, 0xdb, 0xf9, 0x07, 0xf0, 0x64, 0xb9, 0x8f, 0x97, 0x37, 0x63, 0x1e, 0x40, 0x7c, 0xd0, 0xb6, 0xe1, 0xbd, 0x2a, 0x0d, 0xdc, 0xae, 0x35, 0x6b, 0xc0, 0xf9, 0x0b, 0x78, 0x16, 0x66, 0xde, 0x7e, 0x85, 0x73, 0xc5, 0x1b, 0x38, 0xca, 0x9e, 0x86, 0x0a, 0xaa, 0x40, 0x2d, 0xf4, 0x0d, 0x8b, 0x38, 0x8e, 0xff, 0x10, 0x18, 0x49, 0xe0, 0xcf, 0xc7, 0xfe, 0xb3, 0xf7, 0xae };
static const unsigned char vec_certeq_pubkey[32] = { 0x72, 0x96, 0x62, 0x22, 0x23, 0xcb, 0x4f, 0x60, 0x32, 0xbd, 0x7d, 0x85, 0x98, 0x7c, 0x8a, 0xb7, 0xf1, 0xe7, 0xc4, 0xff, 0xda, 0xf3, 0xdf, 0xfb, 0xa5, 0x60, 0x91, 0x78, 0x79, 0x2b, 0x6f, 0xa3 };
static const unsigned char vec_certeq_sig[64] = { 0x63, 0xd7, 0xa5, 0xec, 0x8d, 0xfe, 0x7a, 0x8f, 0x2b, 0x5d, 0xa5, 0x4b, 0x3e, 0x14, 0x26, 0xec, 0xbc, 0x5a, 0xd0, 0xf6, 0x02, 0xf3, 0x77, 0xb1, 0x39, 0x47, 0x4f, 0x9b, 0x62, 0xd4, 0x8c, 0xd3, 0x1d, 0x78, 0xe5, 0xe6, 0x31, 0xcb, 0xb6, 0x90, 0x6e, 0xde, 0xa1, 0xc1, 0xf3, 0xf6, 0xc8, 0x0b, 0xed, 0x14, 0xad, 0x30, 0xff, 0x82, 0xa9, 0x6e, 0x89, 0x93, 0xe4, 0x0a, 0x1b, 0xf9, 0xa8, 0xe1 };
/* certeq participant_id = 7 */
static const unsigned char vec_ecdh_secnonce[32] = { 0x78, 0xfd, 0xe1, 0x58, 0x43, 0x13, 0x37, 0xaf, 0xbe, 0x0a, 0x27, 0x3f, 0x97, 0x8e, 0x84, 0x6b, 0x1d, 0xc3, 0x1b, 0x24, 0x72, 0x04, 0x25, 0x4b, 0x1f, 0x43, 0x76, 0x3c, 0x39, 0x38, 0x92, 0x3d };
static const unsigned char vec_ecdh_pubnonce[33] = { 0x02, 0x83, 0xae, 0x0b, 0x87, 0xf3, 0x12, 0xcc, 0x02, 0x12, 0xb9, 0x09, 0x30, 0x30, 0xe7, 0x1b, 0xc3, 0xd7, 0x11, 0x83, 0xff, 0x3b, 0xbe, 0x4d, 0x1c, 0x51, 0xd7, 0x1d, 0x74, 0x77, 0xc7, 0xb8, 0xc4 };
static const unsigned char vec_ecdh_hostseckey[32] = { 0xf9, 0xc4, 0x5c, 0xef, 0x58, 0x6b, 0x12, 0x4f, 0x47, 0x1e, 0x79, 0xc7, 0xa2, 0xc5, 0x14, 0xe3, 0x5e, 0xab, 0x6e, 0xdb, 0xb0, 0xdf, 0x48, 0x6a, 0x1b, 0x07, 0x20, 0xf5, 0xc0, 0xf3, 0x25, 0xc3 };
static const unsigned char vec_ecdh_hostpubkey[33] = { 0x03, 0x23, 0xf5, 0xef, 0x76, 0xc9, 0x7c, 0xb5, 0x01, 0x35, 0xe8, 0x5f, 0x1e, 0x2b, 0x4a, 0x5d, 0xfa, 0x97, 0x33, 0xb5, 0xca, 0xed, 0x7b, 0xa9, 0xc2, 0xcc, 0x08, 0x0c, 0x9a, 0xa7, 0xe6, 0x08, 0x9c };
static const unsigned char vec_ecdh_enc_context[70] = { 0x00, 0x00, 0x00, 0x02, 0x03, 0x23, 0xf5, 0xef, 0x76, 0xc9, 0x7c, 0xb5, 0x01, 0x35, 0xe8, 0x5f, 0x1e, 0x2b, 0x4a, 0x5d, 0xfa, 0x97, 0x33, 0xb5, 0xca, 0xed, 0x7b, 0xa9, 0xc2, 0xcc, 0x08, 0x0c, 0x9a, 0xa7, 0xe6, 0x08, 0x9c, 0x02, 0x0d, 0xce, 0x0a, 0x57, 0x0a, 0x10, 0x96, 0xce, 0x63, 0xdb, 0x07, 0xf2, 0xbe, 0x37, 0x28, 0x13, 0xbc, 0x92, 0xa1, 0xc4, 0xa0, 0xe0, 0x5a, 0x92, 0x94, 0xe8, 0x99, 0x9a, 0xb1, 0x46, 0xf0, 0xd2 };
static const unsigned char vec_ecdh_pad[32] = { 0x6a, 0x7a, 0x0f, 0xe9, 0xdc, 0x78, 0x5e, 0x35, 0x2e, 0x56, 0xf5, 0x12, 0x5a, 0x7f, 0x78, 0xeb, 0x4f, 0x7f, 0x5e, 0x4e, 0x8f, 0x6b, 0x05, 0x67, 0xe0, 0x27, 0x87, 0x45, 0xc1, 0x1e, 0xe8, 0x05 };
/* ecdh receiver_index = 0, t = 2 */
static const unsigned char vec_self_pad[32] = { 0xd0, 0xc2, 0x55, 0x74, 0xec, 0x55, 0xb5, 0xa8, 0x9e, 0x13, 0x7d, 0x62, 0xbd, 0x3f, 0x4b, 0x2f, 0xae, 0x9d, 0x00, 0x55, 0xbf, 0x4f, 0x94, 0x4f, 0xe8, 0xa1, 0xa3, 0x49, 0x99, 0x93, 0xab, 0x75 };
static const unsigned char vec_vss_seed[32] = { 0x5c, 0xd9, 0x4a, 0x21, 0x75, 0xd5, 0xf1, 0x6d, 0xc5, 0xa5, 0x9a, 0xc8, 0xf0, 0x91, 0x46, 0x44, 0x3a, 0xbc, 0x19, 0x64, 0xc2, 0xe0, 0xe3, 0x4f, 0xd8, 0x60, 0x26, 0x81, 0xea, 0xdc, 0xa8, 0x56 };
static const unsigned char vec_vss_coeffs[3][32] = {
{ 0xc9, 0xfd, 0x58, 0x25, 0xd8, 0x99, 0xc5, 0xb9, 0xd3, 0x16, 0x47, 0xa7, 0x16, 0xdb, 0xc9, 0x0e, 0xbb, 0xa7, 0x2c, 0x7f, 0xe2, 0xad, 0xfd, 0xd8, 0xb3, 0x86, 0x94, 0x93, 0xb1, 0x8b, 0xef, 0x1a },
{ 0x95, 0x22, 0xdb, 0x0d, 0x1f, 0xa2, 0xd3, 0xa5, 0xd9, 0x39, 0xd7, 0xfb, 0xf8, 0xb3, 0xd7, 0x17, 0xef, 0x44, 0x51, 0x2c, 0xd5, 0xea, 0x24, 0xdc, 0xb9, 0xea, 0x74, 0x19, 0xf6, 0x55, 0x0c, 0x34 },
{ 0xc1, 0x62, 0xf3, 0x91, 0x1c, 0x0b, 0x18, 0x91, 0x64, 0xc3, 0x66, 0x81, 0x87, 0xb7, 0x2e, 0x52, 0xab, 0x6a, 0x6e, 0x97, 0xbf, 0x12, 0x25, 0x3b, 0x28, 0x9b, 0x61, 0x2b, 0x9e, 0xf1, 0x72, 0xb1 },
};
static const unsigned char vec_vss_secshares[4][32] = {
{ 0x20, 0x83, 0x26, 0xc4, 0x14, 0x47, 0xb1, 0xf1, 0x11, 0x13, 0x86, 0x24, 0x97, 0x46, 0xce, 0x7b, 0xe0, 0xf8, 0x32, 0x77, 0x19, 0x19, 0x07, 0x79, 0x16, 0x67, 0xac, 0xbf, 0xa6, 0x65, 0xeb, 0x7d },
{ 0xf9, 0xce, 0xdc, 0x84, 0x88, 0x0b, 0xcf, 0x4b, 0x18, 0x97, 0x91, 0xa5, 0x27, 0x20, 0x30, 0x8e, 0x5d, 0x1e, 0x15, 0x9d, 0xcd, 0xa8, 0x5b, 0x8f, 0xca, 0x7f, 0x87, 0x42, 0xd9, 0x22, 0xcd, 0x42 },
{ 0x55, 0xe0, 0x79, 0x67, 0x33, 0xe6, 0x1d, 0xc7, 0xe9, 0xa2, 0x6a, 0x28, 0xc6, 0x67, 0xef, 0x4a, 0x00, 0x0c, 0x3f, 0x3f, 0xf2, 0x82, 0x19, 0x69, 0x90, 0x57, 0x08, 0x76, 0xd9, 0x1f, 0xd0, 0xa6 },
{ 0x34, 0xb7, 0xfd, 0x6c, 0x17, 0xd6, 0x9d, 0x67, 0x84, 0x34, 0x0f, 0xaf, 0x75, 0x1e, 0x0a, 0xac, 0x3f, 0x20, 0x69, 0x2a, 0xe6, 0x37, 0x81, 0x7d, 0xe7, 0x92, 0xed, 0x75, 0x46, 0xc9, 0x78, 0x2b },
};
static const unsigned char vec_vss_coms[3][33] = {
{ 0x03, 0xa5, 0xf8, 0x18, 0x69, 0x15, 0x26, 0x30, 0x9b, 0xbd, 0xdd, 0xbe, 0xd8, 0xf9, 0x7f, 0xfe, 0x02, 0xe0, 0x48, 0x90, 0x9b, 0xa0, 0xa0, 0x26, 0xa2, 0xee, 0x06, 0x84, 0x8f, 0x60, 0xc6, 0xd0, 0xe4 },
{ 0x02, 0x57, 0x8f, 0x58, 0x5c, 0x87, 0xa6, 0xad, 0x82, 0x2d, 0x39, 0xe4, 0x25, 0x30, 0xf0, 0xfe, 0xc4, 0x15, 0x7d, 0x57, 0xc2, 0xab, 0x32, 0x4d, 0x54, 0x6b, 0x48, 0xc9, 0x55, 0xc6, 0xa7, 0x47, 0x71 },
{ 0x03, 0xff, 0x1d, 0x03, 0xb8, 0x58, 0x0a, 0xc7, 0xf4, 0x3f, 0xc2, 0x45, 0x89, 0x6b, 0xae, 0xfb, 0x13, 0xb8, 0x58, 0xe7, 0x58, 0x37, 0x6a, 0xdf, 0x98, 0x94, 0xd9, 0xb5, 0x81, 0x18, 0xac, 0xf7, 0x87 },
};
static const unsigned char vec_vss_pubshares[4][33] = {
{ 0x02, 0x9b, 0x76, 0x00, 0x0c, 0x77, 0xf6, 0x00, 0x87, 0xe0, 0x0f, 0x6e, 0x62, 0xee, 0x8a, 0xa5, 0x83, 0x77, 0x76, 0xd7, 0xee, 0x72, 0x56, 0x8d, 0xf8, 0x3e, 0x75, 0xd9, 0x2f, 0x7b, 0xfe, 0x17, 0x17 },
{ 0x03, 0x16, 0x8c, 0x91, 0xbf, 0xac, 0xbf, 0x64, 0x11, 0x0c, 0x9b, 0x08, 0x0d, 0xb7, 0xb8, 0x4d, 0xe8, 0x39, 0x29, 0x34, 0xbb, 0x47, 0x04, 0xb4, 0xc5, 0x66, 0xaa, 0x19, 0xa2, 0xf0, 0x8c, 0x4c, 0xed },
{ 0x03, 0x92, 0x75, 0x4a, 0xba, 0x32, 0x6a, 0x59, 0x7d, 0x5b, 0x11, 0x2f, 0xc5, 0x40, 0x03, 0xbc, 0x4e, 0x0d, 0x30, 0x3a, 0x7f, 0x61, 0xbc, 0xb6, 0xbf, 0xdd, 0x4f, 0xe7, 0x89, 0x93, 0xbb, 0xa1, 0x4c },
{ 0x03, 0xc4, 0x96, 0xca, 0x1e, 0x45, 0xc7, 0xd9, 0xa8, 0x6f, 0x65, 0xcc, 0x9c, 0xe0, 0xef, 0xc1, 0x33, 0xfc, 0xbb, 0x37, 0xb2, 0xca, 0x55, 0xf7, 0x7e, 0x14, 0xea, 0x21, 0xcb, 0x58, 0x52, 0x1c, 0x35 },
};
static const unsigned char vec_vss_seed_b[32] = { 0x7c, 0x91, 0x4e, 0xab, 0x0b, 0x5c, 0x67, 0x77, 0xac, 0xfd, 0xef, 0x37, 0x16, 0xf9, 0x4a, 0xb9, 0x36, 0x06, 0xe7, 0x3c, 0xb0, 0xcd, 0x15, 0x67, 0xee, 0x3f, 0xae, 0x3c, 0x8a, 0xe4, 0xfa, 0x83 };
static const unsigned char vec_vss_coms_b[3][33] = {
{ 0x03, 0xb5, 0xbf, 0x93, 0x27, 0xec, 0xd2, 0x0c, 0xfa, 0xac, 0xcb, 0x38, 0x90, 0xda, 0xbf, 0x5e, 0x0d, 0x0b, 0xbc, 0x3b, 0x95, 0x12, 0x0a, 0x2e, 0xd3, 0x12, 0xc7, 0xdf, 0x4b, 0xae, 0x04, 0xa3, 0x12 },
{ 0x02, 0xde, 0xec, 0xea, 0x7f, 0x10, 0x13, 0x27, 0x03, 0x50, 0x0f, 0xe9, 0x76, 0xe8, 0x6e, 0xd4, 0x0e, 0xb5, 0x68, 0x94, 0x41, 0x0b, 0x2c, 0x69, 0x16, 0x7f, 0x3e, 0x63, 0x12, 0x57, 0xc3, 0xec, 0xf2 },
{ 0x03, 0x46, 0x36, 0x31, 0x26, 0x2a, 0x46, 0xb3, 0xca, 0x6d, 0x78, 0xb2, 0x81, 0x1f, 0xbe, 0x2c, 0xa0, 0x3a, 0xc3, 0x0f, 0x9c, 0x47, 0x5d, 0xa6, 0x27, 0x23, 0xd6, 0x2b, 0x66, 0xb7, 0x7a, 0x64, 0x1a },
};
static const unsigned char vec_vss_coms_sum[3][33] = {
{ 0x02, 0x01, 0xf8, 0x09, 0x72, 0x3e, 0x78, 0x2d, 0xd6, 0x9d, 0x3f, 0xac, 0xa9, 0x52, 0x53, 0x10, 0xe5, 0x00, 0x10, 0x25, 0xaa, 0x71, 0x51, 0x5a, 0xbb, 0x0c, 0x28, 0x0a, 0x65, 0xf9, 0x2a, 0xfb, 0xbd },
{ 0x03, 0xa5, 0x69, 0xf0, 0x2f, 0x95, 0xe2, 0x60, 0xd4, 0x14, 0x82, 0xcc, 0x4f, 0x23, 0x26, 0x90, 0xee, 0xa4, 0xb6, 0x76, 0xcc, 0x54, 0x73, 0x50, 0x4b, 0x98, 0x2c, 0x04, 0x78, 0x0a, 0xe5, 0xe1, 0x19 },
{ 0x03, 0x78, 0x2e, 0xb3, 0x69, 0x44, 0x8b, 0x68, 0x8e, 0xec, 0x98, 0x2b, 0x41, 0x11, 0xca, 0xb8, 0xf5, 0x5b, 0xbe, 0x0b, 0x22, 0xc7, 0x73, 0xc2, 0x9b, 0x6f, 0x7d, 0xdf, 0x8b, 0x73, 0x23, 0xe2, 0xc5 },
};
static const unsigned char vec_tweak[32] = { 0x83, 0xc0, 0x78, 0xa0, 0xb8, 0x64, 0x40, 0xf7, 0x5e, 0x2e, 0x23, 0xa6, 0xf1, 0x53, 0x81, 0xff, 0x9d, 0xd1, 0x2a, 0x2a, 0xee, 0xbf, 0xb1, 0xe1, 0x6e, 0xa4, 0x4a, 0x3d, 0x5d, 0xce, 0x00, 0xdd };
static const unsigned char vec_pubtweak[33] = { 0x02, 0xa7, 0xb9, 0xf0, 0x1f, 0x01, 0x58, 0x7e, 0xb0, 0x04, 0x7b, 0xc7, 0xc3, 0x83, 0x00, 0x0b, 0xd7, 0x26, 0xe9, 0xad, 0x33, 0x9c, 0x53, 0x62, 0xc8, 0x80, 0xf8, 0xb7, 0x56, 0x26, 0xe2, 0xc1, 0x1f };
static const unsigned char vec_vss_coms_tweaked[3][33] = {
{ 0x03, 0xf0, 0x1a, 0xf4, 0x40, 0xc3, 0x6d, 0xec, 0x54, 0xf8, 0x5f, 0xd6, 0x91, 0x1b, 0xe3, 0x97, 0xdd, 0x95, 0xf0, 0x19, 0x85, 0x20, 0x70, 0x3f, 0x8a, 0x18, 0xc8, 0x06, 0xde, 0xd8, 0x98, 0x5c, 0x1c },
{ 0x02, 0x57, 0x8f, 0x58, 0x5c, 0x87, 0xa6, 0xad, 0x82, 0x2d, 0x39, 0xe4, 0x25, 0x30, 0xf0, 0xfe, 0xc4, 0x15, 0x7d, 0x57, 0xc2, 0xab, 0x32, 0x4d, 0x54, 0x6b, 0x48, 0xc9, 0x55, 0xc6, 0xa7, 0x47, 0x71 },
{ 0x03, 0xff, 0x1d, 0x03, 0xb8, 0x58, 0x0a, 0xc7, 0xf4, 0x3f, 0xc2, 0x45, 0x89, 0x6b, 0xae, 0xfb, 0x13, 0xb8, 0x58, 0xe7, 0x58, 0x37, 0x6a, 0xdf, 0x98, 0x94, 0xd9, 0xb5, 0x81, 0x18, 0xac, 0xf7, 0x87 },
};
/* 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,
0xff, 0xff, 0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41
};
static const unsigned char vec_scalar_order_n_minus_1[32] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40
};
static const unsigned char vec_scalar_wrapped_ff[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x45, 0x51, 0x23, 0x19, 0x50, 0xb7, 0x5f, 0xc4,
0x40, 0x2d, 0xa1, 0x73, 0x2f, 0xc9, 0xbe, 0xbe
};
static void chilldkg_tagged_hashes_test(void) {
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);
secp256k1_sha256 sha;
unsigned char out[32];
secp256k1_chilldkg_sha256_tagged_encpedpop_seed(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_th_encpedpop_seed_msg, sizeof(vec_th_encpedpop_seed_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_th_encpedpop_seed, 32) == 0);
secp256k1_chilldkg_sha256_tagged_simplpedpop_aux(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_th_simplpedpop_aux_msg, sizeof(vec_th_simplpedpop_aux_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_th_simplpedpop_aux, 32) == 0);
secp256k1_chilldkg_sha256_tagged_encpedpop_secnonce(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_th_encpedpop_secnonce_msg, sizeof(vec_th_encpedpop_secnonce_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_th_encpedpop_secnonce, 32) == 0);
secp256k1_chilldkg_sha256_tagged_encpedpop_ecdh(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_th_encpedpop_ecdh_msg, sizeof(vec_th_encpedpop_ecdh_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_th_encpedpop_ecdh, 32) == 0);
secp256k1_chilldkg_sha256_tagged_self_pad(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_th_encaps_multi_self_pad_msg, sizeof(vec_th_encaps_multi_self_pad_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_th_encaps_multi_self_pad, 32) == 0);
secp256k1_chilldkg_sha256_tagged_vss_coeffs(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_th_vss_coeffs_msg, sizeof(vec_th_vss_coeffs_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_th_vss_coeffs, 32) == 0);
/* BIP 341 TapTweak over the x-only encoding of 2*G (no merkle root). */
secp256k1_chilldkg_sha256_tagged_taptweak(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, vec_taptweak_msg, sizeof(vec_taptweak_msg));
secp256k1_sha256_finalize(hash_ctx, &sha, out);
CHECK(secp256k1_memcmp_var(out, vec_taptweak, 32) == 0);
}
static void chilldkg_params_hash_test(void) {
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);
unsigned char out[32];
/* TH("BIP DKG/params_hash", u32be(2) || hostpubkeys) */
secp256k1_chilldkg_params_hash(hash_ctx, out, &vec_params_hash_hostpubkeys[0][0], 3, 2);
CHECK(secp256k1_memcmp_var(out, vec_params_hash, 32) == 0);
/* t is part of the hash input. */
secp256k1_chilldkg_params_hash(hash_ctx, out, &vec_params_hash_hostpubkeys[0][0], 3, 3);
CHECK(secp256k1_memcmp_var(out, vec_params_hash, 32) != 0);
}
static void chilldkg_point_serialization_test(void) {
static const unsigned char zeros33[33] = { 0 };
secp256k1_ge p, q;
secp256k1_gej pj;
secp256k1_scalar one;
unsigned char buf[33];
unsigned char buf32[32];
size_t i;
/* Roundtrip of the compressed encodings of 1*G, 2*G and 3*G. */
for (i = 0; i < 3; i++) {
const unsigned char *expected = i == 0 ? vec_point_0 : (i == 1 ? vec_point_1 : vec_point_2);
CHECK(secp256k1_chilldkg_point_load(&p, expected) == 1);
CHECK(!secp256k1_ge_is_infinity(&p));
secp256k1_chilldkg_point_save(buf, &p);
CHECK(secp256k1_memcmp_var(buf, expected, 33) == 0);
}
/* 1*G computed in C serializes like the reference. */
secp256k1_scalar_set_int(&one, 1);
secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &pj, &one);
secp256k1_ge_set_gej(&p, &pj);
secp256k1_chilldkg_point_save(buf, &p);
CHECK(secp256k1_memcmp_var(buf, vec_point_0, 33) == 0);
/* The point at infinity maps to 33 zero bytes and back. */
CHECK(secp256k1_chilldkg_point_load(&p, zeros33) == 1);
CHECK(secp256k1_ge_is_infinity(&p));
secp256k1_chilldkg_point_save(buf, &p);
CHECK(secp256k1_memcmp_var(buf, zeros33, 33) == 0);
/* Invalid encodings are rejected and distinguishable from success. */
/* Wrong prefix (0x04). */
memcpy(buf, vec_point_0, 33);
buf[0] = 0x04;
CHECK(secp256k1_chilldkg_point_load(&p, buf) == 0);
/* x >= p. */
buf[0] = 0x02;
memset(&buf[1], 0xff, 32);
CHECK(secp256k1_chilldkg_point_load(&p, buf) == 0);
/* x = 0 is not on the curve (0^3 + 7 = 7 is not a quadratic residue). */
buf[0] = 0x02;
memset(&buf[1], 0x00, 32);
CHECK(secp256k1_chilldkg_point_load(&p, buf) == 0);
/* 0x03 with x = 0 is also not on the curve. */
buf[0] = 0x03;
CHECK(secp256k1_chilldkg_point_load(&p, buf) == 0);
/* The prefix selects the y parity: 0x03 || x(G) is -G. */
memcpy(buf, vec_point_0, 33);
buf[0] = 0x03;
CHECK(secp256k1_chilldkg_point_load(&p, buf) == 1);
CHECK(secp256k1_chilldkg_point_load(&q, vec_point_0) == 1);
secp256k1_ge_neg(&q, &q);
CHECK(secp256k1_ge_eq_var(&p, &q));
secp256k1_chilldkg_point_save(buf, &p);
CHECK(buf[0] == 0x03);
/* x-only save/load roundtrip (even y representative). */
CHECK(secp256k1_chilldkg_point_load(&p, vec_point_2) == 1);
secp256k1_chilldkg_xonly_save(buf32, &p);
CHECK(secp256k1_memcmp_var(buf32, &vec_point_2[1], 32) == 0);
CHECK(secp256k1_chilldkg_xonly_load(&q, buf32) == 1);
secp256k1_chilldkg_xonly_save(buf, &q);
CHECK(secp256k1_memcmp_var(buf, buf32, 32) == 0);
/* x-only load rejects x >= p and non-curve x. */
memset(buf32, 0xff, 32);
CHECK(secp256k1_chilldkg_xonly_load(&q, buf32) == 0);
memset(buf32, 0x00, 32);
CHECK(secp256k1_chilldkg_xonly_load(&q, buf32) == 0);
}
static void chilldkg_scalar_parse_test(void) {
static const unsigned char ff32[32] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static const unsigned char zeros32[32] = { 0 };
secp256k1_scalar s;
unsigned char out[32];
int overflow;
/* Checked parse (Scalar.from_bytes_checked): n and above are rejected. */
secp256k1_scalar_set_b32(&s, vec_scalar_order_n, &overflow);
CHECK(overflow);
secp256k1_scalar_set_b32(&s, ff32, &overflow);
CHECK(overflow);
/* n - 1 and 0 are accepted. */
secp256k1_scalar_set_b32(&s, vec_scalar_order_n_minus_1, &overflow);
CHECK(!overflow);
secp256k1_scalar_get_b32(out, &s);
CHECK(secp256k1_memcmp_var(out, vec_scalar_order_n_minus_1, 32) == 0);
secp256k1_scalar_set_b32(&s, zeros32, &overflow);
CHECK(!overflow);
CHECK(secp256k1_scalar_is_zero(&s));
/* Wrapping parse (Scalar.from_bytes_wrapping): reduce mod n. */
secp256k1_scalar_set_b32(&s, ff32, NULL);
secp256k1_scalar_get_b32(out, &s);
CHECK(secp256k1_memcmp_var(out, vec_scalar_wrapped_ff, 32) == 0);
secp256k1_scalar_set_b32(&s, vec_scalar_order_n, NULL);
CHECK(secp256k1_scalar_is_zero(&s));
}
static void chilldkg_schnorrsig_test(void) {
unsigned char sig[64];
unsigned char sig_ref[64];
unsigned char pop_msg[4] = { 0, 0, 0, 5 };
unsigned char msg[101];
unsigned char msg32[32];
unsigned char zero32[32] = { 0 };
secp256k1_keypair keypair;
size_t i;
/* Proof of possession: custom tag prefix "BIP DKG/pop message" over
* u32be(participant_id). */
CHECK(secp256k1_chilldkg_schnorrsig_sign(CTX, sig, pop_msg, sizeof(pop_msg), vec_pop_seckey, vec_pop_aux, "BIP DKG/pop message") == 1);
CHECK(secp256k1_memcmp_var(sig, vec_pop_sig, 64) == 0);
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, pop_msg, sizeof(pop_msg), vec_pop_pubkey, "BIP DKG/pop message") == 1);
/* Wrong tag prefix, message or public key fail verification. */
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, pop_msg, sizeof(pop_msg), vec_pop_pubkey, "BIP0340") == 0);
pop_msg[3] = 6;
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, pop_msg, sizeof(pop_msg), vec_pop_pubkey, "BIP DKG/pop message") == 0);
pop_msg[3] = 5;
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, pop_msg, sizeof(pop_msg), vec_certeq_pubkey, "BIP DKG/pop message") == 0);
/* A corrupted signature fails verification. */
sig[63] ^= 1;
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, pop_msg, sizeof(pop_msg), vec_pop_pubkey, "BIP DKG/pop message") == 0);
sig[63] ^= 1;
/* CertEq-style message: standard "BIP0340" tags over an
* arbitrary-length message with a 33-byte zero-padded prefix
* (pad33("BIP DKG/certeq message") || u32be(id) || eq_input). */
secp256k1_chilldkg_pad33(msg, "BIP DKG/certeq message");
/* pad33 zero-pads to exactly 33 bytes. */
CHECK(secp256k1_memcmp_var(msg, "BIP DKG/certeq message", 22) == 0);
for (i = 22; i < 33; i++) {
CHECK(msg[i] == 0);
}
secp256k1_write_be32(msg + 33, 7);
memcpy(msg + 37, vec_certeq_eq_input, 64);
CHECK(secp256k1_chilldkg_schnorrsig_sign(CTX, sig, msg, sizeof(msg), vec_certeq_seckey, vec_certeq_aux, "BIP0340") == 1);
CHECK(secp256k1_memcmp_var(sig, vec_certeq_sig, 64) == 0);
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, msg, sizeof(msg), vec_certeq_pubkey, "BIP0340") == 1);
CHECK(secp256k1_chilldkg_schnorrsig_verify(CTX, sig, msg, sizeof(msg), vec_certeq_pubkey, "BIP DKG/pop message") == 0);
/* With the "BIP0340" tag prefix and a 32-byte message, the internal
* signer agrees with the public schnorrsig module. */
for (i = 0; i < 32; i++) {
msg32[i] = (unsigned char)(i + 1);
}
CHECK(secp256k1_keypair_create(CTX, &keypair, vec_certeq_seckey) == 1);
CHECK(secp256k1_schnorrsig_sign32(CTX, sig_ref, msg32, &keypair, vec_certeq_aux) == 1);
CHECK(secp256k1_chilldkg_schnorrsig_sign(CTX, sig, msg32, 32, vec_certeq_seckey, vec_certeq_aux, "BIP0340") == 1);
CHECK(secp256k1_memcmp_var(sig, sig_ref, 64) == 0);
/* Invalid secret keys (0 and n) are rejected. */
CHECK(secp256k1_chilldkg_schnorrsig_sign(CTX, sig, msg32, 32, zero32, vec_certeq_aux, "BIP0340") == 0);
CHECK(secp256k1_memcmp_var(sig, zero32, 32) == 0);
CHECK(secp256k1_chilldkg_schnorrsig_sign(CTX, sig, msg32, 32, vec_scalar_order_n, vec_certeq_aux, "BIP0340") == 0);
}
static void chilldkg_ecdh_pad_test(void) {
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);
secp256k1_scalar secnonce, hostseckey, pad, self_pad;
secp256k1_ge pubnonce_ge, hostpubkey_ge;
/* context = u32be(receiver_index = 0) || enc_context */
unsigned char context[4 + 70];
unsigned char out[32];
int overflow;
secp256k1_write_be32(context, 0);
memcpy(context + 4, vec_ecdh_enc_context, 70);
secp256k1_scalar_set_b32(&secnonce, vec_ecdh_secnonce, &overflow);
CHECK(!overflow);
secp256k1_scalar_set_b32(&hostseckey, vec_ecdh_hostseckey, &overflow);
CHECK(!overflow);
CHECK(secp256k1_chilldkg_point_load(&pubnonce_ge, vec_ecdh_pubnonce) == 1);
CHECK(secp256k1_chilldkg_point_load(&hostpubkey_ge, vec_ecdh_hostpubkey) == 1);
/* Sender side: ecdh(secnonce, my=pubnonce, their=hostpubkey, sending). */
secp256k1_chilldkg_encpedpop_ecdh(CTX, &pad, &secnonce, &hostpubkey_ge, vec_ecdh_pubnonce, vec_ecdh_hostpubkey, context, sizeof(context), 1);
secp256k1_scalar_get_b32(out, &pad);
CHECK(secp256k1_memcmp_var(out, vec_ecdh_pad, 32) == 0);
/* Receiver side: ecdh(hostseckey, my=hostpubkey, their=pubnonce, !sending)
* derives the same pad. */
secp256k1_chilldkg_encpedpop_ecdh(CTX, &pad, &hostseckey, &pubnonce_ge, vec_ecdh_hostpubkey, vec_ecdh_pubnonce, context, sizeof(context), 0);
secp256k1_scalar_get_b32(out, &pad);
CHECK(secp256k1_memcmp_var(out, vec_ecdh_pad, 32) == 0);
/* Swapping the roles on the sender side changes the pad (the sender's
* pubnonce comes first in the hash input). */
secp256k1_chilldkg_encpedpop_ecdh(CTX, &pad, &secnonce, &hostpubkey_ge, vec_ecdh_pubnonce, vec_ecdh_hostpubkey, context, sizeof(context), 0);
secp256k1_scalar_get_b32(out, &pad);
CHECK(secp256k1_memcmp_var(out, vec_ecdh_pad, 32) != 0);
/* Symmetric self pad. */
secp256k1_chilldkg_encpedpop_self_pad(hash_ctx, &self_pad, vec_ecdh_hostseckey, vec_ecdh_pubnonce, context, sizeof(context));
secp256k1_scalar_get_b32(out, &self_pad);
CHECK(secp256k1_memcmp_var(out, vec_self_pad, 32) == 0);
secp256k1_scalar_clear(&secnonce);
secp256k1_scalar_clear(&hostseckey);
secp256k1_scalar_clear(&pad);
secp256k1_scalar_clear(&self_pad);
}
static void chilldkg_vss_test(void) {
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);
secp256k1_scalar coeffs[3], share, share2, x, tweak;
secp256k1_ge coms[3], coms_b[3], coms_sum[3], tweaked[3], pubtweak, pubshare;
secp256k1_gej pubsharej;
unsigned char buf[33];
unsigned char buf32[32];
size_t i, j;
/* Coefficient derivation from the seed. */
CHECK(secp256k1_chilldkg_vss_gen_coeffs(hash_ctx, coeffs, 3, vec_vss_seed) == 1);
for (j = 0; j < 3; j++) {
secp256k1_scalar_get_b32(buf32, &coeffs[j]);
CHECK(secp256k1_memcmp_var(buf32, vec_vss_coeffs[j], 32) == 0);
}
/* Shamir shares f(id+1) via Horner. */
for (i = 0; i < 4; i++) {
secp256k1_chilldkg_vss_secshare_for(&share, coeffs, 3, (uint32_t)i);
secp256k1_scalar_get_b32(buf32, &share);
CHECK(secp256k1_memcmp_var(buf32, vec_vss_secshares[i], 32) == 0);
}
/* The x = id+1 convention: secshare_for(0) is the evaluation at x = 1. */
secp256k1_scalar_set_int(&x, 1);
secp256k1_chilldkg_vss_poly_eval(&share2, coeffs, 3, &x);
secp256k1_chilldkg_vss_secshare_for(&share, coeffs, 3, 0);
secp256k1_scalar_get_b32(buf32, &share);
secp256k1_scalar_get_b32(buf, &share2);
CHECK(secp256k1_memcmp_var(buf32, buf, 32) == 0);
/* Commitments com_j = coeffs[j]*G. */
secp256k1_chilldkg_vss_commit(CTX, coms, coeffs, 3);
for (j = 0; j < 3; j++) {
secp256k1_chilldkg_point_save(buf, &coms[j]);
CHECK(secp256k1_memcmp_var(buf, vec_vss_coms[j], 33) == 0);
}
/* Pubshares sum_j (id+1)^j * com_j, and share verification. */
for (i = 0; i < 4; i++) {
secp256k1_chilldkg_vss_pubshare(&pubsharej, coms, 3, (uint32_t)i);
CHECK(!secp256k1_gej_is_infinity(&pubsharej));
secp256k1_ge_set_gej_var(&pubshare, &pubsharej);
secp256k1_chilldkg_point_save(buf, &pubshare);
CHECK(secp256k1_memcmp_var(buf, vec_vss_pubshares[i], 33) == 0);
secp256k1_chilldkg_vss_secshare_for(&share, coeffs, 3, (uint32_t)i);
CHECK(secp256k1_chilldkg_vss_verify_secshare(CTX, &share, &pubshare) == 1);
}
/* The share of participant 0 does not verify against pubshare 1. */
secp256k1_chilldkg_vss_pubshare(&pubsharej, coms, 3, 1);
secp256k1_ge_set_gej_var(&pubshare, &pubsharej);
secp256k1_chilldkg_vss_secshare_for(&share, coeffs, 3, 0);
CHECK(secp256k1_chilldkg_vss_verify_secshare(CTX, &share, &pubshare) == 0);
/* Component-wise commitment addition. */
CHECK(secp256k1_chilldkg_vss_gen_coeffs(hash_ctx, coeffs, 3, vec_vss_seed_b) == 1);
secp256k1_chilldkg_vss_commit(CTX, coms_b, coeffs, 3);
for (j = 0; j < 3; j++) {
secp256k1_chilldkg_point_save(buf, &coms_b[j]);
CHECK(secp256k1_memcmp_var(buf, vec_vss_coms_b[j], 33) == 0);
}
/* Reload the first commitment (coeffs were overwritten above). */
for (j = 0; j < 3; j++) {
CHECK(secp256k1_chilldkg_point_load(&coms[j], vec_vss_coms[j]) == 1);
}
secp256k1_chilldkg_vss_commitment_add(coms_sum, coms, coms_b, 3);
for (j = 0; j < 3; j++) {
secp256k1_chilldkg_point_save(buf, &coms_sum[j]);
CHECK(secp256k1_memcmp_var(buf, vec_vss_coms_sum[j], 33) == 0);
}
/* Adding the point at infinity (33 zero bytes) is the identity. */
{
secp256k1_ge inf_com[3], id_sum[3];
static const unsigned char zeros33[33] = { 0 };
for (j = 0; j < 3; j++) {
CHECK(secp256k1_chilldkg_point_load(&inf_com[j], zeros33) == 1);
}
secp256k1_chilldkg_vss_commitment_add(id_sum, coms, inf_com, 3);
for (j = 0; j < 3; j++) {
secp256k1_chilldkg_point_save(buf, &id_sum[j]);
CHECK(secp256k1_memcmp_var(buf, vec_vss_coms[j], 33) == 0);
}
}
/* TapTweak: the tweaked commitment, tweak and pubtweak match the
* reference, and the tweaked share verifies against the tweaked
* pubshare. */
CHECK(secp256k1_chilldkg_vss_invalid_taproot_commit(CTX, tweaked, &tweak, &pubtweak, coms, 3) == 1);
secp256k1_scalar_get_b32(buf32, &tweak);
CHECK(secp256k1_memcmp_var(buf32, vec_tweak, 32) == 0);
secp256k1_chilldkg_point_save(buf, &pubtweak);
CHECK(secp256k1_memcmp_var(buf, vec_pubtweak, 33) == 0);
for (j = 0; j < 3; j++) {
secp256k1_chilldkg_point_save(buf, &tweaked[j]);
CHECK(secp256k1_memcmp_var(buf, vec_vss_coms_tweaked[j], 33) == 0);
}
CHECK(secp256k1_chilldkg_vss_gen_coeffs(hash_ctx, coeffs, 3, vec_vss_seed) == 1);
secp256k1_chilldkg_vss_secshare_for(&share, coeffs, 3, 0);
secp256k1_scalar_add(&share, &share, &tweak);
secp256k1_chilldkg_vss_pubshare(&pubsharej, tweaked, 3, 0);
secp256k1_ge_set_gej_var(&pubshare, &pubsharej);
CHECK(secp256k1_chilldkg_vss_verify_secshare(CTX, &share, &pubshare) == 1);
/* invalid_taproot_commit fails on an infinity constant term. */
{
static const unsigned char zeros33[33] = { 0 };
CHECK(secp256k1_chilldkg_point_load(&coms[0], zeros33) == 1);
CHECK(secp256k1_chilldkg_vss_invalid_taproot_commit(CTX, tweaked, &tweak, &pubtweak, coms, 3) == 0);
}
for (j = 0; j < 3; j++) {
secp256k1_scalar_clear(&coeffs[j]);
}
secp256k1_scalar_clear(&share);
secp256k1_scalar_clear(&share2);
secp256k1_scalar_clear(&x);
secp256k1_scalar_clear(&tweak);
}
static const struct tf_test_entry tests_chilldkg[] = {
CASE1(chilldkg_scaffolding_test),
CASE1(chilldkg_tagged_hashes_test),
CASE1(chilldkg_params_hash_test),
CASE1(chilldkg_point_serialization_test),
CASE1(chilldkg_scalar_parse_test),
CASE1(chilldkg_schnorrsig_test),
CASE1(chilldkg_ecdh_pad_test),
CASE1(chilldkg_vss_test),
};
#endif

View File

@@ -0,0 +1,99 @@
/***********************************************************************
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_CHILLDKG_UTIL_H
#define SECP256K1_MODULE_CHILLDKG_UTIL_H
#include "../../../include/secp256k1.h"
#include "../../group.h"
#include "../../hash.h"
#include "../../scalar.h"
/* 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
* reference is the goal; the tagged hash strings below are copied verbatim
* from the Python sources. */
/* Tagged hash initializers (BIP 340 tagged hashes). All ChillDKG tags share
* the "BIP DKG/" prefix (BIP_TAG in chilldkg_ref/util.py); "TapTweak" is the
* BIP 341 tag. */
static void secp256k1_chilldkg_sha256_tagged_params_hash(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_encpedpop_seed(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_simplpedpop_aux(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_encpedpop_secnonce(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_encpedpop_ecdh(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_self_pad(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_vss_coeffs(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
static void secp256k1_chilldkg_sha256_tagged_taptweak(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha);
/* Serialize a group element to 33 bytes: the point at infinity maps to 33
* zero bytes, otherwise the standard SEC compressed encoding (0x02/0x03
* prefix) is used. Mirrors GE.to_bytes_compressed_with_infinity. */
static void secp256k1_chilldkg_point_save(unsigned char *out33, const secp256k1_ge *p);
/* Parse a 33-byte group element encoding: 33 zero bytes map to the point at
* infinity, otherwise standard SEC compressed parsing applies. Returns 1 on
* success and 0 if the encoding is invalid (wrong prefix, x >= p, or x not on
* the curve). On failure, *p is set to the point at infinity. Mirrors
* GE.from_bytes_compressed_with_infinity. */
static int secp256k1_chilldkg_point_load(secp256k1_ge *p, const unsigned char *in33);
/* Serialize the x coordinate of a non-infinity group element (32 bytes).
* Mirrors GE.to_bytes_xonly, which asserts non-infinity. */
static void secp256k1_chilldkg_xonly_save(unsigned char *out32, const secp256k1_ge *p);
/* Parse a 32-byte x-only group element encoding (even y). Returns 1 on
* success, 0 if x >= p or x is not on the curve. Mirrors
* GE.from_bytes_xonly. */
static int secp256k1_chilldkg_xonly_load(secp256k1_ge *p, const unsigned char *in32);
/* Write str (at most 33 bytes) zero-padded to 33 bytes. Used to build the
* domain separation prefixes of the CertEq and recovery acknowledgment
* messages (see certeq_message/recovery_ack_message in chilldkg_ref). */
static void secp256k1_chilldkg_pad33(unsigned char *out33, const char *str);
/* BIP 340 Schnorr signing/verification with a parameterized tag prefix and
* arbitrary-length messages, mirroring secp256k1lab/bip340.py's schnorr_sign
* and schnorr_verify. The tags used for aux, nonce and challenge hashes are
* tag_prefix || "/aux", tag_prefix || "/nonce" and tag_prefix ||
* "/challenge". ChillDKG uses tag_prefix "BIP DKG/pop message" for proofs of
* possession (POP_MSG_TAG in simplpedpop.py) and the standard "BIP0340"
* prefix for CertEq and recovery acknowledgments.
*
* sign returns 1 on success and 0 if seckey32 is not in range 1..n-1 or the
* derived nonce is zero (negligible probability). On failure, sig64 is set to
* zero. verify returns 1 if the signature is valid and 0 otherwise. */
static int secp256k1_chilldkg_schnorrsig_sign(const secp256k1_context *ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *seckey32, const unsigned char *aux_rand32, const char *tag_prefix);
static int secp256k1_chilldkg_schnorrsig_verify(const secp256k1_context *ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32, const char *tag_prefix);
/* Compute the EncPedPop ECDH pad (encpedpop.py `ecdh`):
* shared = SHA256(compressed(seckey * their_point)) [libsecp256k1 ECDH]
* out = Scalar.from_bytes_wrapping(TH("BIP DKG/encpedpop ecdh",
* shared || my_pubkey33 || their_pubkey33 || context)) if sending
* out = Scalar.from_bytes_wrapping(TH("BIP DKG/encpedpop ecdh",
* shared || their_pubkey33 || my_pubkey33 || context)) otherwise
* In the protocol, my/their_pubkey33 are the sender's pubnonce and the
* receiver's host pubkey (in this order in the hash input, regardless of
* sending), and context is u32be(receiver_index) || enc_context.
* their_point must be the parsed their_pubkey33 and must not be infinity
* (enforced by VERIFY_CHECK). seckey must be nonzero. */
static void secp256k1_chilldkg_encpedpop_ecdh(const secp256k1_context *ctx, secp256k1_scalar *out, const secp256k1_scalar *seckey, const secp256k1_ge *their_point, const unsigned char *my_pubkey33, const unsigned char *their_pubkey33, const unsigned char *context, size_t context_len, int sending);
/* Compute the EncPedPop symmetric pad for encrypting to ourselves
* (encpedpop.py `self_pad`):
* out = Scalar.from_bytes_wrapping(TH("BIP DKG/encaps_multi self_pad",
* symkey32 || nonce33 || context))
* where symkey32 is the host seckey, nonce33 is the own pubnonce and context
* is u32be(own_index) || enc_context. */
static void secp256k1_chilldkg_encpedpop_self_pad(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *out, const unsigned char *symkey32, const unsigned char *nonce33, const unsigned char *context, size_t context_len);
/* Compute the session parameters hash (chilldkg.py `params_hash`):
* out32 = TH("BIP DKG/params_hash", u32be(t) || hostpubkeys[0] || ... || hostpubkeys[n-1])
* where hostpubkeys33 is an array of n 33-byte compressed host public keys. */
static void secp256k1_chilldkg_params_hash(const secp256k1_hash_ctx *hash_ctx, unsigned char *out32, const unsigned char *hostpubkeys33, size_t n, uint32_t t);
#endif

View File

@@ -0,0 +1,386 @@
/***********************************************************************
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_CHILLDKG_UTIL_IMPL_H
#define SECP256K1_MODULE_CHILLDKG_UTIL_IMPL_H
#include <string.h>
#include "../../../include/secp256k1.h"
#include "util.h"
#include "../../ecmult.h"
#include "../../ecmult_const.h"
#include "../../util.h"
static void secp256k1_chilldkg_sha256_tagged_params_hash(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/params_hash", sizeof("BIP DKG/params_hash") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_encpedpop_seed(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encpedpop seed", sizeof("BIP DKG/encpedpop seed") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_simplpedpop_aux(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/simplpedpop aux", sizeof("BIP DKG/simplpedpop aux") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_encpedpop_secnonce(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encpedpop secnonce", sizeof("BIP DKG/encpedpop secnonce") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_encpedpop_ecdh(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encpedpop ecdh", sizeof("BIP DKG/encpedpop ecdh") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_self_pad(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encaps_multi self_pad", sizeof("BIP DKG/encaps_multi self_pad") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_vss_coeffs(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/vss coeffs", sizeof("BIP DKG/vss coeffs") - 1);
}
static void secp256k1_chilldkg_sha256_tagged_taptweak(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"TapTweak", sizeof("TapTweak") - 1);
}
static void secp256k1_chilldkg_point_save(unsigned char *out33, const secp256k1_ge *p) {
if (secp256k1_ge_is_infinity(p)) {
memset(out33, 0, 33);
return;
}
{
secp256k1_ge tmp = *p;
/* Serialization operates on public data (commitments, pubnonces, host
* public keys), so variable-time normalization is fine. */
secp256k1_fe_normalize_var(&tmp.x);
secp256k1_fe_normalize_var(&tmp.y);
out33[0] = secp256k1_fe_is_odd(&tmp.y) ? 0x03 : 0x02;
secp256k1_fe_get_b32(&out33[1], &tmp.x);
}
}
static int secp256k1_chilldkg_point_load(secp256k1_ge *p, const unsigned char *in33) {
static const unsigned char zeros33[33] = { 0 };
secp256k1_fe x;
/* Parsed data comes from protocol messages, i.e., it is public. */
if (secp256k1_memcmp_var(in33, zeros33, 33) == 0) {
secp256k1_ge_set_infinity(p);
return 1;
}
if (in33[0] != 0x02 && in33[0] != 0x03) {
secp256k1_ge_set_infinity(p);
return 0;
}
if (!secp256k1_fe_set_b32_limit(&x, &in33[1])) {
secp256k1_ge_set_infinity(p);
return 0;
}
if (!secp256k1_ge_set_xo_var(p, &x, in33[0] == 0x03)) {
secp256k1_ge_set_infinity(p);
return 0;
}
return 1;
}
static void secp256k1_chilldkg_xonly_save(unsigned char *out32, const secp256k1_ge *p) {
secp256k1_ge tmp = *p;
VERIFY_CHECK(!secp256k1_ge_is_infinity(p));
secp256k1_fe_normalize_var(&tmp.x);
secp256k1_fe_get_b32(out32, &tmp.x);
}
static int secp256k1_chilldkg_xonly_load(secp256k1_ge *p, const unsigned char *in32) {
secp256k1_fe x;
if (!secp256k1_fe_set_b32_limit(&x, in32)) {
return 0;
}
return secp256k1_ge_set_xo_var(p, &x, 0);
}
static void secp256k1_chilldkg_pad33(unsigned char *out33, const char *str) {
size_t len = strlen(str);
VERIFY_CHECK(len <= 33);
memcpy(out33, str, len);
memset(out33 + len, 0, 33 - len);
}
/* Initializes sha with the BIP 340 tagged hash tag tag_prefix || subtag,
* e.g., "BIP DKG/pop message" || "/nonce". Tag strings are public constants. */
static void secp256k1_chilldkg_schnorrsig_sha256_tagged(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha, const char *tag_prefix, const char *subtag) {
unsigned char tag[64];
size_t prefix_len = strlen(tag_prefix);
size_t subtag_len = strlen(subtag);
VERIFY_CHECK(prefix_len + subtag_len <= sizeof(tag));
memcpy(tag, tag_prefix, prefix_len);
memcpy(tag + prefix_len, subtag, subtag_len);
secp256k1_sha256_initialize_tagged(hash_ctx, sha, tag, prefix_len + subtag_len);
}
/* BIP 340 nonce derivation with parameterized tag prefix, mirroring
* schnorr_sign in secp256k1lab/bip340.py:
* t = seckey32 XOR TH(tag_prefix || "/aux", aux_rand32)
* nonce32 = TH(tag_prefix || "/nonce", t || xonly_pk32 || msg) */
static void secp256k1_chilldkg_schnorrsig_nonce(const secp256k1_hash_ctx *hash_ctx, unsigned char *nonce32, const unsigned char *msg, size_t msglen, const unsigned char *seckey32, const unsigned char *xonly_pk32, const unsigned char *aux_rand32, const char *tag_prefix) {
secp256k1_sha256 sha;
unsigned char masked_key[32];
unsigned char rand[32];
int i;
secp256k1_chilldkg_schnorrsig_sha256_tagged(hash_ctx, &sha, tag_prefix, "/aux");
secp256k1_sha256_write(hash_ctx, &sha, aux_rand32, 32);
secp256k1_sha256_finalize(hash_ctx, &sha, rand);
for (i = 0; i < 32; i++) {
masked_key[i] = seckey32[i] ^ rand[i];
}
secp256k1_chilldkg_schnorrsig_sha256_tagged(hash_ctx, &sha, tag_prefix, "/nonce");
secp256k1_sha256_write(hash_ctx, &sha, masked_key, 32);
secp256k1_sha256_write(hash_ctx, &sha, xonly_pk32, 32);
secp256k1_sha256_write(hash_ctx, &sha, msg, msglen);
secp256k1_sha256_finalize(hash_ctx, &sha, nonce32);
secp256k1_sha256_clear(&sha);
secp256k1_memclear_explicit(masked_key, sizeof(masked_key));
secp256k1_memclear_explicit(rand, sizeof(rand));
}
/* BIP 340 challenge hash with parameterized tag prefix:
* e = TH(tag_prefix || "/challenge", r32 || pubkey32 || msg) mod n
* The reduction modulo the group order matches the reference, which reduces
* the hash output with int_from_bytes(...) % GE.ORDER. */
static void secp256k1_chilldkg_schnorrsig_challenge(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *e, const unsigned char *r32, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32, const char *tag_prefix) {
unsigned char buf[32];
secp256k1_sha256 sha;
secp256k1_chilldkg_schnorrsig_sha256_tagged(hash_ctx, &sha, tag_prefix, "/challenge");
secp256k1_sha256_write(hash_ctx, &sha, r32, 32);
secp256k1_sha256_write(hash_ctx, &sha, pubkey32, 32);
secp256k1_sha256_write(hash_ctx, &sha, msg, msglen);
secp256k1_sha256_finalize(hash_ctx, &sha, buf);
secp256k1_scalar_set_b32(e, buf, NULL);
}
/* Mirrors schnorr_sign in secp256k1lab/bip340.py. The structure follows
* secp256k1_schnorrsig_sign_internal, which cannot be reused directly because
* it hardcodes the "BIP0340" tags. */
static int secp256k1_chilldkg_schnorrsig_sign(const secp256k1_context *ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *seckey32, const unsigned char *aux_rand32, const char *tag_prefix) {
const secp256k1_hash_ctx *hash_ctx;
secp256k1_scalar sk;
secp256k1_scalar e;
secp256k1_scalar k;
secp256k1_ge pk;
secp256k1_ge r;
unsigned char nonce32[32] = { 0 };
unsigned char pk32[32];
unsigned char seckey[32];
int overflow;
int ret = 1;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(sig64 != NULL);
ARG_CHECK(msg != NULL || msglen == 0);
ARG_CHECK(seckey32 != NULL);
ARG_CHECK(aux_rand32 != NULL);
ARG_CHECK(tag_prefix != NULL);
hash_ctx = secp256k1_get_hash_context(ctx);
secp256k1_scalar_set_b32(&sk, seckey32, &overflow);
overflow |= secp256k1_scalar_is_zero(&sk);
/* Branching on the validity of the secret key is fine: whether the
* caller's secret key is in range 1..n-1 is not secret. */
secp256k1_declassify(ctx, &overflow, sizeof(overflow));
if (overflow) {
memset(sig64, 0, 64);
secp256k1_scalar_clear(&sk);
return 0;
}
secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &pk, &sk);
/* The public key is not secret, so variable-time normalization and
* branching on its y parity are fine. */
secp256k1_fe_normalize_var(&pk.x);
secp256k1_fe_normalize_var(&pk.y);
if (secp256k1_fe_is_odd(&pk.y)) {
secp256k1_scalar_negate(&sk, &sk);
}
secp256k1_scalar_get_b32(seckey, &sk);
secp256k1_fe_get_b32(pk32, &pk.x);
secp256k1_chilldkg_schnorrsig_nonce(hash_ctx, nonce32, msg, msglen, seckey, pk32, aux_rand32, tag_prefix);
/* The reference reduces the nonce hash modulo the group order. */
secp256k1_scalar_set_b32(&k, nonce32, NULL);
ret &= !secp256k1_scalar_is_zero(&k);
secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &r, &k);
/* We declassify r to allow using it as a branch point. This is fine
* because r is not a secret. */
secp256k1_declassify(ctx, &r, sizeof(r));
secp256k1_fe_normalize_var(&r.y);
if (secp256k1_fe_is_odd(&r.y)) {
secp256k1_scalar_negate(&k, &k);
}
secp256k1_fe_normalize_var(&r.x);
secp256k1_fe_get_b32(&sig64[0], &r.x);
secp256k1_chilldkg_schnorrsig_challenge(hash_ctx, &e, &sig64[0], msg, msglen, pk32, tag_prefix);
secp256k1_scalar_mul(&e, &e, &sk);
secp256k1_scalar_add(&e, &e, &k);
secp256k1_scalar_get_b32(&sig64[32], &e);
secp256k1_memczero(sig64, 64, !ret);
secp256k1_scalar_clear(&k);
secp256k1_scalar_clear(&sk);
secp256k1_scalar_clear(&e);
secp256k1_ge_clear(&pk);
secp256k1_ge_clear(&r);
secp256k1_memclear_explicit(seckey, sizeof(seckey));
secp256k1_memclear_explicit(nonce32, sizeof(nonce32));
return ret;
}
/* Mirrors schnorr_verify in secp256k1lab/bip340.py. The structure follows
* secp256k1_schnorrsig_verify, which cannot be reused directly because it
* hardcodes the "BIP0340/challenge" tag. */
static int secp256k1_chilldkg_schnorrsig_verify(const secp256k1_context *ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32, const char *tag_prefix) {
secp256k1_scalar s;
secp256k1_scalar e;
secp256k1_gej rj;
secp256k1_ge pk;
secp256k1_gej pkj;
secp256k1_fe rx;
secp256k1_ge r;
unsigned char buf[32];
int overflow;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(msg != NULL || msglen == 0);
ARG_CHECK(pubkey32 != NULL);
ARG_CHECK(tag_prefix != NULL);
if (!secp256k1_fe_set_b32_limit(&rx, &sig64[0])) {
return 0;
}
secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
if (overflow) {
return 0;
}
if (!secp256k1_chilldkg_xonly_load(&pk, pubkey32)) {
return 0;
}
/* Compute e. */
secp256k1_fe_get_b32(buf, &pk.x);
secp256k1_chilldkg_schnorrsig_challenge(secp256k1_get_hash_context(ctx), &e, &sig64[0], msg, msglen, buf, tag_prefix);
/* Compute rj = s*G + (-e)*pkj */
secp256k1_scalar_negate(&e, &e);
secp256k1_gej_set_ge(&pkj, &pk);
secp256k1_ecmult(&rj, &pkj, &e, &s);
secp256k1_ge_set_gej_var(&r, &rj);
if (secp256k1_ge_is_infinity(&r)) {
return 0;
}
secp256k1_fe_normalize_var(&r.y);
return !secp256k1_fe_is_odd(&r.y) &&
secp256k1_fe_equal(&rx, &r.x);
}
static void secp256k1_chilldkg_encpedpop_ecdh(const secp256k1_context *ctx, secp256k1_scalar *out, const secp256k1_scalar *seckey, const secp256k1_ge *their_point, const unsigned char *my_pubkey33, const unsigned char *their_pubkey33, const unsigned char *context, size_t context_len, int sending) {
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx);
secp256k1_gej resj;
secp256k1_ge res;
unsigned char x[32];
unsigned char y[32];
unsigned char shared[32];
unsigned char hash32[32];
secp256k1_sha256 sha;
VERIFY_CHECK(ctx != NULL);
VERIFY_CHECK(!secp256k1_scalar_is_zero(seckey));
VERIFY_CHECK(!secp256k1_ge_is_infinity(their_point));
/* libsecp256k1-style ECDH: SHA256 of the compressed shared point. */
secp256k1_ecmult_const(&resj, their_point, seckey);
secp256k1_ge_set_gej(&res, &resj);
/* The result cannot be the point at infinity: their_point is not
* infinity, seckey is nonzero, and the group has prime order. */
VERIFY_CHECK(!secp256k1_ge_is_infinity(&res));
secp256k1_fe_normalize(&res.x);
secp256k1_fe_normalize(&res.y);
secp256k1_fe_get_b32(x, &res.x);
secp256k1_fe_get_b32(y, &res.y);
/* This hash function always succeeds; call it unconditionally (it must
* not sit inside VERIFY_CHECK, which is compiled out in noverify
* builds). */
if (!ecdh_hash_function_sha256_impl(hash_ctx, shared, x, y, NULL)) {
VERIFY_CHECK(0);
memset(shared, 0, sizeof(shared));
}
secp256k1_chilldkg_sha256_tagged_encpedpop_ecdh(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, shared, 32);
/* The sender's pubnonce always comes first in the hash input. */
if (sending) {
secp256k1_sha256_write(hash_ctx, &sha, my_pubkey33, 33);
secp256k1_sha256_write(hash_ctx, &sha, their_pubkey33, 33);
} else {
secp256k1_sha256_write(hash_ctx, &sha, their_pubkey33, 33);
secp256k1_sha256_write(hash_ctx, &sha, my_pubkey33, 33);
}
secp256k1_sha256_write(hash_ctx, &sha, context, context_len);
secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
secp256k1_sha256_clear(&sha);
/* Pads are reduced modulo the group order (from_bytes_wrapping in the
* reference). */
secp256k1_scalar_set_b32(out, hash32, NULL);
secp256k1_memclear_explicit(x, sizeof(x));
secp256k1_memclear_explicit(y, sizeof(y));
secp256k1_memclear_explicit(shared, sizeof(shared));
secp256k1_memclear_explicit(hash32, sizeof(hash32));
secp256k1_ge_clear(&res);
secp256k1_gej_clear(&resj);
}
static void secp256k1_chilldkg_encpedpop_self_pad(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *out, const unsigned char *symkey32, const unsigned char *nonce33, const unsigned char *context, size_t context_len) {
unsigned char hash32[32];
secp256k1_sha256 sha;
secp256k1_chilldkg_sha256_tagged_self_pad(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, symkey32, 32);
secp256k1_sha256_write(hash_ctx, &sha, nonce33, 33);
secp256k1_sha256_write(hash_ctx, &sha, context, context_len);
secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
secp256k1_sha256_clear(&sha);
/* from_bytes_wrapping in the reference. */
secp256k1_scalar_set_b32(out, hash32, NULL);
secp256k1_memclear_explicit(hash32, sizeof(hash32));
}
static void secp256k1_chilldkg_params_hash(const secp256k1_hash_ctx *hash_ctx, unsigned char *out32, const unsigned char *hostpubkeys33, size_t n, uint32_t t) {
unsigned char buf[4];
secp256k1_sha256 sha;
secp256k1_chilldkg_sha256_tagged_params_hash(hash_ctx, &sha);
secp256k1_write_be32(buf, t);
secp256k1_sha256_write(hash_ctx, &sha, buf, sizeof(buf));
secp256k1_sha256_write(hash_ctx, &sha, hostpubkeys33, 33 * n);
secp256k1_sha256_finalize(hash_ctx, &sha, out32);
secp256k1_sha256_clear(&sha);
}
#endif

View File

@@ -0,0 +1,70 @@
/***********************************************************************
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_CHILLDKG_VSS_H
#define SECP256K1_MODULE_CHILLDKG_VSS_H
#include "../../../include/secp256k1.h"
#include "../../group.h"
#include "../../hash.h"
#include "../../scalar.h"
/* This file contains the internal verifiable secret sharing (VSS) primitives
* of the ChillDKG module, mirroring chilldkg_ref/vss.py of the bip-frost-dkg
* reference implementation.
*
* A polynomial f of degree at most t-1 is represented by an array of t
* coefficients, f(x) = coeffs[0] + coeffs[1]*x + ... + coeffs[t-1]*x^(t-1).
* A VSS commitment is an array of t group elements com[j] = coeffs[j]*G.
* Commitment entries may be the point at infinity (see VSSCommitment in the
* reference). */
/* Derive the t polynomial coefficients from a 32-byte seed (VSS.generate):
* coeffs[j] = Scalar.from_bytes_checked(TH("BIP DKG/vss coeffs", seed32 || u32be(j)))
* Returns 1 on success and 0 if some coefficient overflows the group order
* (negligible probability; the caller declassifies the return value). */
static int secp256k1_chilldkg_vss_gen_coeffs(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *coeffs, size_t t, const unsigned char *seed32);
/* Evaluate the polynomial at x via Horner's method (Polynomial.eval). */
static void secp256k1_chilldkg_vss_poly_eval(secp256k1_scalar *out, const secp256k1_scalar *coeffs, size_t t, const secp256k1_scalar *x);
/* Return the secret share for the participant with the given id, i.e.,
* f(id+1) (VSS.secshare_for). The addition of 1 is performed in scalar
* arithmetic so that id == UINT32_MAX cannot overflow. */
static void secp256k1_chilldkg_vss_secshare_for(secp256k1_scalar *out, const secp256k1_scalar *coeffs, size_t t, uint32_t id);
/* Compute the commitments com[j] = coeffs[j]*G (VSS.commit). A zero
* coefficient (negligible probability) yields the point at infinity. */
static void secp256k1_chilldkg_vss_commit(const secp256k1_context *ctx, secp256k1_ge *coms, const secp256k1_scalar *coeffs, size_t t);
/* Compute the public share of the participant with the given id
* (VSSCommitment.pubshare):
* out = sum_j (id+1)^j * coms[j]
* Commitment entries that are the point at infinity contribute nothing. All
* inputs are public, so variable-time algorithms are used. The result may be
* the point at infinity; the caller must handle that case. */
static void secp256k1_chilldkg_vss_pubshare(secp256k1_gej *out, const secp256k1_ge *coms, size_t t, uint32_t id);
/* Component-wise addition of two commitments (VSSCommitment.__add__):
* out[j] = a[j] + b[j]. All inputs are public. Entries in the result may be
* the point at infinity. */
static void secp256k1_chilldkg_vss_commitment_add(secp256k1_ge *out, const secp256k1_ge *a, const secp256k1_ge *b, size_t t);
/* Verify secshare*G == pubshare (VSSCommitment.verify_secshare). */
static int secp256k1_chilldkg_vss_verify_secshare(const secp256k1_context *ctx, const secp256k1_scalar *secshare, const secp256k1_ge *pubshare);
/* Tweak a VSS commitment such that the resulting threshold public key has an
* unspendable BIP 341 Taproot script path (VSSCommitment.invalid_taproot_commit):
* tweak = Scalar.from_bytes_checked(TH("TapTweak", xonly(coms[0])))
* out_coms[0] = coms[0] + tweak*G, out_coms[j] = coms[j] for j > 0
* The tweak must additionally be added to all secret shares of the
* commitment. Outputs the tweak and pubtweak = tweak*G (which may be the
* point at infinity if tweak is zero, with negligible probability). Returns 1
* on success and 0 if coms[0] is the point at infinity or the tweak hash
* overflows the group order (negligible probability). */
static int secp256k1_chilldkg_vss_invalid_taproot_commit(const secp256k1_context *ctx, secp256k1_ge *out_coms, secp256k1_scalar *tweak, secp256k1_ge *pubtweak, const secp256k1_ge *coms, size_t t);
#endif

View File

@@ -0,0 +1,173 @@
/***********************************************************************
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_CHILLDKG_VSS_IMPL_H
#define SECP256K1_MODULE_CHILLDKG_VSS_IMPL_H
#include "../../../include/secp256k1.h"
#include "util.h"
#include "vss.h"
#include "../../ecmult.h"
#include "../../util.h"
static int secp256k1_chilldkg_vss_gen_coeffs(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *coeffs, size_t t, const unsigned char *seed32) {
unsigned char buf[4];
unsigned char hash32[32];
secp256k1_sha256 sha;
size_t j;
/* Bitwise (not short-circuiting) so that this does not branch on the
* secret-derived overflow; the caller declassifies the return value. */
int ok = 1;
for (j = 0; j < t; j++) {
int overflow;
secp256k1_chilldkg_sha256_tagged_vss_coeffs(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, seed32, 32);
secp256k1_write_be32(buf, (uint32_t)j);
secp256k1_sha256_write(hash_ctx, &sha, buf, sizeof(buf));
secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
/* from_bytes_checked in the reference: coefficients that overflow the
* group order are rejected. */
secp256k1_scalar_set_b32(&coeffs[j], hash32, &overflow);
ok &= !overflow;
}
secp256k1_sha256_clear(&sha);
secp256k1_memclear_explicit(buf, sizeof(buf));
secp256k1_memclear_explicit(hash32, sizeof(hash32));
return ok;
}
static void secp256k1_chilldkg_vss_poly_eval(secp256k1_scalar *out, const secp256k1_scalar *coeffs, size_t t, const secp256k1_scalar *x) {
size_t j = t;
/* Horner's method, iterating over the coefficients from the
* highest-degree term down to the constant term. */
secp256k1_scalar_set_int(out, 0);
while (j > 0) {
j--;
secp256k1_scalar_mul(out, out, x);
secp256k1_scalar_add(out, out, &coeffs[j]);
}
}
static void secp256k1_chilldkg_vss_secshare_for(secp256k1_scalar *out, const secp256k1_scalar *coeffs, size_t t, uint32_t id) {
secp256k1_scalar x;
/* x = id + 1, computed in scalar arithmetic so that id == UINT32_MAX
* does not overflow. This never computes f(0) because x != 0 for all
* uint32_t ids. */
secp256k1_scalar_set_int(&x, id);
secp256k1_scalar_add(&x, &x, &secp256k1_scalar_one);
secp256k1_chilldkg_vss_poly_eval(out, coeffs, t, &x);
secp256k1_scalar_clear(&x);
}
static void secp256k1_chilldkg_vss_commit(const secp256k1_context *ctx, secp256k1_ge *coms, const secp256k1_scalar *coeffs, size_t t) {
size_t j;
VERIFY_CHECK(ctx != NULL);
for (j = 0; j < t; j++) {
secp256k1_gej comj;
/* Constant-time; a zero coefficient (negligible probability) results
* in the point at infinity, matching the reference. */
secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &comj, &coeffs[j]);
secp256k1_ge_set_gej(&coms[j], &comj);
secp256k1_gej_clear(&comj);
}
}
static void secp256k1_chilldkg_vss_pubshare(secp256k1_gej *out, const secp256k1_ge *coms, size_t t, uint32_t id) {
secp256k1_scalar x, power;
size_t j;
/* x = id + 1, computed in scalar arithmetic. All inputs are public, so
* variable-time algorithms and branches are fine. */
secp256k1_scalar_set_int(&x, id);
secp256k1_scalar_add(&x, &x, &secp256k1_scalar_one);
secp256k1_gej_set_infinity(out);
secp256k1_scalar_set_int(&power, 1);
for (j = 0; j < t; j++) {
if (!secp256k1_ge_is_infinity(&coms[j])) {
secp256k1_gej term;
secp256k1_gej_set_ge(&term, &coms[j]);
secp256k1_ecmult(&term, &term, &power, NULL);
secp256k1_gej_add_var(out, out, &term, NULL);
}
secp256k1_scalar_mul(&power, &power, &x);
}
secp256k1_scalar_clear(&x);
secp256k1_scalar_clear(&power);
}
static void secp256k1_chilldkg_vss_commitment_add(secp256k1_ge *out, const secp256k1_ge *a, const secp256k1_ge *b, size_t t) {
size_t j;
for (j = 0; j < t; j++) {
secp256k1_gej aj, bj, sumj;
secp256k1_gej_set_ge(&aj, &a[j]);
secp256k1_gej_set_ge(&bj, &b[j]);
/* gej_add_var handles operands at infinity. */
secp256k1_gej_add_var(&sumj, &aj, &bj, NULL);
secp256k1_ge_set_gej_var(&out[j], &sumj);
}
}
static int secp256k1_chilldkg_vss_verify_secshare(const secp256k1_context *ctx, const secp256k1_scalar *secshare, const secp256k1_ge *pubshare) {
secp256k1_ge actual;
VERIFY_CHECK(ctx != NULL);
secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &actual, secshare);
if (secp256k1_ge_is_infinity(&actual) || secp256k1_ge_is_infinity(pubshare)) {
return secp256k1_ge_is_infinity(&actual) && secp256k1_ge_is_infinity(pubshare);
}
return secp256k1_ge_eq_var(&actual, pubshare);
}
static int secp256k1_chilldkg_vss_invalid_taproot_commit(const secp256k1_context *ctx, secp256k1_ge *out_coms, secp256k1_scalar *tweak, secp256k1_ge *pubtweak, const secp256k1_ge *coms, size_t t) {
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx);
unsigned char pk32[32];
unsigned char hash32[32];
secp256k1_sha256 sha;
secp256k1_gej tweakj, com0j;
int overflow;
size_t j;
VERIFY_CHECK(ctx != NULL);
/* The commitment to the secret must not be infinity (the reference would
* fail to serialize it to x-only bytes). */
if (t == 0 || secp256k1_ge_is_infinity(&coms[0])) {
return 0;
}
secp256k1_chilldkg_xonly_save(pk32, &coms[0]);
secp256k1_chilldkg_sha256_tagged_taptweak(hash_ctx, &sha);
secp256k1_sha256_write(hash_ctx, &sha, pk32, 32);
secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
secp256k1_sha256_clear(&sha);
/* from_bytes_checked in the reference. The tweak is derived from public
* data, so everything here may be variable-time. */
secp256k1_scalar_set_b32(tweak, hash32, &overflow);
secp256k1_memclear_explicit(hash32, sizeof(hash32));
if (overflow) {
return 0;
}
/* pubtweak = tweak*G. This is the point at infinity iff tweak is zero
* (negligible probability), matching the reference. */
secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &tweakj, tweak);
secp256k1_ge_set_gej_var(pubtweak, &tweakj);
for (j = 1; j < t; j++) {
out_coms[j] = coms[j];
}
secp256k1_gej_set_ge(&com0j, &coms[0]);
secp256k1_gej_add_var(&tweakj, &tweakj, &com0j, NULL);
secp256k1_ge_set_gej_var(&out_coms[0], &tweakj);
return 1;
}
#endif