2020-12-17 08:33:49 +02:00
|
|
|
/***********************************************************************
|
|
|
|
|
* Copyright (c) 2020 Gregory Maxwell *
|
|
|
|
|
* Distributed under the MIT software license, see the accompanying *
|
|
|
|
|
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
|
|
|
|
***********************************************************************/
|
2020-01-08 11:56:15 +00:00
|
|
|
|
2021-02-04 23:17:09 +00:00
|
|
|
#include <stdio.h>
|
2025-02-14 13:43:29 +01:00
|
|
|
#include <stdlib.h>
|
2024-01-06 19:31:18 +00:00
|
|
|
#include <string.h>
|
2021-02-04 23:17:09 +00:00
|
|
|
|
2021-05-04 13:19:33 -04:00
|
|
|
#include "../include/secp256k1.h"
|
2020-08-12 15:52:20 -07:00
|
|
|
#include "assumptions.h"
|
2022-12-06 16:35:35 -05:00
|
|
|
#include "checkmem.h"
|
2020-01-08 11:56:15 +00:00
|
|
|
|
2022-12-06 16:41:35 -05:00
|
|
|
#if !SECP256K1_CHECKMEM_ENABLED
|
2022-12-06 18:41:47 -05:00
|
|
|
# error "This tool cannot be compiled without memory-checking interface (valgrind or msan)"
|
2022-12-06 16:41:35 -05:00
|
|
|
#endif
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_ECDH
|
2021-05-04 13:19:33 -04:00
|
|
|
# include "../include/secp256k1_ecdh.h"
|
2020-01-08 11:56:15 +00:00
|
|
|
#endif
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_RECOVERY
|
2021-05-04 13:19:33 -04:00
|
|
|
# include "../include/secp256k1_recovery.h"
|
2020-05-27 00:38:46 +03:00
|
|
|
#endif
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_EXTRAKEYS
|
2021-05-04 13:19:33 -04:00
|
|
|
# include "../include/secp256k1_extrakeys.h"
|
2020-05-12 14:52:34 +00:00
|
|
|
#endif
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_SCHNORRSIG
|
2021-05-04 13:19:33 -04:00
|
|
|
#include "../include/secp256k1_schnorrsig.h"
|
2020-03-12 20:05:07 +00:00
|
|
|
#endif
|
|
|
|
|
|
2024-01-06 19:31:18 +00:00
|
|
|
#ifdef ENABLE_MODULE_MUSIG
|
|
|
|
|
#include "../include/secp256k1_musig.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-11-04 15:52:12 -04:00
|
|
|
#ifdef ENABLE_MODULE_ELLSWIFT
|
|
|
|
|
#include "../include/secp256k1_ellswift.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-12-21 20:27:14 +00:00
|
|
|
#ifdef ENABLE_MODULE_ECDSA_S2C
|
2023-04-21 11:08:17 +02:00
|
|
|
#include "../include/secp256k1_ecdsa_s2c.h"
|
2020-12-21 20:27:14 +00:00
|
|
|
#endif
|
|
|
|
|
|
2021-03-05 01:03:43 -08:00
|
|
|
#ifdef ENABLE_MODULE_ECDSA_ADAPTOR
|
2023-04-21 11:08:17 +02:00
|
|
|
#include "../include/secp256k1_ecdsa_adaptor.h"
|
2021-03-05 01:03:43 -08:00
|
|
|
#endif
|
|
|
|
|
|
2026-06-12 19:10:33 +05:30
|
|
|
#if defined(__GNUC__)
|
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
|
# pragma GCC diagnostic warning "-Wunused-function"
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-08-31 00:05:16 +02:00
|
|
|
#ifdef ENABLE_MODULE_FROST
|
|
|
|
|
#include "../include/secp256k1_frost.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
chilldkg: CI wiring, ctime_tests coverage, declassify fixes
CI:
- ci/ci.sh: new CHILLDKG environment variable, passed to configure as
--enable-module-chilldkg (mirroring FROST).
- .github/workflows/ci.yml: default CHILLDKG: 'no' and CHILLDKG: 'yes'
in every job that enables FROST, except the x86_64 matrix entry that
deliberately builds without the ecdh module (chilldkg requires
schnorrsig + ecdh; the configure-time dependency error would fire
there). YAML validity and per-job dependency presence checked
programmatically.
ctime_tests:
- src/ctime_tests.c: run a full ChillDKG session (n = 2, t = 2) through
the public API under the memory checker: hostpubkey_gen, params_hash,
participant_step1, coordinator_step1, participant_step2,
coordinator_finalize, participant_finalize, participant_recover and
recovery_ack_sign. Host secret keys, session randomness, aux
randomness and the resulting secret shares are undefined (secret);
all protocol messages, the certificate, threshold public key, public
shares, recovery data, ack signature and the secret-free state1
objects are defined (public). state2 stays secret (contains the
secret share).
Constant-time fixes found by running the new block under
MemorySanitizer (valgrind unavailable locally; MSan build via clang +
CMake). All are missing declassifications of secret-derived but public
(or public-outcome) values, following the frost module's
secp256k1_declassify pattern with justification comments; no real
constant-time bugs were found:
- hostpubkey_gen: declassify the computed host public key before
serialization (public output).
- participant_step1: declassify the zero-randomness check result (only
reveals "the RNG returned 32 zero bytes", which aborts the session).
- encpedpop participant_step1: declassify the pubnonce point before
serialization (public, part of pmsg1).
- chilldkg_schnorrsig_sign: declassify the signer public key before
normalization/parity branch, and declassify the return value (a
failure only reveals a zero derived nonce, negligible probability).
- vss_commit: declassify the VSS commitments before serialization
(public, part of pmsg1).
- vss_verify_secshare: declassify secshare*G before the infinity/eq
checks (equals the public pubshare in honest runs; the discrete log
is not revealed).
- simplpedpop_participant_investigate (proactive audit; not reached by
ctime_tests): declassify the secshare-sum comparison result (the
public fault code reveals it anyway).
Verified: MSan ctime_tests exits 0; autotools make check 10/10 (the
local tree is configured without --enable-ctime-tests because neither
valgrind nor an MSan-instrumented gcc build is available; CI runs
ctime_tests under valgrind as before); CMake ctest 428/428;
./tests --target=chilldkg and ./chilldkg_example pass.
2026-08-31 10:25:14 +02:00
|
|
|
#ifdef ENABLE_MODULE_CHILLDKG
|
|
|
|
|
#include "../include/secp256k1_chilldkg.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
prefractal: document the deviations and add the constant-time test
doc/prefractal.md writes down the three deliberate deviations from BIP 445
where a reviewer will find them, since none of them is visible from the API
and two of them are actively counterintuitive:
1. b_frost does not commit to the message, because the target protocols
publish the group's wire nonce before the message exists. The outer b_musig
does commit to it and multiplies b_frost everywhere it appears.
2. There is no g_frost factor, and the reason is NOT that the tweak cache is
the identity. The frost key-side factor is g*gacc; an identity cache gives
gacc = 1, but g is still -1 for every odd-Y threshold key. The doc spells
this out because "identity cache, therefore no key term" is the plausible
wrong reason, and acting on it yields a signer that works for even-Y groups
and fails for odd-Y ones.
3. The frost tweak cache must be the identity, checked at signing and
verification and not only at aggregation, so the key a member signs under
is tied to the cache that was validated.
It also records the two caller obligations the module cannot enforce - one
secnonce per signature, and round-two signers EQUAL to round-one contributors
- and notes that iceberg tolerates a round-two subset where this module must
not, since callers moving between the two would otherwise transpose the rule.
The build section documents the three-way ordering constraint rather than
leaving the next person to copy iceberg's positions, which are wrong for
configure.ac.
The ctime test adds a 2-of-2 nested group with a stock musig cosigner, taken
as far as one signature share, marking the threshold key, the secret shares
and the session randomness as secret and everything else as public. Nonce
generation passes msg = NULL, which is how the module is actually driven.
Verified: valgrind -q ./build/bin/ctime_tests exits 0, so nothing in the
prefractal signing path branches on secret data. Full test suite green.
The example program from the plan's optional list is not included; the test
suite covers the same ground and the doc carries the usage rules.
2026-09-04 01:06:14 +02:00
|
|
|
#ifdef ENABLE_MODULE_PREFRACTAL
|
|
|
|
|
#include "../include/secp256k1_prefractal.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
frost_enrollment: implement the three rounds
Third of six commits. Replaces the Phase 1 stubs with the real
arithmetic, adds a smoke test that a 2-of-3 group really does grow into
a working 2-of-4 one, and wires the entry points into ctime_tests.
The Lagrange machinery is frost's, called in place. pubshare_derive is a
skin over secp256k1_frost_derive_pubshare_at
(src/modules/frost/keygen_impl.h:150) evaluated at identifier new_id,
and the id canonicalization is secp256k1_frost_sort_ids, reached through
the declaration the previous commit added.
The one piece frost could not supply is the scalar Lagrange coefficient
at an arbitrary point. frost's secp256k1_frost_derive_interpolating_value
evaluates at x-coordinate 0, which is what reconstructing the group
secret needs; enrollment needs the basis polynomial at the TARGET
x-coordinate. secp256k1_frost_enrollment_lagrange_at is that, and it is
deliberately the same product derive_pubshare_at applies to each
pubshare, in the same identifier space -- so the scalar path and the
point path agree by construction rather than by coincidence. Working in
identifier space is what makes the id-to-x-coordinate +1 cancel: an
x-coordinate difference x_j - x_i is the identifier difference
id_j - id_i.
Round 1.1 computes v = lambda * secshare and splits it. Every share but
the one kept locally is masking randomness derived as
Scalar.from_bytes_wrapping(
TH("FROST enrollment/share_split",
rand32 || params_hash32 || ser32(my_id) || ser32(recipient_id)))
with rand32 = TH(same tag, session_secrand32) XOR secshare32; the kept
share absorbs the remainder so the set sums to v. Three details:
- The reduction wraps rather than rejects, chilldkg's
from_bytes_wrapping (src/modules/chilldkg/util_impl.h:394). A 256-bit
hash mod the group order is about 2^-128 from uniform; rejection
sampling would buy that back in exchange for a variable-time loop.
- Masking with the secret share is the secp256k1_frost_nonce_gen pattern
(session_impl.h:340), so a broken RNG alone does not reveal the split.
- The derivation is indexed by the recipient's IDENTIFIER, not by its
position in the caller's ids array. The plan called for a counter;
identifiers are unique, so they are one, and using them makes the
split independent of the order a caller lists the helper set in. What
the binding buys is DOMAIN SEPARATION only: params_hash32 carries the
group key and the whole parameter tuple, so two runs sharing a seed
but differing in either cannot produce the same deltas. It cannot
detect a disagreement between helpers, because nothing cross-checks
per-helper private randomness. That is the params hash's job.
session_secrand32 is wiped whether the call succeeds or fails, so a
caller cannot retry a failed run on the same randomness.
Round 1.2 recomputes its own params hash from the group key and the
tuple, compares every received hash against it, then sums. The slot at
the caller's own position in received_params_hashes32 is skipped, while
the same position in all_shares32 is read -- the asymmetry the header
documents, and the thing that makes this a recomputation rather than a
string comparison. The mode and bounds are re-validated here rather than
trusted from the round 1.1 call site, since the full tuple is present.
An out-of-range share is reported through mismatch_id the way
secp256k1_frost_partial_sig_agg reports an unparseable partial
signature.
Round 2 compares the params hash against its own recomputation over the
authenticated group key, sums, rejects a zero share, and checks
secshare*G against the expected public share.
Three deviations from the plan, all to match what the tree already does:
- Value ranges return 0; only pointers get ARG_CHECK. The plan called
for an ARG_CHECK on the n_ids bound, but the frost module's split is
the one used here (secp256k1_frost_trusted_dealer_keygen,
keygen_impl.h:227), and the header already documents these as
return-0 conditions. The bound is still enforced in production builds
-- params_are_valid requires 2 <= threshold <= n_ids <= n_participants
<= 128 -- so it does not ride on the VERIFY_CHECK inside
secp256k1_frost_sort_ids, which is what the plan was guarding against.
- The public-share check declassifies the derived point and compares
with secp256k1_ge_eq_var, rather than comparing 33 serialized bytes in
constant time. There is no constant-time memcmp in this tree, and
secshare*G is a public key: secp256k1_frost_sign declassifies exactly
this quantity before exactly this comparison
(src/modules/frost/session_impl.h:770, :789). Inventing a primitive to
avoid following that precedent would be the worse trade.
- params_hash's public entry point delegates to the same internal
routine every gate uses, so the encoding has exactly one
implementation to keep in step with the vectors.
One real bug found by the tooling rather than by reading. Accumulators
were initialized with secp256k1_scalar_clear, and
secp256k1_memclear_explicit marks its target UNDEFINED in VERIFY builds
(src/util.h:295) precisely so that reading cleared memory is caught. It
was: valgrind reported 143752 errors in share_agg's summation loop.
Accumulators now start at secp256k1_scalar_set_int(x, 0); scalar_clear
is used only where it means "done with this secret". Worth stating
plainly because the failure mode is invisible in a production build,
where memclear_explicit only zeroes.
ctime_tests gains a 2-of-3-enrolls-a-fourth block covering all three
rounds, following prefractal's b66c757b. The threshold key, the secret
shares, the session randomness and every delta and sigma on the wire are
marked secret; the identifiers, public shares, group key, parameters
hashes and derived public share are not. Under valgrind: 0 errors from 0
contexts, so no branch or memory access in the new code depends on
secret data.
Verification: ./tests, ./noverify_tests and ./exhaustive_tests exit 0;
the frost_enrollment module runs clean under valgrind (0 errors); a
separate CPPFLAGS='-DVERIFY' build compiles without warnings and passes;
the module builds warning-free alongside frost, chilldkg, iceberg and
prefractal.
The smoke test is the substantive check: after a 2-of-3 group enrolls
participant 3, every pair {i, 3} for i in 0..2 reconstructs the original
threshold secret and matches the threshold public key, and the untouched
pair {0, 1} still does too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 04:06:59 +02:00
|
|
|
#ifdef ENABLE_MODULE_FROST_ENROLLMENT
|
|
|
|
|
#include "../include/secp256k1_frost_enrollment.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
iceberg: cover the module in ctime_tests
Port the source tree's own iceberg ctime_tests block (guarded by
ENABLE_MODULE_ICEBERG, after the chilldkg block): a 3-of-5 group run
through shares_gen, a share serialize/parse roundtrip,
share_cache_create (with a CHECKMEM_CHECK proving the cache holds no
secrets), pubshare_gen, pubkey_agg, nonce_gen, nonce_agg and
partial_sign, with share secrets undefined and all protocol outputs
defined, following the frost/chilldkg annotation style.
Both memory checkers pass with zero new declassifies needed -- the
module's constant-time layering (vpss variable-time but secret-free,
scalar_poly keeping secrets away from inversions) was already in
place. Verified: valgrind ./ctime_tests exits 0, and the clang
MemorySanitizer build exits 0 (with the chilldkg block unaffected).
2026-08-31 12:25:55 +02:00
|
|
|
#ifdef ENABLE_MODULE_ICEBERG
|
|
|
|
|
#include "../include/secp256k1_iceberg.h"
|
|
|
|
|
#include "../include/secp256k1_iceberg_dealer.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-01-09 12:02:27 -05:00
|
|
|
static void run_tests(secp256k1_context *ctx, unsigned char *key);
|
2021-02-04 23:17:09 +00:00
|
|
|
|
2020-01-08 11:56:15 +00:00
|
|
|
int main(void) {
|
|
|
|
|
secp256k1_context* ctx;
|
2021-02-04 23:17:09 +00:00
|
|
|
unsigned char key[32];
|
|
|
|
|
int ret, i;
|
|
|
|
|
|
2022-12-06 16:35:35 -05:00
|
|
|
if (!SECP256K1_CHECKMEM_RUNNING()) {
|
2023-01-18 09:02:47 +00:00
|
|
|
fprintf(stderr, "This test can only usefully be run inside valgrind because it was not compiled under msan.\n");
|
2026-01-22 10:28:30 -05:00
|
|
|
fprintf(stderr, "Usage: valgrind ./ctime_tests (or with Autotools: libtool --mode=execute valgrind ./ctime_tests)\n");
|
2025-02-14 13:43:29 +01:00
|
|
|
return EXIT_FAILURE;
|
2021-02-04 23:17:09 +00:00
|
|
|
}
|
2022-11-28 16:21:18 +00:00
|
|
|
ctx = secp256k1_context_create(SECP256K1_CONTEXT_DECLASSIFY);
|
2021-02-04 23:17:09 +00:00
|
|
|
/** In theory, testing with a single secret input should be sufficient:
|
|
|
|
|
* If control flow depended on secrets the tool would generate an error.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
|
key[i] = i + 65;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run_tests(ctx, key);
|
|
|
|
|
|
|
|
|
|
/* Test context randomisation. Do this last because it leaves the context
|
|
|
|
|
* tainted. */
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2021-02-04 23:17:09 +00:00
|
|
|
ret = secp256k1_context_randomize(ctx, key);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2021-02-04 23:17:09 +00:00
|
|
|
CHECK(ret);
|
|
|
|
|
|
|
|
|
|
secp256k1_context_destroy(ctx);
|
2025-02-14 13:43:29 +01:00
|
|
|
return EXIT_SUCCESS;
|
2021-02-04 23:17:09 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-09 12:02:27 -05:00
|
|
|
static void run_tests(secp256k1_context *ctx, unsigned char *key) {
|
2020-01-08 11:56:15 +00:00
|
|
|
secp256k1_ecdsa_signature signature;
|
|
|
|
|
secp256k1_pubkey pubkey;
|
|
|
|
|
size_t siglen = 74;
|
|
|
|
|
size_t outputlen = 33;
|
|
|
|
|
int i;
|
|
|
|
|
int ret;
|
|
|
|
|
unsigned char msg[32];
|
|
|
|
|
unsigned char sig[74];
|
|
|
|
|
unsigned char spubkey[33];
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_RECOVERY
|
2020-05-27 00:38:46 +03:00
|
|
|
secp256k1_ecdsa_recoverable_signature recoverable_signature;
|
|
|
|
|
int recid;
|
|
|
|
|
#endif
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_EXTRAKEYS
|
2020-05-12 14:52:34 +00:00
|
|
|
secp256k1_keypair keypair;
|
|
|
|
|
#endif
|
2022-11-04 15:52:12 -04:00
|
|
|
#ifdef ENABLE_MODULE_ELLSWIFT
|
|
|
|
|
unsigned char ellswift[64];
|
|
|
|
|
static const unsigned char prefix[64] = {'t', 'e', 's', 't'};
|
|
|
|
|
#endif
|
2020-01-08 11:56:15 +00:00
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
|
msg[i] = i + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Test keygen. */
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-01-08 11:56:15 +00:00
|
|
|
ret = secp256k1_ec_pubkey_create(ctx, &pubkey, key);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&pubkey, sizeof(secp256k1_pubkey));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret);
|
|
|
|
|
CHECK(secp256k1_ec_pubkey_serialize(ctx, spubkey, &outputlen, &pubkey, SECP256K1_EC_COMPRESSED) == 1);
|
|
|
|
|
|
|
|
|
|
/* Test signing. */
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-01-08 11:56:15 +00:00
|
|
|
ret = secp256k1_ecdsa_sign(ctx, &signature, msg, key, NULL, NULL);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&signature, sizeof(secp256k1_ecdsa_signature));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret);
|
|
|
|
|
CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, sig, &siglen, &signature));
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_ECDH
|
2020-01-08 11:56:15 +00:00
|
|
|
/* Test ECDH. */
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-01-08 11:56:15 +00:00
|
|
|
ret = secp256k1_ecdh(ctx, msg, &pubkey, key, NULL, NULL);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_RECOVERY
|
2020-05-27 00:38:46 +03:00
|
|
|
/* Test signing a recoverable signature. */
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-05-27 00:38:46 +03:00
|
|
|
ret = secp256k1_ecdsa_sign_recoverable(ctx, &recoverable_signature, msg, key, NULL, NULL);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&recoverable_signature, sizeof(recoverable_signature));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-05-27 00:38:46 +03:00
|
|
|
CHECK(ret);
|
|
|
|
|
CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &recoverable_signature));
|
|
|
|
|
CHECK(recid >= 0 && recid <= 3);
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-01-08 11:56:15 +00:00
|
|
|
ret = secp256k1_ec_seckey_verify(ctx, key);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2019-12-19 15:02:29 +00:00
|
|
|
ret = secp256k1_ec_seckey_negate(ctx, key);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(msg, 32);
|
2019-12-19 15:02:29 +00:00
|
|
|
ret = secp256k1_ec_seckey_tweak_add(ctx, key, msg);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(msg, 32);
|
2019-12-19 15:02:29 +00:00
|
|
|
ret = secp256k1_ec_seckey_tweak_mul(ctx, key, msg);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-01-08 11:56:15 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2020-07-22 09:09:34 +00:00
|
|
|
/* Test keypair_create and keypair_xonly_tweak_add. */
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_EXTRAKEYS
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-05-12 14:52:34 +00:00
|
|
|
ret = secp256k1_keypair_create(ctx, &keypair, key);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-05-12 14:52:34 +00:00
|
|
|
CHECK(ret == 1);
|
2020-07-22 09:09:34 +00:00
|
|
|
|
|
|
|
|
/* The tweak is not treated as a secret in keypair_tweak_add */
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(msg, 32);
|
2020-07-22 09:09:34 +00:00
|
|
|
ret = secp256k1_keypair_xonly_tweak_add(ctx, &keypair, msg);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-07-22 09:09:34 +00:00
|
|
|
CHECK(ret == 1);
|
2020-12-19 10:56:51 +02:00
|
|
|
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&keypair, sizeof(keypair));
|
2020-12-19 10:56:51 +02:00
|
|
|
ret = secp256k1_keypair_sec(ctx, key, &keypair);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-12-19 10:56:51 +02:00
|
|
|
CHECK(ret == 1);
|
2020-05-12 14:52:34 +00:00
|
|
|
#endif
|
|
|
|
|
|
2020-09-18 13:36:07 +02:00
|
|
|
#ifdef ENABLE_MODULE_SCHNORRSIG
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2020-03-12 20:05:07 +00:00
|
|
|
ret = secp256k1_keypair_create(ctx, &keypair, key);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-03-12 20:05:07 +00:00
|
|
|
CHECK(ret == 1);
|
2022-03-16 11:43:13 +01:00
|
|
|
ret = secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL);
|
2022-12-06 16:35:35 -05:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-03-12 20:05:07 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
#endif
|
2022-11-04 15:52:12 -04:00
|
|
|
|
2024-01-06 19:31:18 +00:00
|
|
|
#ifdef ENABLE_MODULE_MUSIG
|
|
|
|
|
{
|
|
|
|
|
secp256k1_pubkey pk;
|
|
|
|
|
const secp256k1_pubkey *pk_ptr[1];
|
|
|
|
|
secp256k1_xonly_pubkey agg_pk;
|
|
|
|
|
unsigned char session_secrand[32];
|
|
|
|
|
uint64_t nonrepeating_cnt = 0;
|
|
|
|
|
secp256k1_musig_secnonce secnonce;
|
|
|
|
|
secp256k1_musig_pubnonce pubnonce;
|
|
|
|
|
const secp256k1_musig_pubnonce *pubnonce_ptr[1];
|
|
|
|
|
secp256k1_musig_aggnonce aggnonce;
|
|
|
|
|
secp256k1_musig_keyagg_cache cache;
|
|
|
|
|
secp256k1_musig_session session;
|
|
|
|
|
secp256k1_musig_partial_sig partial_sig;
|
2026-02-13 14:03:05 +02:00
|
|
|
const secp256k1_musig_partial_sig *partial_sig_ptr[1];
|
2024-01-06 19:31:18 +00:00
|
|
|
unsigned char extra_input[32];
|
2026-02-13 14:03:05 +02:00
|
|
|
unsigned char sec_adaptor[32];
|
|
|
|
|
secp256k1_pubkey adaptor;
|
|
|
|
|
unsigned char pre_sig[64];
|
|
|
|
|
int nonce_parity;
|
2024-01-06 19:31:18 +00:00
|
|
|
|
|
|
|
|
pk_ptr[0] = &pk;
|
|
|
|
|
pubnonce_ptr[0] = &pubnonce;
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
|
|
|
|
memcpy(session_secrand, key, sizeof(session_secrand));
|
|
|
|
|
session_secrand[0] = session_secrand[0] + 1;
|
|
|
|
|
memcpy(extra_input, key, sizeof(extra_input));
|
|
|
|
|
extra_input[0] = extra_input[0] + 2;
|
2026-02-13 14:03:05 +02:00
|
|
|
memcpy(sec_adaptor, key, sizeof(sec_adaptor));
|
|
|
|
|
sec_adaptor[0] = extra_input[0] + 3;
|
|
|
|
|
partial_sig_ptr[0] = &partial_sig;
|
2024-01-06 19:31:18 +00:00
|
|
|
|
|
|
|
|
CHECK(secp256k1_keypair_create(ctx, &keypair, key));
|
|
|
|
|
CHECK(secp256k1_keypair_pub(ctx, &pk, &keypair));
|
|
|
|
|
CHECK(secp256k1_musig_pubkey_agg(ctx, &agg_pk, &cache, pk_ptr, 1));
|
2026-02-13 14:03:05 +02:00
|
|
|
CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, sec_adaptor));
|
2024-01-06 19:31:18 +00:00
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(session_secrand, sizeof(session_secrand));
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(extra_input, sizeof(extra_input));
|
2026-02-13 14:03:05 +02:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(sec_adaptor, sizeof(sec_adaptor));
|
2024-01-06 19:31:18 +00:00
|
|
|
ret = secp256k1_musig_nonce_gen(ctx, &secnonce, &pubnonce, session_secrand, key, &pk, msg, &cache, extra_input);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
ret = secp256k1_musig_nonce_gen_counter(ctx, &secnonce, &pubnonce, nonrepeating_cnt, &keypair, msg, &cache, extra_input);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
|
|
|
|
CHECK(secp256k1_musig_nonce_agg(ctx, &aggnonce, pubnonce_ptr, 1));
|
|
|
|
|
/* Make sure that previous tests don't undefine msg. It's not used as a secret here. */
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg));
|
2026-02-13 14:03:05 +02:00
|
|
|
CHECK(secp256k1_musig_nonce_process(ctx, &session, &aggnonce, msg, &cache, &adaptor) == 1);
|
2024-01-06 19:31:18 +00:00
|
|
|
|
|
|
|
|
ret = secp256k1_keypair_create(ctx, &keypair, key);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
ret = secp256k1_musig_partial_sign(ctx, &partial_sig, &secnonce, &keypair, &cache, &session);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
2026-02-13 14:03:05 +02:00
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&partial_sig, sizeof(partial_sig));
|
|
|
|
|
CHECK(secp256k1_musig_partial_sig_agg(ctx, pre_sig, &session, partial_sig_ptr, 1));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pre_sig, sizeof(pre_sig));
|
|
|
|
|
|
|
|
|
|
CHECK(secp256k1_musig_nonce_parity(ctx, &nonce_parity, &session));
|
|
|
|
|
ret = secp256k1_musig_adapt(ctx, sig, pre_sig, sec_adaptor, nonce_parity);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
ret = secp256k1_musig_extract_adaptor(ctx, sec_adaptor, sig, pre_sig, nonce_parity);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
2024-01-06 19:31:18 +00:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-11-04 15:52:12 -04:00
|
|
|
#ifdef ENABLE_MODULE_ELLSWIFT
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2022-11-04 15:52:12 -04:00
|
|
|
ret = secp256k1_ellswift_create(ctx, ellswift, key, NULL);
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2022-11-04 15:52:12 -04:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2022-11-04 15:52:12 -04:00
|
|
|
ret = secp256k1_ellswift_create(ctx, ellswift, key, ellswift);
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2022-11-04 15:52:12 -04:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ellswift, sizeof(ellswift));
|
2022-11-04 15:52:12 -04:00
|
|
|
ret = secp256k1_ellswift_xdh(ctx, msg, ellswift, ellswift, key, i, secp256k1_ellswift_xdh_hash_function_bip324, NULL);
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2022-11-04 15:52:12 -04:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ellswift, sizeof(ellswift));
|
2022-11-04 15:52:12 -04:00
|
|
|
ret = secp256k1_ellswift_xdh(ctx, msg, ellswift, ellswift, key, i, secp256k1_ellswift_xdh_hash_function_prefix, (void *)prefix);
|
2023-08-15 19:13:09 +02:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2022-11-04 15:52:12 -04:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
2023-07-26 15:19:08 +00:00
|
|
|
#endif
|
2022-11-04 15:52:12 -04:00
|
|
|
|
2020-12-21 20:27:14 +00:00
|
|
|
#ifdef ENABLE_MODULE_ECDSA_S2C
|
|
|
|
|
{
|
|
|
|
|
unsigned char s2c_data[32] = {0};
|
|
|
|
|
unsigned char s2c_data_comm[32] = {0};
|
|
|
|
|
secp256k1_ecdsa_s2c_opening s2c_opening;
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(s2c_data, 32);
|
2020-12-21 20:27:14 +00:00
|
|
|
ret = secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, msg, key, s2c_data);
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-12-21 20:27:14 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(s2c_data, 32);
|
2021-02-09 22:46:03 +00:00
|
|
|
ret = secp256k1_ecdsa_anti_exfil_host_commit(ctx, s2c_data_comm, s2c_data);
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-12-21 20:27:14 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(s2c_data, 32);
|
2021-02-09 22:46:03 +00:00
|
|
|
ret = secp256k1_ecdsa_anti_exfil_signer_commit(ctx, &s2c_opening, msg, key, s2c_data);
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2020-12-21 20:27:14 +00:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-03-05 01:03:43 -08:00
|
|
|
|
|
|
|
|
#ifdef ENABLE_MODULE_ECDSA_ADAPTOR
|
|
|
|
|
{
|
|
|
|
|
unsigned char adaptor_sig[162];
|
|
|
|
|
unsigned char deckey[32];
|
|
|
|
|
unsigned char expected_deckey[32];
|
|
|
|
|
secp256k1_pubkey enckey;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
|
deckey[i] = i + 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = secp256k1_ec_pubkey_create(ctx, &enckey, deckey);
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
2021-03-05 01:03:43 -08:00
|
|
|
ret = secp256k1_ecdsa_adaptor_encrypt(ctx, adaptor_sig, key, &enckey, msg, NULL, NULL);
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(adaptor_sig, sizeof(adaptor_sig));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2021-03-05 01:03:43 -08:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(deckey, 32);
|
2021-03-05 01:03:43 -08:00
|
|
|
ret = secp256k1_ecdsa_adaptor_decrypt(ctx, &signature, deckey, adaptor_sig);
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2021-03-05 01:03:43 -08:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&signature, 32);
|
2021-03-05 01:03:43 -08:00
|
|
|
ret = secp256k1_ecdsa_adaptor_recover(ctx, expected_deckey, &signature, adaptor_sig, &enckey);
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(expected_deckey, sizeof(expected_deckey));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2021-03-05 01:03:43 -08:00
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(deckey, sizeof(deckey));
|
2021-03-05 01:03:43 -08:00
|
|
|
ret = secp256k1_memcmp_var(deckey, expected_deckey, sizeof(expected_deckey));
|
2023-07-26 15:19:08 +00:00
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
2021-03-05 01:03:43 -08:00
|
|
|
CHECK(ret == 0);
|
|
|
|
|
}
|
2022-11-04 15:52:12 -04:00
|
|
|
#endif
|
2026-08-31 00:05:16 +02:00
|
|
|
|
|
|
|
|
#ifdef ENABLE_MODULE_FROST
|
|
|
|
|
{
|
|
|
|
|
unsigned char thresh_seckey[32];
|
|
|
|
|
unsigned char secshares[2 * 32];
|
|
|
|
|
secp256k1_pubkey thresh_pk;
|
|
|
|
|
secp256k1_pubkey pubshares[2];
|
|
|
|
|
uint32_t frost_ids[2] = { 0, 1 };
|
|
|
|
|
secp256k1_frost_tweak_cache cache;
|
|
|
|
|
secp256k1_frost_secnonce secnonce[2];
|
|
|
|
|
secp256k1_frost_pubnonce pubnonce[2];
|
|
|
|
|
const secp256k1_frost_pubnonce *pubnonce_ptrs[2];
|
|
|
|
|
secp256k1_frost_aggnonce aggnonce;
|
|
|
|
|
secp256k1_frost_session session;
|
|
|
|
|
secp256k1_frost_partial_sig partial_sig[2];
|
|
|
|
|
const secp256k1_frost_partial_sig *partial_sig_ptrs[2];
|
|
|
|
|
unsigned char session_secrand[2][32];
|
|
|
|
|
|
|
|
|
|
pubnonce_ptrs[0] = &pubnonce[0];
|
|
|
|
|
pubnonce_ptrs[1] = &pubnonce[1];
|
|
|
|
|
partial_sig_ptrs[0] = &partial_sig[0];
|
|
|
|
|
partial_sig_ptrs[1] = &partial_sig[1];
|
|
|
|
|
|
|
|
|
|
/* All public inputs are derived from defined memory. key is reused as
|
|
|
|
|
* the base of the (secret) threshold key and session randomness. */
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
|
|
|
|
memcpy(thresh_seckey, key, sizeof(thresh_seckey));
|
|
|
|
|
thresh_seckey[0] = thresh_seckey[0] + 1;
|
|
|
|
|
memcpy(session_secrand[0], key, 32);
|
|
|
|
|
session_secrand[0][0] = session_secrand[0][0] + 2;
|
|
|
|
|
memcpy(session_secrand[1], key, 32);
|
|
|
|
|
session_secrand[1][0] = session_secrand[1][0] + 3;
|
|
|
|
|
|
|
|
|
|
/* Test frost_trusted_dealer_keygen. The threshold secret key and the
|
|
|
|
|
* resulting secret shares are secret; the threshold public key and
|
|
|
|
|
* the public shares are public. */
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(thresh_seckey, sizeof(thresh_seckey));
|
|
|
|
|
ret = secp256k1_frost_trusted_dealer_keygen(ctx, secshares, &thresh_pk, pubshares, 2, 2, thresh_seckey);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&thresh_pk, sizeof(thresh_pk));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pubshares, sizeof(pubshares));
|
|
|
|
|
|
|
|
|
|
/* The session setup uses only public inputs. */
|
|
|
|
|
CHECK(secp256k1_frost_tweak_cache_init(ctx, &cache, &thresh_pk) == 1);
|
|
|
|
|
/* Make sure that previous tests don't undefine msg. It's not used as a secret here. */
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg));
|
|
|
|
|
|
|
|
|
|
/* Test frost_nonce_gen. The session randomness and the secret share
|
|
|
|
|
* are secret; the pubnonce is public. The secnonce stays secret. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(session_secrand[i], 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&secshares[32 * i], 32);
|
|
|
|
|
ret = secp256k1_frost_nonce_gen(ctx, &secnonce[i], &pubnonce[i], session_secrand[i], &secshares[32 * i], &pubshares[i], NULL, msg, 32, NULL, 0);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&pubnonce[i], sizeof(pubnonce[i]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHECK(secp256k1_frost_nonce_agg(ctx, &aggnonce, NULL, pubnonce_ptrs, 2) == 1);
|
|
|
|
|
CHECK(secp256k1_frost_session_init(ctx, &session, &aggnonce, frost_ids, pubshares, 2, 2, 2, &cache, msg, 32) == 1);
|
|
|
|
|
|
|
|
|
|
/* Test frost_sign. The secret share is secret (the secnonce is
|
|
|
|
|
* tainted through the tainted session randomness; its magic bytes
|
|
|
|
|
* must remain defined for the validity check). The partial signature
|
|
|
|
|
* is public. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&secshares[32 * i], 32);
|
|
|
|
|
ret = secp256k1_frost_sign(ctx, &partial_sig[i], &secnonce[i], &secshares[32 * i], &session, frost_ids, pubshares, 2, frost_ids[i]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&partial_sig[i], sizeof(partial_sig[i]));
|
|
|
|
|
}
|
|
|
|
|
CHECK(secp256k1_frost_partial_sig_agg(ctx, sig, NULL, &session, partial_sig_ptrs, 2) == 1);
|
frost: fix constant-time violations and C90 conformance
The module's BIP 445 logic itself is unchanged and was independently
validated against the pinned spec commit bb5396f (BIP v0.10.0), both via
the checked-in test vectors and via differential testing against the
Python reference over 360 randomised configurations (n up to 128,
shuffled non-contiguous signer ids, mixed xonly/plain tweak chains,
variable-length messages, pubshares present and absent). Every change
below is structural: the differential harness produces byte-identical
pubnonces, aggnonces, partial signatures and final signatures before and
after.
Two classes of problem prevented the module from passing CI.
1. Constant-time violations (ctime_tests)
-----------------------------------------
The CI matrix enables FROST in rows that also run
"valgrind --error-exitcode=42 ./ctime_tests" -- WITH_VALGRIND and
CTIMETESTS both default to 'yes'. With the module enabled that job
reported 639 "conditional jump depends on uninitialised value" errors,
all originating from two sites:
- secp256k1_frost_derive_coefficient returned
!overflow && !secp256k1_scalar_is_zero(out)
where the short-circuiting && branches on `overflow`, which is
derived from the threshold secret key. The caller declassifies the
return value, but the branch has already happened inside the callee.
Replaced with a bitwise &, matching the existing idiom in
secp256k1_scalar_set_b32_seckey (src/scalar_impl.h).
- secp256k1_frost_sign_internal performs the self-verification
recommended by BIP 445, which runs the *variable-time*
secp256k1_ecmult over the partial signature s. nonce_pts and pk were
already declassified ahead of that call; s was not. Since s is the
public output of the function, declassifying it before the
self-verification is both correct and sufficient.
secp256k1_frost_deterministic_sign carried three more instances of the
same class, invisible until now because ctime_tests did not exercise
that path at all:
- the `if (!valid)` check on secp256k1_scalar_set_b32_seckey lacked the
declassify that the identical checks in secp256k1_frost_nonce_gen and
secp256k1_frost_sign_internal already have;
- secp256k1_frost_det_nonce_function used the same short-circuiting &&,
here over the secret nonces;
- the branch on that function's result was not declassified.
The && in det_nonce_function is rewritten via two int locals rather than
a bare bitwise &: clang's -Wbitwise-instead-of-logical fires when both
operands are `!f(...)` expressions, which would break the -Werror clang
builds.
ctime_tests now also covers secp256k1_frost_deterministic_sign, so that
path stays checked from here on.
None of these leak anything of value in practice -- they reveal only
negligible-probability events (a hash overflowing the curve order, a zero
nonce) or whether a secret share is a valid secret key -- but they
violate the project's declassification discipline and fail the ctime
test.
2. C90 conformance (-Werror -pedantic-errors)
---------------------------------------------
The project targets C90 (CMAKE_C_STANDARD 90, -std=c89 -pedantic) and CI
passes WERROR_CFLAGS='-Werror -pedantic-errors'. Compiling src/tests.c
with those flags produced 62 errors in three groups:
- 40x "ISO C forbids empty initializer braces before C2X" in the
generated vectors.h; empty {} initializers are C23-only. Fixed in
tools/test_vectors_frost_generate.py so it survives regeneration:
hexstr_to_intarray now emits "0" for an empty byte string (all six
of its call sites wrap the result in braces), and init_group's
`counted` helper emits "{ 0 }" for an empty group. In every affected
slot the paired count/length field is 0, so the padding element is
never read.
- 1x "comma at end of enumerator list" (C99+), also in the generator.
- 21x "initializer element is not computable at load time" across 11
lines of tests_impl.h. C90 requires constant expressions in
initializers for automatic aggregates, so
const secp256k1_frost_pubnonce *ptrs[2] = { &a, &b };
is invalid. Rewritten as a declaration plus assignments, the style
the musig tests already use, which is why the pre-existing tree was
green.
vectors.h is regenerated from the spec's JSON vectors. Its hex payload is
byte-identical (verified by hashing every 0xNN token) and the file still
reproduces exactly from tools/test_vectors_frost_generate.py.
Verification
------------
- gcc and clang, -std=c89 -pedantic-errors -Werror, with and without
-DVERIFY: clean (was 62 errors)
- ctime_tests under MemorySanitizer: 0 reports (was 639); exits 0 with
halt_on_error=1
- tests, noverify_tests and frost_example: pass
- vectors.h regenerates identically from the pinned spec vectors
- 240 signing + 120 deterministic-signing differential cases against
the BIP 445 Python reference: byte-identical to the pre-fix build
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:57:43 +02:00
|
|
|
|
|
|
|
|
/* Test frost_deterministic_sign. The secret share is secret; the
|
|
|
|
|
* pubnonce and the partial signature are public. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
secp256k1_frost_pubnonce det_pubnonce;
|
|
|
|
|
secp256k1_frost_partial_sig det_partial_sig;
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&secshares[32 * i], 32);
|
|
|
|
|
ret = secp256k1_frost_deterministic_sign(ctx, &det_partial_sig, &det_pubnonce, &secshares[32 * i], frost_ids[i], &aggnonce, frost_ids, pubshares, 2, 2, 2, &cache, msg, 32, NULL);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&det_pubnonce, sizeof(det_pubnonce));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&det_partial_sig, sizeof(det_partial_sig));
|
|
|
|
|
}
|
2026-08-31 00:05:16 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
chilldkg: CI wiring, ctime_tests coverage, declassify fixes
CI:
- ci/ci.sh: new CHILLDKG environment variable, passed to configure as
--enable-module-chilldkg (mirroring FROST).
- .github/workflows/ci.yml: default CHILLDKG: 'no' and CHILLDKG: 'yes'
in every job that enables FROST, except the x86_64 matrix entry that
deliberately builds without the ecdh module (chilldkg requires
schnorrsig + ecdh; the configure-time dependency error would fire
there). YAML validity and per-job dependency presence checked
programmatically.
ctime_tests:
- src/ctime_tests.c: run a full ChillDKG session (n = 2, t = 2) through
the public API under the memory checker: hostpubkey_gen, params_hash,
participant_step1, coordinator_step1, participant_step2,
coordinator_finalize, participant_finalize, participant_recover and
recovery_ack_sign. Host secret keys, session randomness, aux
randomness and the resulting secret shares are undefined (secret);
all protocol messages, the certificate, threshold public key, public
shares, recovery data, ack signature and the secret-free state1
objects are defined (public). state2 stays secret (contains the
secret share).
Constant-time fixes found by running the new block under
MemorySanitizer (valgrind unavailable locally; MSan build via clang +
CMake). All are missing declassifications of secret-derived but public
(or public-outcome) values, following the frost module's
secp256k1_declassify pattern with justification comments; no real
constant-time bugs were found:
- hostpubkey_gen: declassify the computed host public key before
serialization (public output).
- participant_step1: declassify the zero-randomness check result (only
reveals "the RNG returned 32 zero bytes", which aborts the session).
- encpedpop participant_step1: declassify the pubnonce point before
serialization (public, part of pmsg1).
- chilldkg_schnorrsig_sign: declassify the signer public key before
normalization/parity branch, and declassify the return value (a
failure only reveals a zero derived nonce, negligible probability).
- vss_commit: declassify the VSS commitments before serialization
(public, part of pmsg1).
- vss_verify_secshare: declassify secshare*G before the infinity/eq
checks (equals the public pubshare in honest runs; the discrete log
is not revealed).
- simplpedpop_participant_investigate (proactive audit; not reached by
ctime_tests): declassify the secshare-sum comparison result (the
public fault code reveals it anyway).
Verified: MSan ctime_tests exits 0; autotools make check 10/10 (the
local tree is configured without --enable-ctime-tests because neither
valgrind nor an MSan-instrumented gcc build is available; CI runs
ctime_tests under valgrind as before); CMake ctest 428/428;
./tests --target=chilldkg and ./chilldkg_example pass.
2026-08-31 10:25:14 +02:00
|
|
|
|
|
|
|
|
#ifdef ENABLE_MODULE_CHILLDKG
|
|
|
|
|
{
|
|
|
|
|
/* Full ChillDKG session with n = 2, t = 2 (pmsg1: 227 bytes, cmsg1:
|
|
|
|
|
* 357 bytes, cert: 128 bytes, recovery data: 394 bytes). */
|
|
|
|
|
unsigned char hostseckeys[2][32];
|
|
|
|
|
unsigned char hostpubkeys[2 * 33];
|
|
|
|
|
unsigned char dkg_random[2][32];
|
|
|
|
|
unsigned char aux_rands[2][32];
|
|
|
|
|
secp256k1_chilldkg_participant_state1 state1[2];
|
|
|
|
|
secp256k1_chilldkg_participant_state2 state2[2];
|
|
|
|
|
secp256k1_chilldkg_coordinator_state cstate;
|
|
|
|
|
unsigned char pmsgs1[2][227];
|
|
|
|
|
const unsigned char *pmsgs1_ptrs[2];
|
|
|
|
|
unsigned char cmsg1[357];
|
|
|
|
|
unsigned char pmsgs2[2][64];
|
|
|
|
|
const unsigned char *pmsgs2_ptrs[2];
|
|
|
|
|
unsigned char cmsg2[128];
|
|
|
|
|
unsigned char secshare32[32];
|
|
|
|
|
unsigned char thresh_pk33[33];
|
|
|
|
|
unsigned char pubshares33[2 * 33];
|
|
|
|
|
unsigned char recovery[394];
|
|
|
|
|
unsigned char rec_secshare32[32];
|
|
|
|
|
unsigned char rec_thresh_pk33[33];
|
|
|
|
|
unsigned char rec_pubshares33[33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
|
|
|
|
|
unsigned char rec_hostpubkeys33[33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
|
|
|
|
|
unsigned char ack_sig[64];
|
|
|
|
|
size_t rec_n;
|
|
|
|
|
uint32_t rec_t;
|
|
|
|
|
uint32_t fault_index;
|
|
|
|
|
secp256k1_chilldkg_fault fault;
|
|
|
|
|
|
|
|
|
|
pmsgs1_ptrs[0] = pmsgs1[0];
|
|
|
|
|
pmsgs1_ptrs[1] = pmsgs1[1];
|
|
|
|
|
pmsgs2_ptrs[0] = pmsgs2[0];
|
|
|
|
|
pmsgs2_ptrs[1] = pmsgs2[1];
|
|
|
|
|
|
|
|
|
|
/* All public inputs are derived from defined memory. key is reused as
|
|
|
|
|
* the base of the (secret) host keys, session randomness and
|
|
|
|
|
* auxiliary randomness. */
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
memcpy(hostseckeys[i], key, 32);
|
|
|
|
|
hostseckeys[i][0] = hostseckeys[i][0] + 4 + i;
|
|
|
|
|
memcpy(dkg_random[i], key, 32);
|
|
|
|
|
dkg_random[i][0] = dkg_random[i][0] + 6 + i;
|
|
|
|
|
memcpy(aux_rands[i], key, 32);
|
|
|
|
|
aux_rands[i][0] = aux_rands[i][0] + 8 + i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Test chilldkg_hostpubkey_gen. The host secret keys are secret; the
|
|
|
|
|
* host public keys are public. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(hostseckeys[i], 32);
|
|
|
|
|
ret = secp256k1_chilldkg_hostpubkey_gen(ctx, &hostpubkeys[33 * i], hostseckeys[i]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&hostpubkeys[33 * i], 33);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Test chilldkg_participant_step1. The host secret key and the
|
|
|
|
|
* session randomness are secret; pmsg1 and the state1 object (which
|
|
|
|
|
* contains no secrets) are public. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(hostseckeys[i], 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(dkg_random[i], 32);
|
|
|
|
|
ret = secp256k1_chilldkg_participant_step1(ctx, &state1[i], pmsgs1[i], hostseckeys[i], hostpubkeys, 2, 2, dkg_random[i]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pmsgs1[i], sizeof(pmsgs1[i]));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&state1[i], sizeof(state1[i]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The coordinator's steps use only public inputs. */
|
|
|
|
|
fault = secp256k1_chilldkg_coordinator_step1(ctx, &cstate, cmsg1, &fault_index, pmsgs1_ptrs, hostpubkeys, 2, 2);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&fault, sizeof(fault));
|
|
|
|
|
CHECK(fault == SECP256K1_CHILLDKG_OK);
|
|
|
|
|
|
|
|
|
|
/* Test chilldkg_participant_step2. The host secret key and the aux
|
|
|
|
|
* randomness are secret; the CertEq signature is public. The state2
|
|
|
|
|
* object contains the secret share and stays secret. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(hostseckeys[i], 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(aux_rands[i], 32);
|
|
|
|
|
fault = secp256k1_chilldkg_participant_step2(ctx, &state2[i], pmsgs2[i], &fault_index, NULL, &state1[i], hostseckeys[i], cmsg1, aux_rands[i]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&fault, sizeof(fault));
|
|
|
|
|
CHECK(fault == SECP256K1_CHILLDKG_OK);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pmsgs2[i], sizeof(pmsgs2[i]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fault = secp256k1_chilldkg_coordinator_finalize(ctx, cmsg2, thresh_pk33, pubshares33, recovery, &fault_index, &cstate, pmsgs2_ptrs);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&fault, sizeof(fault));
|
|
|
|
|
CHECK(fault == SECP256K1_CHILLDKG_OK);
|
|
|
|
|
|
|
|
|
|
/* Test chilldkg_participant_finalize. The state2 input and the
|
|
|
|
|
* secshare output are secret; the threshold public key, the public
|
|
|
|
|
* shares and the recovery data are public. */
|
|
|
|
|
fault = secp256k1_chilldkg_participant_finalize(ctx, secshare32, thresh_pk33, pubshares33, recovery, &fault_index, &state2[0], cmsg2);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&fault, sizeof(fault));
|
|
|
|
|
CHECK(fault == SECP256K1_CHILLDKG_OK);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(thresh_pk33, sizeof(thresh_pk33));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pubshares33, sizeof(pubshares33));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(recovery, sizeof(recovery));
|
|
|
|
|
|
|
|
|
|
/* Test chilldkg_participant_recover. The host secret key and the
|
|
|
|
|
* recovered secret share are secret; the recovery data and the
|
|
|
|
|
* remaining outputs are public. */
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(hostseckeys[0], 32);
|
|
|
|
|
fault = secp256k1_chilldkg_participant_recover(ctx, rec_secshare32, rec_thresh_pk33, rec_pubshares33, rec_hostpubkeys33, &rec_n, &rec_t, &fault_index, hostseckeys[0], recovery, sizeof(recovery));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&fault, sizeof(fault));
|
|
|
|
|
CHECK(fault == SECP256K1_CHILLDKG_OK);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(rec_thresh_pk33, sizeof(rec_thresh_pk33));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(rec_pubshares33, 2 * 33);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(rec_hostpubkeys33, 2 * 33);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&rec_n, sizeof(rec_n));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&rec_t, sizeof(rec_t));
|
|
|
|
|
CHECK(rec_n == 2 && rec_t == 2);
|
|
|
|
|
|
|
|
|
|
/* Test chilldkg_recovery_ack_sign. The host secret key and the aux
|
|
|
|
|
* randomness are secret; the acknowledgment signature is public. */
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(hostseckeys[0], 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(aux_rands[0], 32);
|
|
|
|
|
ret = secp256k1_chilldkg_recovery_ack_sign(ctx, ack_sig, hostseckeys[0], hostpubkeys, 2, 2, recovery, sizeof(recovery), aux_rands[0]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(ack_sig, sizeof(ack_sig));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
iceberg: cover the module in ctime_tests
Port the source tree's own iceberg ctime_tests block (guarded by
ENABLE_MODULE_ICEBERG, after the chilldkg block): a 3-of-5 group run
through shares_gen, a share serialize/parse roundtrip,
share_cache_create (with a CHECKMEM_CHECK proving the cache holds no
secrets), pubshare_gen, pubkey_agg, nonce_gen, nonce_agg and
partial_sign, with share secrets undefined and all protocol outputs
defined, following the frost/chilldkg annotation style.
Both memory checkers pass with zero new declassifies needed -- the
module's constant-time layering (vpss variable-time but secret-free,
scalar_poly keeping secrets away from inversions) was already in
place. Verified: valgrind ./ctime_tests exits 0, and the clang
MemorySanitizer build exits 0 (with the chilldkg block unaffected).
2026-08-31 12:25:55 +02:00
|
|
|
|
prefractal: document the deviations and add the constant-time test
doc/prefractal.md writes down the three deliberate deviations from BIP 445
where a reviewer will find them, since none of them is visible from the API
and two of them are actively counterintuitive:
1. b_frost does not commit to the message, because the target protocols
publish the group's wire nonce before the message exists. The outer b_musig
does commit to it and multiplies b_frost everywhere it appears.
2. There is no g_frost factor, and the reason is NOT that the tweak cache is
the identity. The frost key-side factor is g*gacc; an identity cache gives
gacc = 1, but g is still -1 for every odd-Y threshold key. The doc spells
this out because "identity cache, therefore no key term" is the plausible
wrong reason, and acting on it yields a signer that works for even-Y groups
and fails for odd-Y ones.
3. The frost tweak cache must be the identity, checked at signing and
verification and not only at aggregation, so the key a member signs under
is tied to the cache that was validated.
It also records the two caller obligations the module cannot enforce - one
secnonce per signature, and round-two signers EQUAL to round-one contributors
- and notes that iceberg tolerates a round-two subset where this module must
not, since callers moving between the two would otherwise transpose the rule.
The build section documents the three-way ordering constraint rather than
leaving the next person to copy iceberg's positions, which are wrong for
configure.ac.
The ctime test adds a 2-of-2 nested group with a stock musig cosigner, taken
as far as one signature share, marking the threshold key, the secret shares
and the session randomness as secret and everything else as public. Nonce
generation passes msg = NULL, which is how the module is actually driven.
Verified: valgrind -q ./build/bin/ctime_tests exits 0, so nothing in the
prefractal signing path branches on secret data. Full test suite green.
The example program from the plan's optional list is not included; the test
suite covers the same ground and the doc carries the usage rules.
2026-09-04 01:06:14 +02:00
|
|
|
#ifdef ENABLE_MODULE_PREFRACTAL
|
|
|
|
|
{
|
|
|
|
|
/* A 2-of-2 nested group with one stock musig cosigner, taken as far as
|
|
|
|
|
* one signature share. Secret here is the threshold key, the secret
|
|
|
|
|
* shares derived from it, and the session randomness the nonces come
|
|
|
|
|
* from. Not secret: the identifiers, the public shares, the threshold
|
|
|
|
|
* public key, both aggregate nonces, the outer keyagg cache, b_frost,
|
|
|
|
|
* and the resulting partial signature. */
|
|
|
|
|
unsigned char thresh_seckey[32];
|
|
|
|
|
unsigned char secshares[2 * 32];
|
|
|
|
|
unsigned char session_secrand[2][32];
|
|
|
|
|
unsigned char cosigner_seckey[32];
|
|
|
|
|
secp256k1_pubkey thresh_pk, pubshares[2], cosigner_pk;
|
|
|
|
|
const secp256k1_pubkey *outer_pubkeys[2];
|
|
|
|
|
uint32_t pf_ids[2] = { 0, 1 };
|
|
|
|
|
secp256k1_frost_tweak_cache pf_cache;
|
|
|
|
|
secp256k1_frost_secnonce pf_secnonce[2];
|
|
|
|
|
secp256k1_frost_pubnonce pf_pubnonce[2];
|
|
|
|
|
const secp256k1_frost_pubnonce *pf_pubnonce_ptrs[2];
|
|
|
|
|
secp256k1_frost_aggnonce pf_aggnonce;
|
|
|
|
|
secp256k1_frost_partial_sig pf_partial_sig;
|
|
|
|
|
secp256k1_xonly_pubkey outer_xonly;
|
|
|
|
|
secp256k1_musig_keyagg_cache outer_cache;
|
|
|
|
|
secp256k1_musig_pubnonce group_pubnonce, cosigner_pubnonce;
|
|
|
|
|
const secp256k1_musig_pubnonce *just_cosigner[1];
|
|
|
|
|
secp256k1_musig_secnonce cosigner_secnonce;
|
|
|
|
|
secp256k1_musig_aggnonce cosigner_aggnonce;
|
|
|
|
|
unsigned char cosigner_secrand[32];
|
|
|
|
|
|
|
|
|
|
pf_pubnonce_ptrs[0] = &pf_pubnonce[0];
|
|
|
|
|
pf_pubnonce_ptrs[1] = &pf_pubnonce[1];
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
|
|
|
|
memcpy(thresh_seckey, key, sizeof(thresh_seckey));
|
|
|
|
|
thresh_seckey[0] = thresh_seckey[0] + 4;
|
|
|
|
|
memcpy(cosigner_seckey, key, sizeof(cosigner_seckey));
|
|
|
|
|
cosigner_seckey[0] = cosigner_seckey[0] + 5;
|
|
|
|
|
memcpy(session_secrand[0], key, 32);
|
|
|
|
|
session_secrand[0][0] = session_secrand[0][0] + 6;
|
|
|
|
|
memcpy(session_secrand[1], key, 32);
|
|
|
|
|
session_secrand[1][0] = session_secrand[1][0] + 7;
|
|
|
|
|
memcpy(cosigner_secrand, key, 32);
|
|
|
|
|
cosigner_secrand[0] = cosigner_secrand[0] + 8;
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg));
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(thresh_seckey, sizeof(thresh_seckey));
|
|
|
|
|
ret = secp256k1_frost_trusted_dealer_keygen(ctx, secshares, &thresh_pk, pubshares, 2, 2, thresh_seckey);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&thresh_pk, sizeof(thresh_pk));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pubshares, sizeof(pubshares));
|
|
|
|
|
CHECK(secp256k1_frost_tweak_cache_init(ctx, &pf_cache, &thresh_pk) == 1);
|
|
|
|
|
|
|
|
|
|
/* The outer aggregation and the cosigner's round one are entirely
|
|
|
|
|
* public as far as this module is concerned. */
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(cosigner_seckey, sizeof(cosigner_seckey));
|
|
|
|
|
ret = secp256k1_ec_pubkey_create(ctx, &cosigner_pk, cosigner_seckey);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&cosigner_pk, sizeof(cosigner_pk));
|
|
|
|
|
outer_pubkeys[0] = &thresh_pk;
|
|
|
|
|
outer_pubkeys[1] = &cosigner_pk;
|
|
|
|
|
CHECK(secp256k1_musig_pubkey_agg(ctx, &outer_xonly, &outer_cache, outer_pubkeys, 2) == 1);
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(cosigner_secrand, sizeof(cosigner_secrand));
|
|
|
|
|
ret = secp256k1_musig_nonce_gen(ctx, &cosigner_secnonce, &cosigner_pubnonce, cosigner_secrand, cosigner_seckey, &cosigner_pk, msg, &outer_cache, NULL);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&cosigner_pubnonce, sizeof(cosigner_pubnonce));
|
|
|
|
|
just_cosigner[0] = &cosigner_pubnonce;
|
|
|
|
|
CHECK(secp256k1_musig_nonce_agg(ctx, &cosigner_aggnonce, just_cosigner, 1) == 1);
|
|
|
|
|
|
|
|
|
|
/* Group round one. msg is NULL: the wire nonce is published before the
|
|
|
|
|
* message is known. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(session_secrand[i], 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&secshares[32 * i], 32);
|
|
|
|
|
ret = secp256k1_frost_nonce_gen(ctx, &pf_secnonce[i], &pf_pubnonce[i], session_secrand[i], &secshares[32 * i], &pubshares[i], NULL, NULL, 0, NULL, 0);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&pf_pubnonce[i], sizeof(pf_pubnonce[i]));
|
|
|
|
|
}
|
|
|
|
|
/* Aggregation is over published nonces only, so it is public. */
|
|
|
|
|
CHECK(secp256k1_prefractal_nonce_agg(ctx, &group_pubnonce, &pf_aggnonce, pf_pubnonce_ptrs, pf_ids, 2, &thresh_pk) == 1);
|
|
|
|
|
|
|
|
|
|
/* The share and the secnonce are secret; the partial signature is the
|
|
|
|
|
* public output. */
|
|
|
|
|
ret = secp256k1_prefractal_sign(ctx, &pf_partial_sig, &pf_secnonce[0], &secshares[0], pf_ids[0], pf_ids, pubshares, 2, &pf_aggnonce, &thresh_pk, &pf_cache, &outer_cache, &cosigner_aggnonce, msg);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&pf_partial_sig, sizeof(pf_partial_sig));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
frost_enrollment: implement the three rounds
Third of six commits. Replaces the Phase 1 stubs with the real
arithmetic, adds a smoke test that a 2-of-3 group really does grow into
a working 2-of-4 one, and wires the entry points into ctime_tests.
The Lagrange machinery is frost's, called in place. pubshare_derive is a
skin over secp256k1_frost_derive_pubshare_at
(src/modules/frost/keygen_impl.h:150) evaluated at identifier new_id,
and the id canonicalization is secp256k1_frost_sort_ids, reached through
the declaration the previous commit added.
The one piece frost could not supply is the scalar Lagrange coefficient
at an arbitrary point. frost's secp256k1_frost_derive_interpolating_value
evaluates at x-coordinate 0, which is what reconstructing the group
secret needs; enrollment needs the basis polynomial at the TARGET
x-coordinate. secp256k1_frost_enrollment_lagrange_at is that, and it is
deliberately the same product derive_pubshare_at applies to each
pubshare, in the same identifier space -- so the scalar path and the
point path agree by construction rather than by coincidence. Working in
identifier space is what makes the id-to-x-coordinate +1 cancel: an
x-coordinate difference x_j - x_i is the identifier difference
id_j - id_i.
Round 1.1 computes v = lambda * secshare and splits it. Every share but
the one kept locally is masking randomness derived as
Scalar.from_bytes_wrapping(
TH("FROST enrollment/share_split",
rand32 || params_hash32 || ser32(my_id) || ser32(recipient_id)))
with rand32 = TH(same tag, session_secrand32) XOR secshare32; the kept
share absorbs the remainder so the set sums to v. Three details:
- The reduction wraps rather than rejects, chilldkg's
from_bytes_wrapping (src/modules/chilldkg/util_impl.h:394). A 256-bit
hash mod the group order is about 2^-128 from uniform; rejection
sampling would buy that back in exchange for a variable-time loop.
- Masking with the secret share is the secp256k1_frost_nonce_gen pattern
(session_impl.h:340), so a broken RNG alone does not reveal the split.
- The derivation is indexed by the recipient's IDENTIFIER, not by its
position in the caller's ids array. The plan called for a counter;
identifiers are unique, so they are one, and using them makes the
split independent of the order a caller lists the helper set in. What
the binding buys is DOMAIN SEPARATION only: params_hash32 carries the
group key and the whole parameter tuple, so two runs sharing a seed
but differing in either cannot produce the same deltas. It cannot
detect a disagreement between helpers, because nothing cross-checks
per-helper private randomness. That is the params hash's job.
session_secrand32 is wiped whether the call succeeds or fails, so a
caller cannot retry a failed run on the same randomness.
Round 1.2 recomputes its own params hash from the group key and the
tuple, compares every received hash against it, then sums. The slot at
the caller's own position in received_params_hashes32 is skipped, while
the same position in all_shares32 is read -- the asymmetry the header
documents, and the thing that makes this a recomputation rather than a
string comparison. The mode and bounds are re-validated here rather than
trusted from the round 1.1 call site, since the full tuple is present.
An out-of-range share is reported through mismatch_id the way
secp256k1_frost_partial_sig_agg reports an unparseable partial
signature.
Round 2 compares the params hash against its own recomputation over the
authenticated group key, sums, rejects a zero share, and checks
secshare*G against the expected public share.
Three deviations from the plan, all to match what the tree already does:
- Value ranges return 0; only pointers get ARG_CHECK. The plan called
for an ARG_CHECK on the n_ids bound, but the frost module's split is
the one used here (secp256k1_frost_trusted_dealer_keygen,
keygen_impl.h:227), and the header already documents these as
return-0 conditions. The bound is still enforced in production builds
-- params_are_valid requires 2 <= threshold <= n_ids <= n_participants
<= 128 -- so it does not ride on the VERIFY_CHECK inside
secp256k1_frost_sort_ids, which is what the plan was guarding against.
- The public-share check declassifies the derived point and compares
with secp256k1_ge_eq_var, rather than comparing 33 serialized bytes in
constant time. There is no constant-time memcmp in this tree, and
secshare*G is a public key: secp256k1_frost_sign declassifies exactly
this quantity before exactly this comparison
(src/modules/frost/session_impl.h:770, :789). Inventing a primitive to
avoid following that precedent would be the worse trade.
- params_hash's public entry point delegates to the same internal
routine every gate uses, so the encoding has exactly one
implementation to keep in step with the vectors.
One real bug found by the tooling rather than by reading. Accumulators
were initialized with secp256k1_scalar_clear, and
secp256k1_memclear_explicit marks its target UNDEFINED in VERIFY builds
(src/util.h:295) precisely so that reading cleared memory is caught. It
was: valgrind reported 143752 errors in share_agg's summation loop.
Accumulators now start at secp256k1_scalar_set_int(x, 0); scalar_clear
is used only where it means "done with this secret". Worth stating
plainly because the failure mode is invisible in a production build,
where memclear_explicit only zeroes.
ctime_tests gains a 2-of-3-enrolls-a-fourth block covering all three
rounds, following prefractal's b66c757b. The threshold key, the secret
shares, the session randomness and every delta and sigma on the wire are
marked secret; the identifiers, public shares, group key, parameters
hashes and derived public share are not. Under valgrind: 0 errors from 0
contexts, so no branch or memory access in the new code depends on
secret data.
Verification: ./tests, ./noverify_tests and ./exhaustive_tests exit 0;
the frost_enrollment module runs clean under valgrind (0 errors); a
separate CPPFLAGS='-DVERIFY' build compiles without warnings and passes;
the module builds warning-free alongside frost, chilldkg, iceberg and
prefractal.
The smoke test is the substantive check: after a 2-of-3 group enrolls
participant 3, every pair {i, 3} for i in 0..2 reconstructs the original
threshold secret and matches the threshold public key, and the untouched
pair {0, 1} still does too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 04:06:59 +02:00
|
|
|
#ifdef ENABLE_MODULE_FROST_ENROLLMENT
|
|
|
|
|
{
|
|
|
|
|
/* A 2-of-3 group enrolling a fourth participant, taken through all
|
|
|
|
|
* three rounds. Secret here is the threshold key, the secret shares
|
|
|
|
|
* derived from it, the session randomness the split comes from, and
|
|
|
|
|
* every delta and sigma value on the wire -- those are additive shares
|
|
|
|
|
* of real secret shares. Not secret: the identifiers, the public
|
|
|
|
|
* shares, the threshold public key, the parameters hashes, and the
|
|
|
|
|
* public share derived for the new participant. */
|
|
|
|
|
unsigned char thresh_seckey[32];
|
|
|
|
|
unsigned char secshares[3 * 32];
|
|
|
|
|
unsigned char session_secrand[2][32];
|
|
|
|
|
unsigned char shares[2][2 * 32];
|
|
|
|
|
unsigned char all_shares[2 * 32];
|
|
|
|
|
unsigned char received_hashes[2 * 32];
|
|
|
|
|
unsigned char params_hashes[2][32];
|
|
|
|
|
unsigned char sigmas[2 * 32];
|
|
|
|
|
unsigned char new_secshare[32];
|
|
|
|
|
unsigned char direct_hash[32];
|
|
|
|
|
secp256k1_pubkey thresh_pk, pubshares[3], new_pubshare;
|
|
|
|
|
uint32_t fe_ids[2] = { 0, 1 };
|
|
|
|
|
uint32_t mismatch_id;
|
|
|
|
|
int j;
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
|
|
|
|
memcpy(thresh_seckey, key, sizeof(thresh_seckey));
|
|
|
|
|
thresh_seckey[0] = thresh_seckey[0] + 9;
|
|
|
|
|
memcpy(session_secrand[0], key, 32);
|
|
|
|
|
session_secrand[0][0] = session_secrand[0][0] + 10;
|
|
|
|
|
memcpy(session_secrand[1], key, 32);
|
|
|
|
|
session_secrand[1][0] = session_secrand[1][0] + 11;
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(thresh_seckey, sizeof(thresh_seckey));
|
|
|
|
|
ret = secp256k1_frost_trusted_dealer_keygen(ctx, secshares, &thresh_pk, pubshares, 3, 2, thresh_seckey);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&thresh_pk, sizeof(thresh_pk));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pubshares, sizeof(pubshares));
|
|
|
|
|
|
|
|
|
|
/* The parameters hash and the public share at the target identifier
|
|
|
|
|
* are functions of public data alone. */
|
|
|
|
|
CHECK(secp256k1_frost_enrollment_params_hash(ctx, direct_hash, &thresh_pk, fe_ids, 2, 3, 3, 2) == 1);
|
|
|
|
|
CHECK(secp256k1_frost_enrollment_pubshare_derive(ctx, &new_pubshare, pubshares, fe_ids, 2, 3, 3, 2) == 1);
|
|
|
|
|
|
|
|
|
|
/* Round 1.1. The seed and the secret share are secret; the parameters
|
|
|
|
|
* hash is public, the delta values are not. */
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(session_secrand[i], 32);
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(&secshares[32 * i], 32);
|
|
|
|
|
ret = secp256k1_frost_enrollment_shares_gen(ctx, shares[i], params_hashes[i], session_secrand[i], &secshares[32 * i], &thresh_pk, fe_ids, 2, fe_ids[i], 3, 3, 2);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(params_hashes[i], 32);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Round 1.2, for both helpers. The delta values stay secret through
|
|
|
|
|
* the sum; only the return value is examined. */
|
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
|
memset(received_hashes, 0, sizeof(received_hashes));
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
|
memcpy(&all_shares[32 * i], &shares[i][32 * j], 32);
|
|
|
|
|
if (i != j) {
|
|
|
|
|
memcpy(&received_hashes[32 * i], params_hashes[i], 32);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ret = secp256k1_frost_enrollment_share_agg(ctx, &sigmas[32 * j], &mismatch_id, all_shares, received_hashes, &thresh_pk, fe_ids, 2, fe_ids[j], 3, 3, 2);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Round 2, with both optional checks on. The resulting share is
|
|
|
|
|
* secret; the verification against the expected public share is the
|
|
|
|
|
* one place a secret-derived point is deliberately declassified. */
|
|
|
|
|
ret = secp256k1_frost_enrollment_secshare_gen(ctx, new_secshare, sigmas, &thresh_pk, fe_ids, 2, 3, 3, 2, params_hashes[0], &new_pubshare);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
iceberg: cover the module in ctime_tests
Port the source tree's own iceberg ctime_tests block (guarded by
ENABLE_MODULE_ICEBERG, after the chilldkg block): a 3-of-5 group run
through shares_gen, a share serialize/parse roundtrip,
share_cache_create (with a CHECKMEM_CHECK proving the cache holds no
secrets), pubshare_gen, pubkey_agg, nonce_gen, nonce_agg and
partial_sign, with share secrets undefined and all protocol outputs
defined, following the frost/chilldkg annotation style.
Both memory checkers pass with zero new declassifies needed -- the
module's constant-time layering (vpss variable-time but secret-free,
scalar_poly keeping secrets away from inversions) was already in
place. Verified: valgrind ./ctime_tests exits 0, and the clang
MemorySanitizer build exits 0 (with the chilldkg block unaffected).
2026-08-31 12:25:55 +02:00
|
|
|
#ifdef ENABLE_MODULE_ICEBERG
|
|
|
|
|
{
|
|
|
|
|
/* A 3-of-5 group, dealt from `key` and taken as far as one signature
|
|
|
|
|
* share. Secret here is the dealer's root seed and everything the
|
|
|
|
|
* module derives from it: the per-subset seeds, the key share, and the
|
|
|
|
|
* two nonce shares. Not secret: participant indices, the group and
|
|
|
|
|
* threshold, every Lagrange weight, the commitments, the session label,
|
|
|
|
|
* and both nonce coefficients. */
|
|
|
|
|
enum { ICEBERG_N = 5, ICEBERG_T = 3, ICEBERG_MU = 2 * ICEBERG_T - 1 };
|
|
|
|
|
secp256k1_iceberg_share shares[ICEBERG_N];
|
|
|
|
|
secp256k1_iceberg_share *share_ptr[ICEBERG_N];
|
|
|
|
|
secp256k1_iceberg_share_cache share_cache;
|
|
|
|
|
secp256k1_iceberg_pubshare pubshares[ICEBERG_N];
|
|
|
|
|
const secp256k1_iceberg_pubshare *pubshare_ptr[ICEBERG_N];
|
|
|
|
|
secp256k1_iceberg_pubnonce nonces[ICEBERG_N];
|
|
|
|
|
const secp256k1_iceberg_pubnonce *nonce_ptr[ICEBERG_N];
|
|
|
|
|
secp256k1_iceberg_aggnonce iceberg_aggnonce;
|
|
|
|
|
secp256k1_iceberg_partial_sig iceberg_psig;
|
|
|
|
|
secp256k1_musig_pubnonce group_pubnonce, cosigner_pubnonce;
|
|
|
|
|
const secp256k1_musig_pubnonce *cosigner_ptr[1];
|
|
|
|
|
secp256k1_musig_secnonce cosigner_secnonce;
|
|
|
|
|
secp256k1_musig_aggnonce cosigner_aggnonce;
|
|
|
|
|
secp256k1_musig_keyagg_cache iceberg_cache;
|
|
|
|
|
secp256k1_pubkey group_pk, cosigner_pk;
|
|
|
|
|
const secp256k1_pubkey *iceberg_pk_ptr[2];
|
|
|
|
|
unsigned char share_bytes[SECP256K1_ICEBERG_SHARE_MAX_LEN];
|
|
|
|
|
unsigned char sid[32], cosigner_secrand[32];
|
|
|
|
|
size_t share_len;
|
|
|
|
|
int party;
|
|
|
|
|
|
|
|
|
|
for (party = 0; party < ICEBERG_N; party++) {
|
|
|
|
|
share_ptr[party] = &shares[party];
|
|
|
|
|
pubshare_ptr[party] = &pubshares[party];
|
|
|
|
|
nonce_ptr[party] = &nonces[party];
|
|
|
|
|
}
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
|
|
|
|
/* The cosigner needs randomness distinct from the dealer's root seed;
|
|
|
|
|
* any perturbation of `key` will do. */
|
|
|
|
|
memcpy(cosigner_secrand, key, sizeof(cosigner_secrand));
|
|
|
|
|
cosigner_secrand[0] = cosigner_secrand[0] + 3;
|
|
|
|
|
CHECK(secp256k1_keypair_create(ctx, &keypair, key));
|
|
|
|
|
CHECK(secp256k1_keypair_pub(ctx, &cosigner_pk, &keypair));
|
|
|
|
|
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
|
|
|
|
ret = secp256k1_iceberg_shares_gen(ctx, share_ptr, ICEBERG_N, ICEBERG_T, key);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
|
|
|
|
/* Storing and restoring a share moves seed material through a buffer,
|
|
|
|
|
* which is where a length or an offset computed from it would show. */
|
|
|
|
|
share_len = sizeof(share_bytes);
|
|
|
|
|
ret = secp256k1_iceberg_share_serialize(ctx, share_bytes, &share_len, &shares[0]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
ret = secp256k1_iceberg_share_parse(ctx, &shares[0], share_bytes, share_len);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
|
|
|
|
|
ret = secp256k1_iceberg_share_cache_create(ctx, &share_cache, &shares[0]);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
/* The header says the cache holds no secret, and this is the line that
|
|
|
|
|
* makes that a result rather than a claim. Every byte of it must be
|
|
|
|
|
* defined: the weights come from the group size, the threshold and the
|
|
|
|
|
* participant index, all of which are public, and none of them from a
|
|
|
|
|
* seed. Declassifying is not the same test: it would say the value may be
|
|
|
|
|
* published, where this says nothing secret reached it. */
|
|
|
|
|
SECP256K1_CHECKMEM_CHECK(&share_cache, sizeof(share_cache));
|
|
|
|
|
|
|
|
|
|
for (party = 0; party < ICEBERG_N; party++) {
|
|
|
|
|
ret = secp256k1_iceberg_pubshare_gen(ctx, &pubshares[party], &shares[party],
|
|
|
|
|
party == 0 ? &share_cache : NULL);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* A commitment is public, and so is everything built from one. */
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(pubshares, sizeof(pubshares));
|
|
|
|
|
CHECK(secp256k1_iceberg_pubkey_agg(ctx, &group_pk, pubshare_ptr, ICEBERG_MU, ICEBERG_N, ICEBERG_T) == 1);
|
|
|
|
|
|
|
|
|
|
iceberg_pk_ptr[0] = &group_pk;
|
|
|
|
|
iceberg_pk_ptr[1] = &cosigner_pk;
|
|
|
|
|
CHECK(secp256k1_musig_pubkey_agg(ctx, NULL, &iceberg_cache, iceberg_pk_ptr, 2));
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg));
|
|
|
|
|
SECP256K1_CHECKMEM_UNDEFINE(cosigner_secrand, sizeof(cosigner_secrand));
|
|
|
|
|
ret = secp256k1_musig_nonce_gen(ctx, &cosigner_secnonce, &cosigner_pubnonce,
|
|
|
|
|
cosigner_secrand, NULL, &cosigner_pk, msg,
|
|
|
|
|
&iceberg_cache, NULL);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&cosigner_pubnonce, sizeof(cosigner_pubnonce));
|
|
|
|
|
cosigner_ptr[0] = &cosigner_pubnonce;
|
|
|
|
|
CHECK(secp256k1_musig_nonce_agg(ctx, &cosigner_aggnonce, cosigner_ptr, 1));
|
|
|
|
|
memset(sid, 0x7e, sizeof(sid)); /* public: the label is the caller's to choose */
|
|
|
|
|
|
|
|
|
|
for (party = 0; party < ICEBERG_MU; party++) {
|
|
|
|
|
ret = secp256k1_iceberg_nonce_gen(ctx, &nonces[party], &shares[party],
|
|
|
|
|
party == 0 ? &share_cache : NULL, sid);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(nonces, sizeof(nonces));
|
|
|
|
|
CHECK(secp256k1_iceberg_nonce_agg(ctx, &group_pubnonce, &iceberg_aggnonce,
|
|
|
|
|
nonce_ptr, ICEBERG_MU, ICEBERG_N, ICEBERG_T, &group_pk) == 1);
|
|
|
|
|
|
|
|
|
|
ret = secp256k1_iceberg_partial_sign(ctx, &iceberg_psig, &shares[0], &share_cache,
|
|
|
|
|
sid, nonce_ptr, ICEBERG_MU, &group_pk,
|
|
|
|
|
&iceberg_cache, msg, &cosigner_aggnonce);
|
|
|
|
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
|
|
|
|
CHECK(ret == 1);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2020-01-08 11:56:15 +00:00
|
|
|
}
|
2026-06-12 19:10:33 +05:30
|
|
|
|
|
|
|
|
#if defined(__GNUC__)
|
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
|
#endif
|