From e462f7c1ac3f8c1409c922c7dc1becd5b20bfd3c Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Mon, 31 Aug 2026 04:05:15 +0200 Subject: [PATCH] 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 /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. --- src/modules/chilldkg/Makefile.am.include | 4 + src/modules/chilldkg/main_impl.h | 3 + src/modules/chilldkg/tests_impl.h | 517 ++++++++++++++++++++++- src/modules/chilldkg/util.h | 99 +++++ src/modules/chilldkg/util_impl.h | 386 +++++++++++++++++ src/modules/chilldkg/vss.h | 70 +++ src/modules/chilldkg/vss_impl.h | 173 ++++++++ 7 files changed, 1246 insertions(+), 6 deletions(-) create mode 100644 src/modules/chilldkg/util.h create mode 100644 src/modules/chilldkg/util_impl.h create mode 100644 src/modules/chilldkg/vss.h create mode 100644 src/modules/chilldkg/vss_impl.h diff --git a/src/modules/chilldkg/Makefile.am.include b/src/modules/chilldkg/Makefile.am.include index 68cefaed..2eb5c770 100644 --- a/src/modules/chilldkg/Makefile.am.include +++ b/src/modules/chilldkg/Makefile.am.include @@ -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 diff --git a/src/modules/chilldkg/main_impl.h b/src/modules/chilldkg/main_impl.h index ca29e111..ec5eba29 100644 --- a/src/modules/chilldkg/main_impl.h +++ b/src/modules/chilldkg/main_impl.h @@ -8,4 +8,7 @@ #include "../../../include/secp256k1_chilldkg.h" +#include "util_impl.h" +#include "vss_impl.h" + #endif diff --git a/src/modules/chilldkg/tests_impl.h b/src/modules/chilldkg/tests_impl.h index fd0e42cc..a6c65ad4 100644 --- a/src/modules/chilldkg/tests_impl.h +++ b/src/modules/chilldkg/tests_impl.h @@ -6,16 +6,521 @@ #ifndef SECP256K1_MODULE_CHILLDKG_TESTS_IMPL_H #define SECP256K1_MODULE_CHILLDKG_TESTS_IMPL_H -#include "../../../include/secp256k1_chilldkg.h" +#include -/* 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 diff --git a/src/modules/chilldkg/util.h b/src/modules/chilldkg/util.h new file mode 100644 index 00000000..21689497 --- /dev/null +++ b/src/modules/chilldkg/util.h @@ -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 diff --git a/src/modules/chilldkg/util_impl.h b/src/modules/chilldkg/util_impl.h new file mode 100644 index 00000000..3b6cb29d --- /dev/null +++ b/src/modules/chilldkg/util_impl.h @@ -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 + +#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 diff --git a/src/modules/chilldkg/vss.h b/src/modules/chilldkg/vss.h new file mode 100644 index 00000000..5d44b5ae --- /dev/null +++ b/src/modules/chilldkg/vss.h @@ -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 diff --git a/src/modules/chilldkg/vss_impl.h b/src/modules/chilldkg/vss_impl.h new file mode 100644 index 00000000..94cb9a0e --- /dev/null +++ b/src/modules/chilldkg/vss_impl.h @@ -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