From 26166c4f5fd5688c31e488449ee2325eb8f2fb36 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Fri, 17 Oct 2025 14:18:42 +0000 Subject: [PATCH 01/26] ecmult_multi: reduce strauss memory usage by 30% --- src/ecmult_impl.h | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index 0b53b3fcb..29f02f547 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -220,9 +220,24 @@ static int secp256k1_ecmult_wnaf(int *wnaf, int len, const secp256k1_scalar *a, return last_set_bit + 1; } +/* Same as secp256k1_ecmult_wnaf, but stores to int8_t array. Requires w <= 8. */ +static int secp256k1_ecmult_wnaf_small(int8_t *wnaf, int len, const secp256k1_scalar *a, int w) { + int wnaf_tmp[256]; + int ret, i; + + VERIFY_CHECK(2 <= w && w <= 8); + ret = secp256k1_ecmult_wnaf(wnaf_tmp, len, a, w); + + for (i = 0; i < len; i++) { + wnaf[i] = (int8_t)wnaf_tmp[i]; + } + + return ret; +} + struct secp256k1_strauss_point_state { - int wnaf_na_1[129]; - int wnaf_na_lam[129]; + int8_t wnaf_na_1[129]; + int8_t wnaf_na_lam[129]; int bits_na_1; int bits_na_lam; }; @@ -259,8 +274,8 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state * secp256k1_scalar_split_lambda(&na_1, &na_lam, &na[np]); /* build wnaf representation for na_1 and na_lam. */ - state->ps[no].bits_na_1 = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_1, 129, &na_1, WINDOW_A); - state->ps[no].bits_na_lam = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_lam, 129, &na_lam, WINDOW_A); + state->ps[no].bits_na_1 = secp256k1_ecmult_wnaf_small(state->ps[no].wnaf_na_1, 129, &na_1, WINDOW_A); + state->ps[no].bits_na_lam = secp256k1_ecmult_wnaf_small(state->ps[no].wnaf_na_lam, 129, &na_lam, WINDOW_A); VERIFY_CHECK(state->ps[no].bits_na_1 <= 129); VERIFY_CHECK(state->ps[no].bits_na_lam <= 129); if (state->ps[no].bits_na_1 > bits) { From 153eea20c2527252c4a1785869def7bc052435d0 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:04:51 +0000 Subject: [PATCH 02/26] bench: Use `ALIGNMENT` macro instead of hardcoded value --- src/bench_ecmult.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index b2bab65d2..c9a1d9002 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -334,7 +334,7 @@ int main(int argc, char **argv) { } data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*16; + scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT; if (!have_flag(argc, argv, "simple")) { data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size); } else { From 2f73e5281de44e2c2bcf60ebc7b113440e13b07f Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Tue, 28 Oct 2025 15:00:58 +0100 Subject: [PATCH 03/26] group: Avoid using infinity field directly in other modules --- src/ecmult_const_impl.h | 19 ++++++++++--------- src/ecmult_impl.h | 4 ++-- src/tests.c | 4 ++-- src/tests_exhaustive.c | 14 +++++++------- src/testutil.h | 12 ++++-------- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/ecmult_const_impl.h b/src/ecmult_const_impl.h index 4f6620910..1d24aeaf3 100644 --- a/src/ecmult_const_impl.h +++ b/src/ecmult_const_impl.h @@ -87,17 +87,15 @@ static void secp256k1_ecmult_const_odd_multiples_table_globalz(secp256k1_ge *pre secp256k1_fe neg_y; \ VERIFY_CHECK((n) < (1U << ECMULT_CONST_GROUP_SIZE)); \ VERIFY_CHECK(index < (1U << (ECMULT_CONST_GROUP_SIZE - 1))); \ - /* Unconditionally set r->x = (pre)[m].x. r->y = (pre)[m].y. because it's either the correct one + /* Unconditionally set r->x = (pre)[m].x and r->y = (pre)[m].y because it's either the correct one * or will get replaced in the later iterations, this is needed to make sure `r` is initialized. */ \ - (r)->x = (pre)[m].x; \ - (r)->y = (pre)[m].y; \ + secp256k1_ge_set_xy((r), &(pre)[m].x, &(pre)[m].y); \ for (m = 1; m < ECMULT_CONST_TABLE_SIZE; m++) { \ /* This loop is used to avoid secret data in array indices. See * the comment in ecmult_gen_impl.h for rationale. */ \ secp256k1_fe_cmov(&(r)->x, &(pre)[m].x, m == index); \ secp256k1_fe_cmov(&(r)->y, &(pre)[m].y, m == index); \ } \ - (r)->infinity = 0; \ secp256k1_fe_negate(&neg_y, &(r)->y, 1); \ secp256k1_fe_cmov(&(r)->y, &neg_y, negative); \ } while(0) @@ -375,11 +373,14 @@ static int secp256k1_ecmult_const_xonly(secp256k1_fe* r, const secp256k1_fe *n, SECP256K1_FE_VERIFY_MAGNITUDE(&g, 2); - /* Compute base point P = (n*g, g^2), the effective affine version of (n*g, g^2, v), which has - * corresponding affine X coordinate n/d. */ - secp256k1_fe_mul(&p.x, &g, n); - secp256k1_fe_sqr(&p.y, &g); - p.infinity = 0; + /* Compute base point P = (n*g, g^2), the effective affine version of + * (n*g, g^2, v), which has corresponding affine X coordinate n/d. */ + { + secp256k1_fe x, y; + secp256k1_fe_mul(&x, &g, n); + secp256k1_fe_sqr(&y, &g); + secp256k1_ge_set_xy(&p, &x, &y); + } /* Perform x-only EC multiplication of P with q. */ VERIFY_CHECK(!secp256k1_scalar_is_zero(q)); diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index 0b53b3fcb..20cf750fd 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -75,7 +75,7 @@ static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_ge *pre_a, sec secp256k1_ge d_ge; int i; - VERIFY_CHECK(!a->infinity); + VERIFY_CHECK(!secp256k1_gej_is_infinity(a)); secp256k1_gej_double_var(&d, a, NULL); @@ -341,7 +341,7 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state * } } - if (!r->infinity) { + if (!secp256k1_gej_is_infinity(r)) { secp256k1_fe_mul(&r->z, &r->z, &Z); } } diff --git a/src/tests.c b/src/tests.c index 0ccdbeecf..dc5f27903 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4152,8 +4152,8 @@ static void test_group_decompress(const secp256k1_fe* x) { secp256k1_fe_normalize_var(&ge_even.y); /* No infinity allowed. */ - CHECK(!ge_even.infinity); - CHECK(!ge_odd.infinity); + CHECK(!secp256k1_ge_is_infinity(&ge_even)); + CHECK(!secp256k1_ge_is_infinity(&ge_odd)); /* Check that the x coordinates check out. */ CHECK(secp256k1_fe_equal(&ge_even.x, x)); diff --git a/src/tests_exhaustive.c b/src/tests_exhaustive.c index f8bbcaaf5..13bda6119 100644 --- a/src/tests_exhaustive.c +++ b/src/tests_exhaustive.c @@ -103,9 +103,11 @@ static void test_exhaustive_addition(const secp256k1_ge *group, const secp256k1_ secp256k1_gej_add_ge_var(&tmp, &groupj[i], &group[j], NULL); CHECK(secp256k1_gej_eq_ge_var(&tmp, &group[(i + j) % EXHAUSTIVE_TEST_ORDER])); /* add_zinv_var */ - zless_gej.infinity = groupj[j].infinity; - zless_gej.x = groupj[j].x; - zless_gej.y = groupj[j].y; + if (secp256k1_gej_is_infinity(&groupj[j])) { + secp256k1_ge_set_infinity(&zless_gej); + } else { + secp256k1_ge_set_xy(&zless_gej, &groupj[j].x, &groupj[j].y); + } secp256k1_gej_add_zinv_var(&tmp, &groupj[i], &zless_gej, &fe_inv); CHECK(secp256k1_gej_eq_ge_var(&tmp, &group[(i + j) % EXHAUSTIVE_TEST_ORDER])); } @@ -422,10 +424,8 @@ int main(int argc, char** argv) { secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &generatedj, &scalar_i); secp256k1_ge_set_gej(&generated, &generatedj); - CHECK(group[i].infinity == 0); - CHECK(generated.infinity == 0); - CHECK(secp256k1_fe_equal(&generated.x, &group[i].x)); - CHECK(secp256k1_fe_equal(&generated.y, &group[i].y)); + CHECK(!secp256k1_ge_is_infinity(&group[i])); + CHECK(secp256k1_ge_eq_var(&group[i], &generated)); } } diff --git a/src/testutil.h b/src/testutil.h index 480f6a1a0..93ee3d58b 100644 --- a/src/testutil.h +++ b/src/testutil.h @@ -96,17 +96,13 @@ static void testutil_random_ge_test(secp256k1_ge *ge) { break; } } while(1); - ge->infinity = 0; } static void testutil_random_ge_jacobian_test(secp256k1_gej *gej, const secp256k1_ge *ge) { - secp256k1_fe z2, z3; - testutil_random_fe_non_zero_test(&gej->z); - secp256k1_fe_sqr(&z2, &gej->z); - secp256k1_fe_mul(&z3, &z2, &gej->z); - secp256k1_fe_mul(&gej->x, &ge->x, &z2); - secp256k1_fe_mul(&gej->y, &ge->y, &z3); - gej->infinity = ge->infinity; + secp256k1_fe z; + testutil_random_fe_non_zero_test(&z); + secp256k1_gej_set_ge(gej, ge); + secp256k1_gej_rescale(gej, &z); } static void testutil_random_gej_test(secp256k1_gej *gej) { From f252da7e6e1bbd0082fd43c4ffabfa90abafb248 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:12:39 +0000 Subject: [PATCH 04/26] ci: Use Python virtual environment in "x86_64-macos-native" job --- .github/workflows/ci.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 617675159..59d225149 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -469,9 +469,14 @@ jobs: env: ${{ matrix.env_vars }} run: ./ci/ci.sh - - name: Symbol check + - &SYMBOL_CHECK_MACOS + name: Symbol check + env: + VIRTUAL_ENV: '${{ github.workspace }}/venv' run: | python3 --version + python3 -m venv $VIRTUAL_ENV + export PATH="$VIRTUAL_ENV/bin:$PATH" python3 -m pip install lief python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib @@ -512,17 +517,7 @@ jobs: ln -s $(brew --prefix gcc)/bin/gcc-?? /usr/local/bin/gcc - *CI_SCRIPT_ON_HOST - - - name: Symbol check - env: - VIRTUAL_ENV: '${{ github.workspace }}/venv' - run: | - python3 --version - python3 -m venv $VIRTUAL_ENV - export PATH="$VIRTUAL_ENV/bin:$PATH" - python3 -m pip install lief - python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib - + - *SYMBOL_CHECK_MACOS - *PRINT_LOGS win64-native: From fc7458ca3e28d9eb34427a766122accaa0f36f26 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 17 Nov 2025 16:43:49 +0100 Subject: [PATCH 05/26] introduce `secp256k1_eckey_pubkey_serialize{33,65}` functions --- src/eckey.h | 4 ++++ src/eckey_impl.h | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/eckey.h b/src/eckey.h index d54d44c99..3ebe89692 100644 --- a/src/eckey.h +++ b/src/eckey.h @@ -16,6 +16,10 @@ static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size); static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed); +/** Serialize a group element (that is not allowed to be infinity) to a compressed public key (33 bytes). */ +static void secp256k1_eckey_pubkey_serialize33(secp256k1_ge *elem, unsigned char *pub33); +/** Serialize a group element (that is not allowed to be infinity) to an uncompressed public key (65 bytes). */ +static void secp256k1_eckey_pubkey_serialize65(secp256k1_ge *elem, unsigned char *pub65); static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak); static int secp256k1_eckey_pubkey_tweak_add(secp256k1_ge *key, const secp256k1_scalar *tweak); diff --git a/src/eckey_impl.h b/src/eckey_impl.h index a88a5964d..4f647607b 100644 --- a/src/eckey_impl.h +++ b/src/eckey_impl.h @@ -55,6 +55,25 @@ static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *p return 1; } +static void secp256k1_eckey_pubkey_serialize33(secp256k1_ge *elem, unsigned char *pub33) { + VERIFY_CHECK(!secp256k1_ge_is_infinity(elem)); + + secp256k1_fe_normalize_var(&elem->x); + secp256k1_fe_normalize_var(&elem->y); + pub33[0] = secp256k1_fe_is_odd(&elem->y) ? SECP256K1_TAG_PUBKEY_ODD : SECP256K1_TAG_PUBKEY_EVEN; + secp256k1_fe_get_b32(&pub33[1], &elem->x); +} + +static void secp256k1_eckey_pubkey_serialize65(secp256k1_ge *elem, unsigned char *pub65) { + VERIFY_CHECK(!secp256k1_ge_is_infinity(elem)); + + secp256k1_fe_normalize_var(&elem->x); + secp256k1_fe_normalize_var(&elem->y); + pub65[0] = SECP256K1_TAG_PUBKEY_UNCOMPRESSED; + secp256k1_fe_get_b32(&pub65[1], &elem->x); + secp256k1_fe_get_b32(&pub65[33], &elem->y); +} + static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak) { secp256k1_scalar_add(key, key, tweak); return !secp256k1_scalar_is_zero(key); From adb76f82eadadf4f9df4cd958f7c266b551b50c6 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 17 Nov 2025 16:58:01 +0100 Subject: [PATCH 06/26] use new `_eckey_pubkey_serialize{33,65}` functions in public API --- src/secp256k1.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/secp256k1.c b/src/secp256k1.c index 26336a45c..c2d3fcbdd 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -280,10 +280,14 @@ int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *o ARG_CHECK(pubkey != NULL); ARG_CHECK((flags & SECP256K1_FLAGS_TYPE_MASK) == SECP256K1_FLAGS_TYPE_COMPRESSION); if (secp256k1_pubkey_load(ctx, &Q, pubkey)) { - ret = secp256k1_eckey_pubkey_serialize(&Q, output, &len, !!(flags & SECP256K1_FLAGS_BIT_COMPRESSION)); - if (ret) { - *outputlen = len; + if (flags & SECP256K1_FLAGS_BIT_COMPRESSION) { + secp256k1_eckey_pubkey_serialize33(&Q, output); + *outputlen = 33; + } else { + secp256k1_eckey_pubkey_serialize65(&Q, output); + *outputlen = 65; } + ret = 1; } return ret; } From 0d3659c547bdd5a477346e1ef14be3f98cc08f2e Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 17 Nov 2025 18:13:56 +0100 Subject: [PATCH 07/26] use new `_eckey_pubkey_serialize{33,65}` functions in modules (ellswift,musig) --- src/modules/ellswift/main_impl.h | 9 +-------- src/modules/musig/keyagg_impl.h | 9 +-------- src/modules/musig/session_impl.h | 29 +++-------------------------- 3 files changed, 5 insertions(+), 42 deletions(-) diff --git a/src/modules/ellswift/main_impl.h b/src/modules/ellswift/main_impl.h index 0d13f7342..096f4a3c7 100644 --- a/src/modules/ellswift/main_impl.h +++ b/src/modules/ellswift/main_impl.h @@ -406,19 +406,12 @@ int secp256k1_ellswift_encode(const secp256k1_context *ctx, unsigned char *ell64 if (secp256k1_pubkey_load(ctx, &p, pubkey)) { secp256k1_fe t; unsigned char p64[64] = {0}; - size_t ser_size; - int ser_ret; secp256k1_sha256 hash; /* Set up hasher state; the used RNG is H(pubkey || "\x00"*31 || rnd32 || cnt++), using * BIP340 tagged hash with tag "secp256k1_ellswift_encode". */ secp256k1_ellswift_sha256_init_encode(&hash); - ser_ret = secp256k1_eckey_pubkey_serialize(&p, p64, &ser_size, 1); -#ifdef VERIFY - VERIFY_CHECK(ser_ret && ser_size == 33); -#else - (void)ser_ret; -#endif + secp256k1_eckey_pubkey_serialize33(&p, p64); secp256k1_sha256_write(&hash, p64, sizeof(p64)); secp256k1_sha256_write(&hash, rnd32, 32); diff --git a/src/modules/musig/keyagg_impl.h b/src/modules/musig/keyagg_impl.h index 0db4fce85..87869a410 100644 --- a/src/modules/musig/keyagg_impl.h +++ b/src/modules/musig/keyagg_impl.h @@ -124,18 +124,11 @@ static void secp256k1_musig_keyaggcoef_internal(secp256k1_scalar *r, const unsig } else { secp256k1_sha256 sha; unsigned char buf[33]; - size_t buflen = sizeof(buf); - int ret; secp256k1_musig_keyaggcoef_sha256(&sha); secp256k1_sha256_write(&sha, pks_hash, 32); - ret = secp256k1_eckey_pubkey_serialize(pk, buf, &buflen, 1); -#ifdef VERIFY /* Serialization does not fail since the pk is not the point at infinity * (according to this function's precondition). */ - VERIFY_CHECK(ret && buflen == sizeof(buf)); -#else - (void) ret; -#endif + secp256k1_eckey_pubkey_serialize33(pk, buf); secp256k1_sha256_write(&sha, buf, sizeof(buf)); secp256k1_sha256_finalize(&sha, buf); secp256k1_scalar_set_b32(r, buf, NULL); diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 2c8778b3c..6a80a27b3 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -25,15 +25,8 @@ static void secp256k1_musig_ge_serialize_ext(unsigned char *out33, secp256k1_ge* if (secp256k1_ge_is_infinity(ge)) { memset(out33, 0, 33); } else { - int ret; - size_t size = 33; - ret = secp256k1_eckey_pubkey_serialize(ge, out33, &size, 1); -#ifdef VERIFY /* Serialize must succeed because the point is not at infinity */ - VERIFY_CHECK(ret && size == 33); -#else - (void) ret; -#endif + secp256k1_eckey_pubkey_serialize33(ge, out33); } } @@ -224,15 +217,8 @@ int secp256k1_musig_pubnonce_serialize(const secp256k1_context* ctx, unsigned ch return 0; } for (i = 0; i < 2; i++) { - int ret; - size_t size = 33; - ret = secp256k1_eckey_pubkey_serialize(&ges[i], &out66[33*i], &size, 1); -#ifdef VERIFY /* serialize must succeed because the point was just loaded */ - VERIFY_CHECK(ret && size == 33); -#else - (void) ret; -#endif + secp256k1_eckey_pubkey_serialize33(&ges[i], &out66[33*i]); } return 1; } @@ -398,11 +384,9 @@ static int secp256k1_musig_nonce_gen_internal(const secp256k1_context* ctx, secp secp256k1_gej nonce_ptj[2]; int i; unsigned char pk_ser[33]; - size_t pk_ser_len = sizeof(pk_ser); unsigned char aggpk_ser[32]; unsigned char *aggpk_ser_ptr = NULL; secp256k1_ge pk; - int pk_serialize_success; int ret = 1; ARG_CHECK(pubnonce != NULL); @@ -429,15 +413,8 @@ static int secp256k1_musig_nonce_gen_internal(const secp256k1_context* ctx, secp if (!secp256k1_pubkey_load(ctx, &pk, pubkey)) { return 0; } - pk_serialize_success = secp256k1_eckey_pubkey_serialize(&pk, pk_ser, &pk_ser_len, 1); - -#ifdef VERIFY /* A pubkey cannot be the point at infinity */ - VERIFY_CHECK(pk_serialize_success); - VERIFY_CHECK(pk_ser_len == sizeof(pk_ser)); -#else - (void) pk_serialize_success; -#endif + secp256k1_eckey_pubkey_serialize33(&pk, pk_ser); secp256k1_nonce_function_musig(k, input_nonce, msg32, seckey, pk_ser, aggpk_ser_ptr, extra_input32); VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[0])); From f5e815f4306ea1763d27d83646b4f06e3d2f0b94 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 17 Nov 2025 18:35:32 +0100 Subject: [PATCH 08/26] remove secp256k1_eckey_pubkey_serialize function --- src/eckey.h | 1 - src/eckey_impl.h | 20 -------------------- src/tests.c | 23 +++++++++-------------- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/src/eckey.h b/src/eckey.h index 3ebe89692..c2bbc4703 100644 --- a/src/eckey.h +++ b/src/eckey.h @@ -15,7 +15,6 @@ #include "ecmult_gen.h" static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size); -static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed); /** Serialize a group element (that is not allowed to be infinity) to a compressed public key (33 bytes). */ static void secp256k1_eckey_pubkey_serialize33(secp256k1_ge *elem, unsigned char *pub33); /** Serialize a group element (that is not allowed to be infinity) to an uncompressed public key (65 bytes). */ diff --git a/src/eckey_impl.h b/src/eckey_impl.h index 4f647607b..48745e8fe 100644 --- a/src/eckey_impl.h +++ b/src/eckey_impl.h @@ -35,26 +35,6 @@ static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char } } -static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed) { - VERIFY_CHECK(compressed == 0 || compressed == 1); - - if (secp256k1_ge_is_infinity(elem)) { - return 0; - } - secp256k1_fe_normalize_var(&elem->x); - secp256k1_fe_normalize_var(&elem->y); - secp256k1_fe_get_b32(&pub[1], &elem->x); - if (compressed) { - *size = 33; - pub[0] = secp256k1_fe_is_odd(&elem->y) ? SECP256K1_TAG_PUBKEY_ODD : SECP256K1_TAG_PUBKEY_EVEN; - } else { - *size = 65; - pub[0] = SECP256K1_TAG_PUBKEY_UNCOMPRESSED; - secp256k1_fe_get_b32(&pub[33], &elem->y); - } - return 1; -} - static void secp256k1_eckey_pubkey_serialize33(secp256k1_ge *elem, unsigned char *pub33) { VERIFY_CHECK(!secp256k1_ge_is_infinity(elem)); diff --git a/src/tests.c b/src/tests.c index 0ccdbeecf..4029de565 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4315,8 +4315,6 @@ static void test_point_times_order(const secp256k1_gej *point) { secp256k1_scalar nx; secp256k1_gej res1, res2; secp256k1_ge res3; - unsigned char pub[65]; - size_t psize = 65; testutil_random_scalar_order_test(&x); secp256k1_scalar_negate(&nx, &x); secp256k1_ecmult(&res1, point, &x, &x); /* calc res1 = x * point + x * G; */ @@ -4326,9 +4324,6 @@ static void test_point_times_order(const secp256k1_gej *point) { secp256k1_ge_set_gej(&res3, &res1); CHECK(secp256k1_ge_is_infinity(&res3)); CHECK(secp256k1_ge_is_valid_var(&res3) == 0); - CHECK(secp256k1_eckey_pubkey_serialize(&res3, pub, &psize, 0) == 0); - psize = 65; - CHECK(secp256k1_eckey_pubkey_serialize(&res3, pub, &psize, 1) == 0); /* check zero/one edge cases */ secp256k1_ecmult(&res1, point, &secp256k1_scalar_zero, &secp256k1_scalar_zero); secp256k1_ge_set_gej(&res3, &res1); @@ -5435,7 +5430,6 @@ static void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar secp256k1_gej rj1, rj2, rj3, rj4, rj5, rj6, gj, infj; secp256k1_ge r; unsigned char bytes[65]; - size_t size = 65; secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g); secp256k1_gej_set_infinity(&infj); secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &rj1, x); @@ -5456,9 +5450,8 @@ static void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar secp256k1_sha256_write(acc, zerobyte, 1); } else { /* Store other points using their uncompressed serialization. */ - secp256k1_eckey_pubkey_serialize(&r, bytes, &size, 0); - CHECK(size == 65); - secp256k1_sha256_write(acc, bytes, size); + secp256k1_eckey_pubkey_serialize65(&r, bytes); + secp256k1_sha256_write(acc, bytes, sizeof(bytes)); } } @@ -6543,16 +6536,18 @@ static void test_random_pubkeys(void) { size_t size = len; firstb = in[0]; /* If the pubkey can be parsed, it should round-trip... */ - CHECK(secp256k1_eckey_pubkey_serialize(&elem, out, &size, len == 33)); - CHECK(size == len); + if (len == 33) { + secp256k1_eckey_pubkey_serialize33(&elem, out); + } else { + secp256k1_eckey_pubkey_serialize65(&elem, out); + } CHECK(secp256k1_memcmp_var(&in[1], &out[1], len-1) == 0); /* ... except for the type of hybrid inputs. */ if ((in[0] != 6) && (in[0] != 7)) { CHECK(in[0] == out[0]); } size = 65; - CHECK(secp256k1_eckey_pubkey_serialize(&elem, in, &size, 0)); - CHECK(size == 65); + secp256k1_eckey_pubkey_serialize65(&elem, in); CHECK(secp256k1_eckey_pubkey_parse(&elem2, in, size)); CHECK(secp256k1_ge_eq_var(&elem2, &elem)); /* Check that the X9.62 hybrid type is checked. */ @@ -6567,7 +6562,7 @@ static void test_random_pubkeys(void) { } if (res) { CHECK(secp256k1_ge_eq_var(&elem, &elem2)); - CHECK(secp256k1_eckey_pubkey_serialize(&elem, out, &size, 0)); + secp256k1_eckey_pubkey_serialize65(&elem, out); CHECK(secp256k1_memcmp_var(&in[1], &out[1], 64) == 0); } } From 3b5b03f30169903baae7f365cff3137a371a9a8d Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Thu, 4 Dec 2025 16:53:15 -0500 Subject: [PATCH 09/26] doc/bench: Added cmake build options to bench error messages --- src/bench.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bench.c b/src/bench.c index 8ba7623a0..04685d930 100644 --- a/src/bench.c +++ b/src/bench.c @@ -205,7 +205,7 @@ int main(int argc, char** argv) { #ifndef ENABLE_MODULE_ECDH if (have_flag(argc, argv, "ecdh")) { fprintf(stderr, "./bench: ECDH module not enabled.\n"); - fprintf(stderr, "Use ./configure --enable-module-ecdh.\n\n"); + fprintf(stderr, "See README.md for configuration instructions.\n\n"); return EXIT_FAILURE; } #endif @@ -213,7 +213,7 @@ int main(int argc, char** argv) { #ifndef ENABLE_MODULE_RECOVERY if (have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) { fprintf(stderr, "./bench: Public key recovery module not enabled.\n"); - fprintf(stderr, "Use ./configure --enable-module-recovery.\n\n"); + fprintf(stderr, "See README.md for configuration instructions.\n\n"); return EXIT_FAILURE; } #endif @@ -221,7 +221,7 @@ int main(int argc, char** argv) { #ifndef ENABLE_MODULE_SCHNORRSIG if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) { fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n"); - fprintf(stderr, "Use ./configure --enable-module-schnorrsig.\n\n"); + fprintf(stderr, "See README.md for configuration instructions.\n\n"); return EXIT_FAILURE; } #endif @@ -231,7 +231,7 @@ int main(int argc, char** argv) { have_flag(argc, argv, "encode") || have_flag(argc, argv, "decode") || have_flag(argc, argv, "ellswift_keygen") || have_flag(argc, argv, "ellswift_ecdh")) { fprintf(stderr, "./bench: ElligatorSwift module not enabled.\n"); - fprintf(stderr, "Use ./configure --enable-module-ellswift.\n\n"); + fprintf(stderr, "See README.md for configuration instructions.\n\n"); return EXIT_FAILURE; } #endif From 5a08c1bcdc958f264450e771d7923922526f74f1 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Sat, 6 Dec 2025 01:04:18 +0100 Subject: [PATCH 10/26] Add ARG_CHECKs to ensure "array of pointers" elements are non-NULL --- src/modules/musig/keyagg_impl.h | 3 +++ src/modules/musig/session_impl.h | 8 ++++++++ src/secp256k1.c | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/src/modules/musig/keyagg_impl.h b/src/modules/musig/keyagg_impl.h index 87869a410..e412a27ec 100644 --- a/src/modules/musig/keyagg_impl.h +++ b/src/modules/musig/keyagg_impl.h @@ -177,6 +177,9 @@ int secp256k1_musig_pubkey_agg(const secp256k1_context* ctx, secp256k1_xonly_pub } ARG_CHECK(pubkeys != NULL); ARG_CHECK(n_pubkeys > 0); + for (i = 0; i < n_pubkeys; i++) { + ARG_CHECK(pubkeys[i] != NULL); + } ecmult_data.ctx = ctx; ecmult_data.pks = pubkeys; diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 6a80a27b3..4a245ada5 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -521,10 +521,15 @@ static int secp256k1_musig_sum_pubnonces(const secp256k1_context* ctx, secp256k1 int secp256k1_musig_nonce_agg(const secp256k1_context* ctx, secp256k1_musig_aggnonce *aggnonce, const secp256k1_musig_pubnonce * const* pubnonces, size_t n_pubnonces) { secp256k1_gej aggnonce_ptsj[2]; secp256k1_ge aggnonce_pts[2]; + size_t i; + VERIFY_CHECK(ctx != NULL); ARG_CHECK(aggnonce != NULL); ARG_CHECK(pubnonces != NULL); ARG_CHECK(n_pubnonces > 0); + for (i = 0; i < n_pubnonces; i++) { + ARG_CHECK(pubnonces[i] != NULL); + } if (!secp256k1_musig_sum_pubnonces(ctx, aggnonce_ptsj, pubnonces, n_pubnonces)) { return 0; @@ -782,6 +787,9 @@ int secp256k1_musig_partial_sig_agg(const secp256k1_context* ctx, unsigned char ARG_CHECK(session != NULL); ARG_CHECK(partial_sigs != NULL); ARG_CHECK(n_sigs > 0); + for (i = 0; i < n_sigs; i++) { + ARG_CHECK(partial_sigs[i] != NULL); + } if (!secp256k1_musig_session_load(ctx, &session_i, session)) { return 0; diff --git a/src/secp256k1.c b/src/secp256k1.c index c2d3fcbdd..bce06d37f 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -325,8 +325,13 @@ static int secp256k1_ec_pubkey_sort_cmp(const void* pk1, const void* pk2, void * } int secp256k1_ec_pubkey_sort(const secp256k1_context* ctx, const secp256k1_pubkey **pubkeys, size_t n_pubkeys) { + size_t i; + VERIFY_CHECK(ctx != NULL); ARG_CHECK(pubkeys != NULL); + for (i = 0; i < n_pubkeys; i++) { + ARG_CHECK(pubkeys[i] != NULL); + } /* Suppress wrong warning (fixed in MSVC 19.33) */ #if defined(_MSC_VER) && (_MSC_VER < 1933) From 8bcda186d20e11ba9a04e6917928f062513b534f Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Tue, 9 Dec 2025 01:30:56 +0100 Subject: [PATCH 11/26] test: Add non-NULL checks for "pointer of array" API functions --- src/modules/musig/tests_impl.h | 21 +++++++++++++++++++++ src/tests.c | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index b4ba18549..de09c5e28 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -201,6 +201,13 @@ static void musig_api_tests(void) { CHECK(secp256k1_musig_pubkey_agg(CTX, &agg_pk, &keyagg_cache, pk_ptr, 2) == 1); CHECK(secp256k1_musig_pubkey_agg(CTX, NULL, &keyagg_cache, pk_ptr, 2) == 1); CHECK(secp256k1_musig_pubkey_agg(CTX, &agg_pk, NULL, pk_ptr, 2) == 1); + /* check that NULL in array of public key pointers is not allowed */ + for (i = 0; i < 2; i++) { + const secp256k1_pubkey *original_ptr = pk_ptr[i]; + pk_ptr[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_musig_pubkey_agg(CTX, &agg_pk, NULL, pk_ptr, 2)); + pk_ptr[i] = original_ptr; + } CHECK_ILLEGAL(CTX, secp256k1_musig_pubkey_agg(CTX, &agg_pk, &keyagg_cache, NULL, 2)); CHECK(memcmp_and_randomize(agg_pk.data, zeros132, sizeof(agg_pk.data)) == 0); CHECK_ILLEGAL(CTX, secp256k1_musig_pubkey_agg(CTX, &agg_pk, &keyagg_cache, invalid_pk_ptr2, 2)); @@ -350,6 +357,13 @@ static void musig_api_tests(void) { /** Receive nonces and aggregate **/ CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); + /* check that NULL in array of public nonce pointers is not allowed */ + for (i = 0; i < 2; i++) { + const secp256k1_musig_pubnonce *original_ptr = pubnonce_ptr[i]; + pubnonce_ptr[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2)); + pubnonce_ptr[i] = original_ptr; + } CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_agg(CTX, NULL, pubnonce_ptr, 2)); CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_agg(CTX, &aggnonce, NULL, 2)); CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 0)); @@ -474,6 +488,13 @@ static void musig_api_tests(void) { /** Signature aggregation and verification */ CHECK(secp256k1_musig_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 2) == 1); + /* check that NULL in array of partial signature pointers is not allowed */ + for (i = 0; i < 2; i++) { + const secp256k1_musig_partial_sig *original_ptr = partial_sig_ptr[i]; + partial_sig_ptr[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_musig_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 2)); + partial_sig_ptr[i] = original_ptr; + } CHECK_ILLEGAL(CTX, secp256k1_musig_partial_sig_agg(CTX, NULL, &session, partial_sig_ptr, 2)); CHECK_ILLEGAL(CTX, secp256k1_musig_partial_sig_agg(CTX, pre_sig, NULL, partial_sig_ptr, 2)); CHECK_ILLEGAL(CTX, secp256k1_musig_partial_sig_agg(CTX, pre_sig, &invalid_session, partial_sig_ptr, 2)); diff --git a/src/tests.c b/src/tests.c index 4029de565..14aa78589 100644 --- a/src/tests.c +++ b/src/tests.c @@ -6052,6 +6052,7 @@ static void run_eckey_edge_case_test(void) { secp256k1_pubkey pubkey_negone; const secp256k1_pubkey *pubkeys[3]; size_t len; + int i; /* Group order is too large, reject. */ CHECK(secp256k1_ec_seckey_verify(CTX, orderc) == 0); SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); @@ -6245,6 +6246,14 @@ static void run_eckey_edge_case_test(void) { CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, pubkeys, 3) == 1); SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); + /* check that NULL in array of pubkey pointers is not allowed */ + for (i = 0; i < 3; i++) { + const secp256k1_pubkey *original_ptr = pubkeys[i]; + secp256k1_pubkey result; + pubkeys[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_ec_pubkey_combine(CTX, &result, pubkeys, 3)); + pubkeys[i] = original_ptr; + } len = 33; CHECK(secp256k1_ec_pubkey_serialize(CTX, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); CHECK(secp256k1_ec_pubkey_serialize(CTX, ctmp2, &len, &pubkey_one, SECP256K1_EC_COMPRESSED) == 1); @@ -6640,6 +6649,7 @@ static void permute(size_t *arr, size_t n) { static void test_sort_api(void) { secp256k1_pubkey pks[2]; const secp256k1_pubkey *pks_ptr[2]; + int i; pks_ptr[0] = &pks[0]; pks_ptr[1] = &pks[1]; @@ -6648,6 +6658,13 @@ static void test_sort_api(void) { testutil_random_pubkey_test(&pks[1]); CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1); + /* check that NULL in array of public key pointers is not allowed */ + for (i = 0; i < 2; i++) { + const secp256k1_pubkey *original_ptr = pks_ptr[i]; + pks_ptr[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2)); + pks_ptr[i] = original_ptr; + } CHECK_ILLEGAL(CTX, secp256k1_ec_pubkey_sort(CTX, NULL, 2)); CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 0) == 1); /* Test illegal public keys */ From 3daab83a6050805763da5ac9ebe8217d1965eb0c Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Tue, 9 Dec 2025 16:08:35 -0500 Subject: [PATCH 12/26] refactor: remove ret from secp256k1_ec_pubkey_serialize --- src/secp256k1.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/secp256k1.c b/src/secp256k1.c index c2d3fcbdd..7d6b9bd3e 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -268,7 +268,6 @@ int secp256k1_ec_pubkey_parse(const secp256k1_context* ctx, secp256k1_pubkey* pu int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_pubkey* pubkey, unsigned int flags) { secp256k1_ge Q; size_t len; - int ret = 0; VERIFY_CHECK(ctx != NULL); ARG_CHECK(outputlen != NULL); @@ -287,9 +286,9 @@ int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *o secp256k1_eckey_pubkey_serialize65(&Q, output); *outputlen = 65; } - ret = 1; + return 1; } - return ret; + return 0; } int secp256k1_ec_pubkey_cmp(const secp256k1_context* ctx, const secp256k1_pubkey* pubkey0, const secp256k1_pubkey* pubkey1) { From ae00c552df55f4d170175903855901d640b92761 Mon Sep 17 00:00:00 2001 From: John Moffett Date: Tue, 9 Dec 2025 12:43:49 -0500 Subject: [PATCH 13/26] Add VERIFY_CHECKs that flags are 0 or 1 Flags for constant-time masking rely on the values being exactly 0 or 1 rather than 0 or true. Add VERIFY_CHECKs to enforce in VERIFY builds as a preventative measure and add documentation where relevant. --- src/field.h | 3 ++- src/field_10x26_impl.h | 2 ++ src/field_5x52_impl.h | 2 ++ src/group.h | 6 ++++-- src/group_impl.h | 2 ++ src/modules/musig/session_impl.h | 3 ++- src/scalar.h | 6 +++--- src/scalar_4x64_impl.h | 3 +++ src/scalar_8x32_impl.h | 3 +++ src/scalar_low_impl.h | 3 +++ src/util.h | 5 ++++- 11 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/field.h b/src/field.h index 1f6ba7460..e3f0234f5 100644 --- a/src/field.h +++ b/src/field.h @@ -307,7 +307,8 @@ static void secp256k1_fe_to_storage(secp256k1_fe_storage *r, const secp256k1_fe */ static void secp256k1_fe_from_storage(secp256k1_fe *r, const secp256k1_fe_storage *a); -/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ +/** If flag is 1, set *r equal to *a; if flag is 0, leave it. Constant-time. + * Both *r and *a must be initialized. Flag must be 0 or 1. */ static void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag); /** Conditionally move a field element in constant time. diff --git a/src/field_10x26_impl.h b/src/field_10x26_impl.h index ea14c2731..aa45434d9 100644 --- a/src/field_10x26_impl.h +++ b/src/field_10x26_impl.h @@ -1014,6 +1014,7 @@ SECP256K1_INLINE static void secp256k1_fe_impl_sqr(secp256k1_fe *r, const secp25 SECP256K1_INLINE static void secp256k1_fe_impl_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag) { uint32_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = vflag + ~((uint32_t)0); mask1 = ~mask0; @@ -1097,6 +1098,7 @@ static SECP256K1_INLINE void secp256k1_fe_impl_half(secp256k1_fe *r) { static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { uint32_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = vflag + ~((uint32_t)0); mask1 = ~mask0; diff --git a/src/field_5x52_impl.h b/src/field_5x52_impl.h index 46dca6b98..3a976135e 100644 --- a/src/field_5x52_impl.h +++ b/src/field_5x52_impl.h @@ -349,6 +349,7 @@ SECP256K1_INLINE static void secp256k1_fe_impl_sqr(secp256k1_fe *r, const secp25 SECP256K1_INLINE static void secp256k1_fe_impl_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag) { uint64_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = vflag + ~((uint64_t)0); mask1 = ~mask0; @@ -416,6 +417,7 @@ static SECP256K1_INLINE void secp256k1_fe_impl_half(secp256k1_fe *r) { static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { uint64_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = vflag + ~((uint64_t)0); mask1 = ~mask0; diff --git a/src/group.h b/src/group.h index 05ae0d203..ee3ebbbef 100644 --- a/src/group.h +++ b/src/group.h @@ -169,10 +169,12 @@ static void secp256k1_ge_to_storage(secp256k1_ge_storage *r, const secp256k1_ge /** Convert a group element back from the storage type. */ static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storage *a); -/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ +/** If flag is 1, set *r equal to *a; if flag is 0, leave it. Constant-time. + * Both *r and *a must be initialized. Flag must be 0 or 1. */ static void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k1_gej *a, int flag); -/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ +/** If flag is 1, set *r equal to *a; if flag is 0, leave it. Constant-time. + * Both *r and *a must be initialized. Flag must be 0 or 1. */ static void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag); /** Rescale a jacobian point by b which must be non-zero. Constant-time. */ diff --git a/src/group_impl.h b/src/group_impl.h index 81a24b9d5..f5169650a 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -898,6 +898,7 @@ static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storag static SECP256K1_INLINE void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k1_gej *a, int flag) { SECP256K1_GEJ_VERIFY(r); SECP256K1_GEJ_VERIFY(a); + VERIFY_CHECK(flag == 0 || flag == 1); secp256k1_fe_cmov(&r->x, &a->x, flag); secp256k1_fe_cmov(&r->y, &a->y, flag); @@ -908,6 +909,7 @@ static SECP256K1_INLINE void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k } static SECP256K1_INLINE void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag) { + VERIFY_CHECK(flag == 0 || flag == 1); secp256k1_fe_storage_cmov(&r->x, &a->x, flag); secp256k1_fe_storage_cmov(&r->y, &a->y, flag); } diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 6a80a27b3..6fb328841 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -69,7 +69,8 @@ static int secp256k1_musig_secnonce_load(const secp256k1_context* ctx, secp256k1 return 1; } -/* If flag is true, invalidate the secnonce; otherwise leave it. Constant-time. */ +/* If flag is 1, invalidate the secnonce; if flag is 0, leave it. + * Constant-time. Flag must be 0 or 1. */ static void secp256k1_musig_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_musig_secnonce *secnonce, int flag) { secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag); /* The flag argument is usually classified. So, the line above makes the diff --git a/src/scalar.h b/src/scalar.h index 70f49b1cf..40d67191a 100644 --- a/src/scalar.h +++ b/src/scalar.h @@ -48,7 +48,7 @@ static void secp256k1_scalar_get_b32(unsigned char *bin, const secp256k1_scalar* /** Add two scalars together (modulo the group order). Returns whether it overflowed. */ static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b); -/** Conditionally add a power of two to a scalar. The result is not allowed to overflow. */ +/** Conditionally add a power of two to a scalar. The result is not allowed to overflow. Flag must be 0 or 1. */ static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag); /** Multiply two scalars (modulo the group order). */ @@ -78,7 +78,7 @@ static int secp256k1_scalar_is_even(const secp256k1_scalar *a); /** Check whether a scalar is higher than the group order divided by 2. */ static int secp256k1_scalar_is_high(const secp256k1_scalar *a); -/** Conditionally negate a number, in constant time. +/** Conditionally negate a number, in constant time. Flag must be 0 or 1. * Returns -1 if the number was negated, 1 otherwise */ static int secp256k1_scalar_cond_negate(secp256k1_scalar *a, int flag); @@ -95,7 +95,7 @@ static void secp256k1_scalar_split_lambda(secp256k1_scalar * SECP256K1_RESTRICT /** Multiply a and b (without taking the modulus!), divide by 2**shift, and round to the nearest integer. Shift must be at least 256. */ static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b, unsigned int shift); -/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ +/** If flag is 1, set *r equal to *a; if flag is 0, leave it. Constant-time. Both *r and *a must be initialized. Flag must be 0 or 1. */ static void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag); /** Check invariants on a scalar (no-op unless VERIFY is enabled). */ diff --git a/src/scalar_4x64_impl.h b/src/scalar_4x64_impl.h index 807b9b70a..a5bf18feb 100644 --- a/src/scalar_4x64_impl.h +++ b/src/scalar_4x64_impl.h @@ -120,6 +120,7 @@ static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { secp256k1_uint128 t; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(r); VERIFY_CHECK(bit < 256); @@ -259,6 +260,7 @@ static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { uint64_t mask = -vflag; uint64_t nonzero = (secp256k1_scalar_is_zero(r) != 0) - 1; secp256k1_uint128 t; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(r); secp256k1_u128_from_u64(&t, r->d[0] ^ mask); @@ -911,6 +913,7 @@ SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, static SECP256K1_INLINE void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag) { uint64_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(a); SECP256K1_CHECKMEM_CHECK_VERIFY(r->d, sizeof(r->d)); diff --git a/src/scalar_8x32_impl.h b/src/scalar_8x32_impl.h index 261049605..aa87b1d3d 100644 --- a/src/scalar_8x32_impl.h +++ b/src/scalar_8x32_impl.h @@ -147,6 +147,7 @@ static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { uint64_t t; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(r); VERIFY_CHECK(bit < 256); @@ -314,6 +315,7 @@ static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { uint32_t mask = -vflag; uint32_t nonzero = 0xFFFFFFFFUL * (secp256k1_scalar_is_zero(r) == 0); uint64_t t = (uint64_t)(r->d[0] ^ mask) + ((SECP256K1_N_0 + 1) & mask); + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(r); r->d[0] = t & nonzero; t >>= 32; @@ -709,6 +711,7 @@ SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, static SECP256K1_INLINE void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag) { uint32_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(a); SECP256K1_CHECKMEM_CHECK_VERIFY(r->d, sizeof(r->d)); diff --git a/src/scalar_low_impl.h b/src/scalar_low_impl.h index 84e1a380a..628bfd33e 100644 --- a/src/scalar_low_impl.h +++ b/src/scalar_low_impl.h @@ -56,6 +56,7 @@ static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { SECP256K1_SCALAR_VERIFY(r); + VERIFY_CHECK(flag == 0 || flag == 1); if (flag && bit < 32) *r += ((uint32_t)1 << bit); @@ -121,6 +122,7 @@ static int secp256k1_scalar_is_high(const secp256k1_scalar *a) { static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { SECP256K1_SCALAR_VERIFY(r); + VERIFY_CHECK(flag == 0 || flag == 1); if (flag) secp256k1_scalar_negate(r, r); @@ -157,6 +159,7 @@ SECP256K1_INLINE static int secp256k1_scalar_eq(const secp256k1_scalar *a, const static SECP256K1_INLINE void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag) { uint32_t mask0, mask1; volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); SECP256K1_SCALAR_VERIFY(a); SECP256K1_CHECKMEM_CHECK_VERIFY(r, sizeof(*r)); diff --git a/src/util.h b/src/util.h index 94e0837da..46ab38107 100644 --- a/src/util.h +++ b/src/util.h @@ -212,6 +212,7 @@ static SECP256K1_INLINE void secp256k1_memczero(void *s, size_t len, int flag) { take only be 0 or 1, which leads to variable time code. */ volatile int vflag = flag; unsigned char mask = -(unsigned char) vflag; + VERIFY_CHECK(flag == 0 || flag == 1); while (len) { *p &= ~mask; p++; @@ -294,7 +295,8 @@ static SECP256K1_INLINE int secp256k1_is_zero_array(const unsigned char *s, size return ret; } -/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized and non-negative.*/ +/** If flag is 1, set *r equal to *a; if flag is 0, leave it. Constant-time. + * Both *r and *a must be initialized and non-negative. Flag must be 0 or 1. */ static SECP256K1_INLINE void secp256k1_int_cmov(int *r, const int *a, int flag) { unsigned int mask0, mask1, r_masked, a_masked; /* Access flag with a volatile-qualified lvalue. @@ -302,6 +304,7 @@ static SECP256K1_INLINE void secp256k1_int_cmov(int *r, const int *a, int flag) take only be 0 or 1, which leads to variable time code. */ volatile int vflag = flag; + VERIFY_CHECK(flag == 0 || flag == 1); /* Casting a negative int to unsigned and back to int is implementation defined behavior */ VERIFY_CHECK(*r >= 0 && *a >= 0); From d822b2902106829bae1455cabec6bfca9a265a77 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 14 Dec 2025 10:31:41 -0500 Subject: [PATCH 14/26] test: split monolithic ellswift test into independent cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No behavior changes. Refactors the previously monolithic ElligatorSwift test into isolated, independent test cases. Doing so allows the test suite to execute these cases in parallel rather than sequentially. Overall, seen 35-40% tests time reduction locally. This is quite useful for the Debug build with no optimizations, which is noticeably slow. #### Local Debug-build Results (7 jobs): - master: 138.0 seconds. - this PR: 89.3 seconds. (~1.55× speedup, ~35% reduction) #### Local Release-build Results (7 jobs): - master: 9.5 seconds. - this PR: 5.9 seconds. (~1.61× speedup, ~38% reduction) --- src/modules/ellswift/tests_impl.h | 98 ++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/src/modules/ellswift/tests_impl.h b/src/modules/ellswift/tests_impl.h index 63c36e7ff..4cc7f4b55 100644 --- a/src/modules/ellswift/tests_impl.h +++ b/src/modules/ellswift/tests_impl.h @@ -177,9 +177,9 @@ static int ellswift_xdh_hash_x32(unsigned char *output, const unsigned char *x32 return 1; } -void run_ellswift_tests(void) { - int i = 0; - /* Test vectors. */ +/* Run the test vectors for ellswift encoding */ +void ellswift_encoding_test_vectors_tests(void) { + int i; for (i = 0; (unsigned)i < sizeof(ellswift_xswiftec_inv_tests) / sizeof(ellswift_xswiftec_inv_tests[0]); ++i) { const struct ellswift_xswiftec_inv_test *testcase = &ellswift_xswiftec_inv_tests[i]; int c; @@ -195,6 +195,11 @@ void run_ellswift_tests(void) { } } } +} + +/* Run the test vectors for ellswift decoding */ +void ellswift_decoding_test_vectors_tests(void) { + int i; for (i = 0; (unsigned)i < sizeof(ellswift_decode_tests) / sizeof(ellswift_decode_tests[0]); ++i) { const struct ellswift_decode_test *testcase = &ellswift_decode_tests[i]; secp256k1_pubkey pubkey; @@ -207,6 +212,11 @@ void run_ellswift_tests(void) { CHECK(fe_equal(&testcase->x, &ge.x)); CHECK(secp256k1_fe_is_odd(&ge.y) == testcase->odd_y); } +} + +/* Run the test vectors for ellswift expected xdh BIP324 shared secrets */ +void ellswift_xdh_test_vectors_tests(void) { + int i; for (i = 0; (unsigned)i < sizeof(ellswift_xdh_tests_bip324) / sizeof(ellswift_xdh_tests_bip324[0]); ++i) { const struct ellswift_xdh_test *test = &ellswift_xdh_tests_bip324[i]; unsigned char shared_secret[32]; @@ -223,7 +233,11 @@ void run_ellswift_tests(void) { CHECK(ret); CHECK(secp256k1_memcmp_var(shared_secret, test->shared_secret, 32) == 0); } - /* Verify that secp256k1_ellswift_encode + decode roundtrips. */ +} + +/* Verify that secp256k1_ellswift_encode + decode roundtrips */ +void ellswift_encode_decode_roundtrip_tests(void) { + int i; for (i = 0; i < 1000 * COUNT; i++) { unsigned char rnd32[32]; unsigned char ell64[64]; @@ -240,7 +254,11 @@ void run_ellswift_tests(void) { /* Compare with original. */ CHECK(secp256k1_ge_eq_var(&g, &g2)); } - /* Verify the behavior of secp256k1_ellswift_create */ +} + +/* Verify the behavior of secp256k1_ellswift_create */ +void ellswift_create_tests(void) { + int i; for (i = 0; i < 400 * COUNT; i++) { unsigned char auxrnd32[32], sec32[32]; secp256k1_scalar sec; @@ -262,7 +280,11 @@ void run_ellswift_tests(void) { secp256k1_ecmult(&res, NULL, &secp256k1_scalar_zero, &sec); CHECK(secp256k1_gej_eq_ge_var(&res, &dec)); } - /* Verify that secp256k1_ellswift_xdh computes the right shared X coordinate. */ +} + +/* Verify that secp256k1_ellswift_xdh computes the right shared X coordinate */ +void ellswift_compute_shared_secret_tests(void) { + int i; for (i = 0; i < 800 * COUNT; i++) { unsigned char ell64[64], sec32[32], share32[32]; secp256k1_scalar sec; @@ -293,6 +315,10 @@ void run_ellswift_tests(void) { /* Compare. */ CHECK(fe_equal(&res.x, &share_x)); } +} + +void ellswift_xdh_correctness_tests(void) { + int i; /* Verify the joint behavior of secp256k1_ellswift_xdh */ for (i = 0; i < 200 * COUNT; i++) { unsigned char auxrnd32a[32], auxrnd32b[32], auxrnd32a_bad[32], auxrnd32b_bad[32]; @@ -403,41 +429,47 @@ void run_ellswift_tests(void) { CHECK(secp256k1_memcmp_var(share32_bad, share32b, 32) != 0); } } +} - /* Test hash initializers. */ - { - secp256k1_sha256 sha_optimized; - /* "secp256k1_ellswift_encode" */ - static const unsigned char encode_tag[] = {'s', 'e', 'c', 'p', '2', '5', '6', 'k', '1', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'e', 'n', 'c', 'o', 'd', 'e'}; - /* "secp256k1_ellswift_create" */ - static const unsigned char create_tag[] = {'s', 'e', 'c', 'p', '2', '5', '6', 'k', '1', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'c', 'r', 'e', 'a', 't', 'e'}; - /* "bip324_ellswift_xonly_ecdh" */ - static const unsigned char bip324_tag[] = {'b', 'i', 'p', '3', '2', '4', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'x', 'o', 'n', 'l', 'y', '_', 'e', 'c', 'd', 'h'}; +/* Test hash initializers */ +void ellswift_hash_init_tests(void) { + secp256k1_sha256 sha_optimized; + /* "secp256k1_ellswift_encode" */ + static const unsigned char encode_tag[] = {'s', 'e', 'c', 'p', '2', '5', '6', 'k', '1', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'e', 'n', 'c', 'o', 'd', 'e'}; + /* "secp256k1_ellswift_create" */ + static const unsigned char create_tag[] = {'s', 'e', 'c', 'p', '2', '5', '6', 'k', '1', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'c', 'r', 'e', 'a', 't', 'e'}; + /* "bip324_ellswift_xonly_ecdh" */ + static const unsigned char bip324_tag[] = {'b', 'i', 'p', '3', '2', '4', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'x', 'o', 'n', 'l', 'y', '_', 'e', 'c', 'd', 'h'}; - /* Check that hash initialized by - * secp256k1_ellswift_sha256_init_encode has the expected - * state. */ - secp256k1_ellswift_sha256_init_encode(&sha_optimized); - test_sha256_tag_midstate(&sha_optimized, encode_tag, sizeof(encode_tag)); + /* Check that hash initialized by + * secp256k1_ellswift_sha256_init_encode has the expected + * state. */ + secp256k1_ellswift_sha256_init_encode(&sha_optimized); + test_sha256_tag_midstate(&sha_optimized, encode_tag, sizeof(encode_tag)); - /* Check that hash initialized by - * secp256k1_ellswift_sha256_init_create has the expected - * state. */ - secp256k1_ellswift_sha256_init_create(&sha_optimized); - test_sha256_tag_midstate(&sha_optimized, create_tag, sizeof(create_tag)); + /* Check that hash initialized by + * secp256k1_ellswift_sha256_init_create has the expected + * state. */ + secp256k1_ellswift_sha256_init_create(&sha_optimized); + test_sha256_tag_midstate(&sha_optimized, create_tag, sizeof(create_tag)); - /* Check that hash initialized by - * secp256k1_ellswift_sha256_init_bip324 has the expected - * state. */ - secp256k1_ellswift_sha256_init_bip324(&sha_optimized); - test_sha256_tag_midstate(&sha_optimized, bip324_tag, sizeof(bip324_tag)); - } + /* Check that hash initialized by + * secp256k1_ellswift_sha256_init_bip324 has the expected + * state. */ + secp256k1_ellswift_sha256_init_bip324(&sha_optimized); + test_sha256_tag_midstate(&sha_optimized, bip324_tag, sizeof(bip324_tag)); } /* --- Test registry --- */ -/* TODO: subdivide test in cases */ static const struct tf_test_entry tests_ellswift[] = { - CASE(ellswift_tests), + CASE1(ellswift_encoding_test_vectors_tests), + CASE1(ellswift_decoding_test_vectors_tests), + CASE1(ellswift_xdh_test_vectors_tests), + CASE1(ellswift_encode_decode_roundtrip_tests), + CASE1(ellswift_create_tests), + CASE1(ellswift_compute_shared_secret_tests), + CASE1(ellswift_xdh_correctness_tests), + CASE1(ellswift_hash_init_tests), }; #endif From 0406cfc4d1d414bbc806757bb42e540bf0d8a0c1 Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Thu, 18 Dec 2025 22:15:38 -0500 Subject: [PATCH 15/26] doc: include arg -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1 for cmake --- include/secp256k1.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/secp256k1.h b/include/secp256k1.h index b0a13b144..2799d4a1f 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -349,7 +349,8 @@ SECP256K1_API void secp256k1_context_destroy( * writes the message to stderr and calls abort. This default callback can be * replaced at link time if the preprocessor macro * USE_EXTERNAL_DEFAULT_CALLBACKS is defined, which is the case if the build - * has been configured with --enable-external-default-callbacks. Then the + * has been configured with --enable-external-default-callbacks (GNU Autotools) or + * -DSECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS=ON (CMake). Then the * following two symbols must be provided to link against: * - void secp256k1_default_illegal_callback_fn(const char *message, void *data); * - void secp256k1_default_error_callback_fn(const char *message, void *data); From bd5ced1fe174e4568261e66025b93c922d8e4be3 Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Mon, 29 Dec 2025 22:01:35 -0500 Subject: [PATCH 16/26] doc/bench: added help text for SECP256K1_BENCH_ITERS env var for bench_ecmult In addition a print message saying some tests were skipped was added --- src/bench_ecmult.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index c9a1d9002..e8fab1495 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -19,9 +19,12 @@ #define POINTS 32768 -static void help(char **argv) { +static void help(char **argv, int default_iters) { printf("Benchmark EC multiplication algorithms\n"); printf("\n"); + printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters); + printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n"); + printf("\n"); printf("Usage: %s \n", argv[0]); printf("The output shows the number of multiplied and summed points right after the\n"); printf("function name. The letter 'g' indicates that one of the points is the generator.\n"); @@ -308,7 +311,8 @@ int main(int argc, char **argv) { int i, p; size_t scratch_size; - int iters = get_iters(10000); + int default_iters = 10000; + int iters = get_iters(default_iters); data.ecmult_multi = secp256k1_ecmult_multi_var; @@ -316,7 +320,7 @@ int main(int argc, char **argv) { if(have_flag(argc, argv, "-h") || have_flag(argc, argv, "--help") || have_flag(argc, argv, "help")) { - help(argv); + help(argv, default_iters); return EXIT_SUCCESS; } else if(have_flag(argc, argv, "pippenger_wnaf")) { printf("Using pippenger_wnaf:\n"); @@ -328,7 +332,7 @@ int main(int argc, char **argv) { printf("Using simple algorithm:\n"); } else { fprintf(stderr, "%s: unrecognized argument '%s'.\n\n", argv[0], argv[1]); - help(argv); + help(argv, default_iters); return EXIT_FAILURE; } } @@ -381,6 +385,8 @@ int main(int argc, char **argv) { run_ecmult_multi_bench(&data, i << p, 1, iters); } } + } else { + printf("Skipping some benchmarks due to SECP256K1_BENCH_ITERS <= 2\n"); } if (data.scratch != NULL) { From 29ac4d8491b326ffaeb8e43d152a2d09049b9af3 Mon Sep 17 00:00:00 2001 From: Justsomebuddy Date: Wed, 14 Jan 2026 11:26:00 +0000 Subject: [PATCH 17/26] sage: verify Eisenstein integer connection for GLV constants Add assertions to verify that the GLV decomposition constants arise from the Eisenstein integer factorization of the group order N. The group order factors as N = pi * conj(pi) in Z[w], where pi = A - B*w is an Eisenstein prime. The GLV eigenvalue LAMBDA = B/A mod N, which is the image of w^2 under the isomorphism Z[w]/(pi) -> Z/NZ. --- sage/gen_split_lambda_constants.sage | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sage/gen_split_lambda_constants.sage b/sage/gen_split_lambda_constants.sage index 7d4359e0f..7a5761acd 100644 --- a/sage/gen_split_lambda_constants.sage +++ b/sage/gen_split_lambda_constants.sage @@ -81,6 +81,15 @@ assert (A1 + A2)/2 < sqrt(N) assert B1 < sqrt(N) assert B2 < sqrt(N) +# Verify connection to Eisenstein integers Z[w] where w = (-1 + sqrt(-3))/2. +# The group order N factors as N = pi * conj(pi) in Z[w], where pi = A - B*w +# is an Eisenstein prime with norm A^2 + A*B + B^2. The GLV endomorphism +# eigenvalue LAMBDA equals B/A mod N, which is the image of w^2 under the +# isomorphism Z[w]/(pi) -> Z/NZ (since w -> A/B and (A/B)^2 = B/A in Z/NZ). +A_EIS, B_EIS = -B1, A1 +assert A_EIS**2 + A_EIS*B_EIS + B_EIS**2 == N +assert Z(B_EIS / A_EIS) == LAMBDA + G1 = round((2**384)*B2/N) G2 = round((2**384)*(-B1)/N) From c09215f7af9db3e34427ff9a5633e7978acabca1 Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Thu, 8 Jan 2026 12:15:33 -0500 Subject: [PATCH 18/26] bench: fail early if user inputs invalid value for SECP256K1_BENCH_ITERS In this change the get_iters function was updated to print an error message and then return 0. In the functions that use get_iters they print the help text and then EXIT_FAILURE --- src/bench.c | 9 +++++++-- src/bench.h | 8 +++++++- src/bench_ecmult.c | 4 ++++ src/bench_internal.c | 6 +++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/bench.c b/src/bench.c index 04685d930..a5231b71e 100644 --- a/src/bench.c +++ b/src/bench.c @@ -177,8 +177,6 @@ int main(int argc, char** argv) { bench_data data; int d = argc == 1; - int default_iters = 20000; - int iters = get_iters(default_iters); /* Check for invalid user arguments */ char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", @@ -188,6 +186,13 @@ int main(int argc, char** argv) { size_t valid_args_size = sizeof(valid_args)/sizeof(valid_args[0]); int invalid_args = have_invalid_args(argc, argv, valid_args, valid_args_size); + int default_iters = 20000; + int iters = get_iters(default_iters); + if (iters == 0) { + help(default_iters); + return EXIT_FAILURE; + } + if (argc > 1) { if (have_flag(argc, argv, "-h") || have_flag(argc, argv, "--help") diff --git a/src/bench.h b/src/bench.h index 4e8e961b6..72a3f211b 100644 --- a/src/bench.h +++ b/src/bench.h @@ -150,7 +150,13 @@ static int have_invalid_args(int argc, char** argv, char** valid_args, size_t n) static int get_iters(int default_iters) { char* env = getenv("SECP256K1_BENCH_ITERS"); if (env) { - return strtol(env, NULL, 0); + char* endptr; + long int iters = strtol(env, &endptr, 0); + if (*endptr != '\0' || iters <= 0) { + printf("Error: Value of SECP256K1_BENCH_ITERS is not a positive integer: %s\n\n", env); + return 0; + } + return iters; } else { return default_iters; } diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index e8fab1495..bcf8b4315 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -313,6 +313,10 @@ int main(int argc, char **argv) { int default_iters = 10000; int iters = get_iters(default_iters); + if (iters == 0) { + help(argv, default_iters); + return EXIT_FAILURE; + } data.ecmult_multi = secp256k1_ecmult_multi_var; diff --git a/src/bench_internal.c b/src/bench_internal.c index 8688a4dc7..001bd25e0 100644 --- a/src/bench_internal.c +++ b/src/bench_internal.c @@ -385,9 +385,13 @@ static void bench_context(void* arg, int iters) { int main(int argc, char **argv) { bench_inv data; + int d = argc == 1; /* default */ int default_iters = 20000; int iters = get_iters(default_iters); - int d = argc == 1; /* default */ + if (iters == 0) { + help(default_iters); + return EXIT_FAILURE; + } if (argc > 1) { if (have_flag(argc, argv, "-h") From c4b6a81a6062492eb23c428afc978d45b20d36e8 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 26 Jan 2026 16:26:39 +0000 Subject: [PATCH 19/26] changelog: update in preparation for the v0.7.1 release --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c787b38..92ad92360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +#### Changed + - Tests: Introduced a unit test framework with support for parallel test execution, selective test running, and named command-line arguments. Run `./tests -help` for usage information. + +#### Fixed + - Increased the number of cases where the library attempts to clear secrets from the stack. + - build: Fixed x86_64 assembly feature check that could fail when user-provided `CFLAGS` included `-Werror`. This would cause the build to fall back to the slower C implementation instead of using the optimized x86_64 assembly. + ## [0.7.0] - 2025-07-21 #### Added From 20a209f11c0b688dae97ad5d2a515d00022d7799 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 26 Jan 2026 16:39:20 +0000 Subject: [PATCH 20/26] release: prepare for 0.7.1 --- CHANGELOG.md | 7 +++++-- configure.ac | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ad92360..29d23a96a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.7.1] - 2026-01-26 #### Changed - Tests: Introduced a unit test framework with support for parallel test execution, selective test running, and named command-line arguments. Run `./tests -help` for usage information. @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Increased the number of cases where the library attempts to clear secrets from the stack. - build: Fixed x86_64 assembly feature check that could fail when user-provided `CFLAGS` included `-Werror`. This would cause the build to fall back to the slower C implementation instead of using the optimized x86_64 assembly. +#### ABI Compatibility +The ABI is backward compatible with version 0.7.0. + ## [0.7.0] - 2025-07-21 #### Added @@ -194,7 +197,7 @@ This version was in fact never released. The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6). Therefore, this version number does not uniquely identify a set of source files. -[unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.7.0...HEAD +[0.7.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.7.0...v0.7.1 [0.7.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0 [0.5.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.0...v0.5.1 diff --git a/configure.ac b/configure.ac index 6028ee288..97ea22590 100644 --- a/configure.ac +++ b/configure.ac @@ -6,7 +6,7 @@ AC_PREREQ([2.60]) define(_PKG_VERSION_MAJOR, 0) define(_PKG_VERSION_MINOR, 7) define(_PKG_VERSION_PATCH, 1) -define(_PKG_VERSION_IS_RELEASE, false) +define(_PKG_VERSION_IS_RELEASE, true) # The library version is based on libtool versioning of the ABI. The set of # rules for updating the version can be found here: From ae7eb729c0450fcb5c79117e04ee92694a5a5e77 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 26 Jan 2026 19:18:39 +0000 Subject: [PATCH 21/26] release cleanup: bump version after 0.7.1 --- CHANGELOG.md | 3 +++ CMakeLists.txt | 4 ++-- configure.ac | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d23a96a..d4beb9c80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + ## [0.7.1] - 2026-01-26 #### Changed @@ -197,6 +199,7 @@ This version was in fact never released. The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6). Therefore, this version number does not uniquely identify a set of source files. +[Unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.7.1...HEAD [0.7.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.7.0...v0.7.1 [0.7.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 11dc3f6e5..01b14b537 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ project(libsecp256k1 # The package (a.k.a. release) version is based on semantic versioning 2.0.0 of # the API. All changes in experimental modules are treated as # backwards-compatible and therefore at most increase the minor version. - VERSION 0.7.1 + VERSION 0.7.2 DESCRIPTION "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1." HOMEPAGE_URL "https://github.com/bitcoin-core/secp256k1" LANGUAGES C @@ -22,7 +22,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. set(${PROJECT_NAME}_LIB_VERSION_CURRENT 6) -set(${PROJECT_NAME}_LIB_VERSION_REVISION 1) +set(${PROJECT_NAME}_LIB_VERSION_REVISION 2) set(${PROJECT_NAME}_LIB_VERSION_AGE 0) #============================= diff --git a/configure.ac b/configure.ac index 97ea22590..d7465612d 100644 --- a/configure.ac +++ b/configure.ac @@ -5,8 +5,8 @@ AC_PREREQ([2.60]) # backwards-compatible and therefore at most increase the minor version. define(_PKG_VERSION_MAJOR, 0) define(_PKG_VERSION_MINOR, 7) -define(_PKG_VERSION_PATCH, 1) -define(_PKG_VERSION_IS_RELEASE, true) +define(_PKG_VERSION_PATCH, 2) +define(_PKG_VERSION_IS_RELEASE, false) # The library version is based on libtool versioning of the ABI. The set of # rules for updating the version can be found here: @@ -14,7 +14,7 @@ define(_PKG_VERSION_IS_RELEASE, true) # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. define(_LIB_VERSION_CURRENT, 6) -define(_LIB_VERSION_REVISION, 1) +define(_LIB_VERSION_REVISION, 2) define(_LIB_VERSION_AGE, 0) AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_PATCH)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-dev]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) From 2542b4345196fba49dec0d794c6b94f80f3f8275 Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Mon, 2 Mar 2026 16:40:12 +0200 Subject: [PATCH 22/26] modules: Port bitcoin-core/secp256k1#1774 to zkp-specific code --- src/modules/bppp/bppp_util.h | 7 +++- src/modules/ecdsa_adaptor/dleq_impl.h | 20 +++------- src/modules/ecdsa_adaptor/main_impl.h | 49 ++++++++++--------------- src/modules/ecdsa_adaptor/tests_impl.h | 24 +++--------- src/modules/rangeproof/borromean_impl.h | 14 +++---- src/modules/whitelist/whitelist_impl.h | 15 +++----- src/secp256k1.c | 10 +---- 7 files changed, 46 insertions(+), 93 deletions(-) diff --git a/src/modules/bppp/bppp_util.h b/src/modules/bppp/bppp_util.h index 1debfb92c..ec979c69c 100644 --- a/src/modules/bppp/bppp_util.h +++ b/src/modules/bppp/bppp_util.h @@ -47,8 +47,11 @@ static int secp256k1_bppp_parse_one_of_points(secp256k1_ge *pt, const unsigned c /* Outputs a serialized point in compressed form. Returns 0 at point at infinity. */ static int secp256k1_bppp_serialize_pt(unsigned char *output, secp256k1_ge *lpt) { - size_t size; - return secp256k1_eckey_pubkey_serialize(lpt, output, &size, 1 /*compressed*/); + if (secp256k1_ge_is_infinity(lpt)) { + return 0; + } + secp256k1_eckey_pubkey_serialize33(lpt, output); + return 1; } /* little-endian encodes a uint64 */ diff --git a/src/modules/ecdsa_adaptor/dleq_impl.h b/src/modules/ecdsa_adaptor/dleq_impl.h index 11d2d6675..60e63a34a 100644 --- a/src/modules/ecdsa_adaptor/dleq_impl.h +++ b/src/modules/ecdsa_adaptor/dleq_impl.h @@ -20,16 +20,13 @@ static void secp256k1_nonce_function_dleq_sha256_tagged(secp256k1_sha256 *sha) { /* algo argument for nonce_function_ecdsa_adaptor to derive the nonce using a tagged hash function. */ static const unsigned char dleq_algo[] = {'D','L','E','Q'}; -static int secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) { +static void secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) { unsigned char buf[33]; size_t size = 33; - if (!secp256k1_eckey_pubkey_serialize(p, buf, &size, 1)) { - return 0; - } + secp256k1_eckey_pubkey_serialize33(p, buf); secp256k1_sha256_write(sha, buf, size); - return 1; } static int secp256k1_dleq_nonce(secp256k1_scalar *k, const unsigned char *sk32, const unsigned char *gen2_33, const unsigned char *p1_33, const unsigned char *p2_33, secp256k1_nonce_function_hardened_ecdsa_adaptor noncefp, void *ndata) { @@ -95,18 +92,11 @@ static int secp256k1_dleq_prove(const secp256k1_context* ctx, secp256k1_scalar * unsigned char gen2_33[33]; unsigned char p1_33[33]; unsigned char p2_33[33]; - size_t pubkey_size = 33; int ret; - if (!secp256k1_eckey_pubkey_serialize(gen2, gen2_33, &pubkey_size, 1)) { - return 0; - } - if (!secp256k1_eckey_pubkey_serialize(p1, p1_33, &pubkey_size, 1)) { - return 0; - } - if (!secp256k1_eckey_pubkey_serialize(p2, p2_33, &pubkey_size, 1)) { - return 0; - } + secp256k1_eckey_pubkey_serialize33(gen2, gen2_33); + secp256k1_eckey_pubkey_serialize33(p1, p1_33); + secp256k1_eckey_pubkey_serialize33(p2, p2_33); secp256k1_scalar_get_b32(sk32, sk); diff --git a/src/modules/ecdsa_adaptor/main_impl.h b/src/modules/ecdsa_adaptor/main_impl.h index e97bb5a0e..e43a4222a 100644 --- a/src/modules/ecdsa_adaptor/main_impl.h +++ b/src/modules/ecdsa_adaptor/main_impl.h @@ -11,20 +11,12 @@ #include "dleq_impl.h" /* (R, R', s', dleq_proof) */ -static int secp256k1_ecdsa_adaptor_sig_serialize(unsigned char *adaptor_sig162, secp256k1_ge *r, secp256k1_ge *rp, const secp256k1_scalar *sp, const secp256k1_scalar *dleq_proof_e, const secp256k1_scalar *dleq_proof_s) { - size_t size = 33; - - if (!secp256k1_eckey_pubkey_serialize(r, adaptor_sig162, &size, 1)) { - return 0; - } - if (!secp256k1_eckey_pubkey_serialize(rp, &adaptor_sig162[33], &size, 1)) { - return 0; - } +static void secp256k1_ecdsa_adaptor_sig_serialize(unsigned char *adaptor_sig162, secp256k1_ge *r, secp256k1_ge *rp, const secp256k1_scalar *sp, const secp256k1_scalar *dleq_proof_e, const secp256k1_scalar *dleq_proof_s) { + secp256k1_eckey_pubkey_serialize33(r, adaptor_sig162); + secp256k1_eckey_pubkey_serialize33(rp, &adaptor_sig162[33]); secp256k1_scalar_get_b32(&adaptor_sig162[66], sp); secp256k1_scalar_get_b32(&adaptor_sig162[98], dleq_proof_e); secp256k1_scalar_get_b32(&adaptor_sig162[130], dleq_proof_s); - - return 1; } static int secp256k1_ecdsa_adaptor_sig_deserialize(secp256k1_ge *r, secp256k1_scalar *sigr, secp256k1_ge *rp, secp256k1_scalar *sp, secp256k1_scalar *dleq_proof_e, secp256k1_scalar *dleq_proof_s, const unsigned char *adaptor_sig162) { @@ -162,7 +154,6 @@ int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char secp256k1_scalar n; unsigned char nonce32[32] = { 0 }; unsigned char buf33[33]; - size_t size = 33; int ret = 1; VERIFY_CHECK(ctx != NULL); @@ -183,7 +174,7 @@ int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char return 0; } - secp256k1_eckey_pubkey_serialize(&enckey_ge, buf33, &size, 1); + secp256k1_eckey_pubkey_serialize33(&enckey_ge, buf33); ret &= !!noncefp(nonce32, msg32, seckey32, buf33, ecdsa_adaptor_algo, sizeof(ecdsa_adaptor_algo), ndata); secp256k1_scalar_set_b32(&k, nonce32, NULL); ret &= !secp256k1_scalar_is_zero(&k); @@ -222,7 +213,7 @@ int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char ret &= !secp256k1_scalar_is_zero(&sp); /* return (R, R', s', dleq_proof) */ - ret &= secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig162, &nonce_pts[1], &nonce_pts[0], &sp, &dleq_proof_e, &dleq_proof_s); + secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig162, &nonce_pts[1], &nonce_pts[0], &sp, &dleq_proof_e, &dleq_proof_s); secp256k1_memczero(adaptor_sig162, 162, !ret); secp256k1_memclear_explicit(nonce32, sizeof(nonce32)); @@ -319,10 +310,10 @@ int secp256k1_ecdsa_adaptor_recover(const secp256k1_context* ctx, unsigned char secp256k1_scalar s, r; secp256k1_scalar deckey; secp256k1_ge enckey_expected_ge; + secp256k1_ge enckey_ge; secp256k1_gej enckey_expected_gej; unsigned char enckey33[33]; unsigned char enckey_expected33[33]; - size_t size = 33; int ret = 1; VERIFY_CHECK(ctx != NULL); @@ -349,23 +340,21 @@ int secp256k1_ecdsa_adaptor_recover(const secp256k1_context* ctx, unsigned char /* We declassify non-secret enckey_expected_ge to allow using it as a * branch point. */ secp256k1_declassify(ctx, &enckey_expected_ge, sizeof(enckey_expected_ge)); - if (!secp256k1_eckey_pubkey_serialize(&enckey_expected_ge, enckey_expected33, &size, 1)) { - /* Unreachable from tests (and other VERIFY builds) and therefore this - * branch should be ignored in test coverage analysis. - * - * Proof: - * eckey_pubkey_serialize fails <=> deckey = 0 - * deckey = 0 <=> s^-1 = 0 or sp = 0 - * case 1: s^-1 = 0 impossible by the definition of multiplicative - * inverse and because the scalar_inverse implementation - * VERIFY_CHECKs that the inputs are valid scalars. - * case 2: sp = 0 impossible because ecdsa_adaptor_sig_deserialize would have already failed - */ - return 0; - } - if (!secp256k1_ec_pubkey_serialize(ctx, enckey33, &size, enckey, SECP256K1_EC_COMPRESSED)) { + /* enckey_expected_ge cannot be infinity: + * + * Proof: + * enckey_expected_ge is infinity <=> deckey = 0 + * deckey = 0 <=> s^-1 = 0 or sp = 0 + * case 1: s^-1 = 0 impossible by the definition of multiplicative + * inverse and because the scalar_inverse implementation + * VERIFY_CHECKs that the inputs are valid scalars. + * case 2: sp = 0 impossible because ecdsa_adaptor_sig_deserialize would have already failed + */ + secp256k1_eckey_pubkey_serialize33(&enckey_expected_ge, enckey_expected33); + if (!secp256k1_pubkey_load(ctx, &enckey_ge, enckey)) { return 0; } + secp256k1_eckey_pubkey_serialize33(&enckey_ge, enckey33); if (secp256k1_memcmp_var(&enckey_expected33[1], &enckey33[1], 32) != 0) { return 0; } diff --git a/src/modules/ecdsa_adaptor/tests_impl.h b/src/modules/ecdsa_adaptor/tests_impl.h index 5e41a9c0c..3abc81277 100644 --- a/src/modules/ecdsa_adaptor/tests_impl.h +++ b/src/modules/ecdsa_adaptor/tests_impl.h @@ -39,7 +39,6 @@ static void dleq_tests_internal(void) { unsigned char p2_33[33]; unsigned char aux_rand[32]; int i; - size_t pubkey_size = 33; rand_point(&gen2); rand_scalar(&sk); @@ -62,19 +61,12 @@ static void dleq_tests_internal(void) { CHECK(secp256k1_dleq_verify(&s, &e, &p1, &p_tmp, &p2) == 0); CHECK(secp256k1_dleq_verify(&s, &e, &p1, &gen2, &p_tmp) == 0); } - { - secp256k1_ge p_inf; - secp256k1_ge_set_infinity(&p_inf); - CHECK(secp256k1_dleq_prove(CTX, &s, &e, &sk, &p_inf, &p1, &p2, NULL, NULL) == 0); - CHECK(secp256k1_dleq_prove(CTX, &s, &e, &sk, &gen2, &p_inf, &p2, NULL, NULL) == 0); - CHECK(secp256k1_dleq_prove(CTX, &s, &e, &sk, &gen2, &p1, &p_inf, NULL, NULL) == 0); - } /* Nonce tests */ secp256k1_scalar_get_b32(sk32, &sk); - CHECK(secp256k1_eckey_pubkey_serialize(&gen2, gen2_33, &pubkey_size, 1)); - CHECK(secp256k1_eckey_pubkey_serialize(&p1, p1_33, &pubkey_size, 1)); - CHECK(secp256k1_eckey_pubkey_serialize(&p2, p2_33, &pubkey_size, 1)); + secp256k1_eckey_pubkey_serialize33(&gen2, gen2_33); + secp256k1_eckey_pubkey_serialize33(&p1, p1_33); + secp256k1_eckey_pubkey_serialize33(&p2, p2_33); CHECK(secp256k1_dleq_nonce(&k, sk32, gen2_33, p1_33, p2_33, NULL, NULL) == 1); testrand_bytes_test(sk32, sizeof(sk32)); @@ -165,7 +157,7 @@ static void test_ecdsa_adaptor_spec_vectors_check_serialization(const unsigned c CHECK(expected == secp256k1_ecdsa_adaptor_sig_deserialize(&r, &sigr, &rp, &sp, &dleq_proof_e, &dleq_proof_s, adaptor_sig162)); if (expected == 1) { - CHECK(secp256k1_ecdsa_adaptor_sig_serialize(buf, &r, &rp, &sp, &dleq_proof_e, &dleq_proof_s) == 1); + secp256k1_ecdsa_adaptor_sig_serialize(buf, &r, &rp, &sp, &dleq_proof_e, &dleq_proof_s); CHECK(secp256k1_memcmp_var(buf, adaptor_sig162, 162) == 0); } } @@ -896,18 +888,12 @@ static void adaptor_tests_internal(void) { secp256k1_scalar sigr; secp256k1_scalar sp; secp256k1_scalar dleq_proof_s, dleq_proof_e; - secp256k1_ge p_inf; unsigned char adaptor_sig_tmp[162]; CHECK(secp256k1_ecdsa_adaptor_sig_deserialize(&r, &sigr, &rp, &sp, &dleq_proof_e, &dleq_proof_s, adaptor_sig) == 1); - CHECK(secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig_tmp, &r, &rp, &sp, &dleq_proof_e, &dleq_proof_s) == 1); + secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig_tmp, &r, &rp, &sp, &dleq_proof_e, &dleq_proof_s); CHECK(secp256k1_memcmp_var(adaptor_sig_tmp, adaptor_sig, sizeof(adaptor_sig_tmp)) == 0); - - /* Test adaptor_sig_serialize points at infinity */ - secp256k1_ge_set_infinity(&p_inf); - CHECK(secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig_tmp, &p_inf, &rp, &sp, &dleq_proof_e, &dleq_proof_s) == 0); - CHECK(secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig_tmp, &r, &p_inf, &sp, &dleq_proof_e, &dleq_proof_s) == 0); } { /* Test adaptor_sig_deserialize */ diff --git a/src/modules/rangeproof/borromean_impl.h b/src/modules/rangeproof/borromean_impl.h index 4906ce85f..2fff3c28c 100644 --- a/src/modules/rangeproof/borromean_impl.h +++ b/src/modules/rangeproof/borromean_impl.h @@ -60,7 +60,6 @@ int secp256k1_borromean_verify(secp256k1_scalar *evalues, const unsigned char *e size_t i; size_t j; size_t count; - size_t size; int overflow; VERIFY_CHECK(e0 != NULL); VERIFY_CHECK(s != NULL); @@ -88,12 +87,12 @@ int secp256k1_borromean_verify(secp256k1_scalar *evalues, const unsigned char *e } /* OPT: loop can be hoisted and split to use batch inversion across all the rings; this would make it much faster. */ secp256k1_ge_set_gej_var(&rge, &rgej); - secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1); + secp256k1_eckey_pubkey_serialize33(&rge, tmp); if (j != rsizes[i] - 1) { secp256k1_borromean_hash(tmp, m, mlen, tmp, 33, i, j + 1); secp256k1_scalar_set_b32(&ens, tmp, &overflow); } else { - secp256k1_sha256_write(&sha256_e0, tmp, size); + secp256k1_sha256_write(&sha256_e0, tmp, 33); } count++; } @@ -115,7 +114,6 @@ int secp256k1_borromean_sign(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, size_t i; size_t j; size_t count; - size_t size; int overflow; VERIFY_CHECK(ecmult_gen_ctx != NULL); VERIFY_CHECK(e0 != NULL); @@ -136,7 +134,7 @@ int secp256k1_borromean_sign(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, if (secp256k1_gej_is_infinity(&rgej)) { return 0; } - secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1); + secp256k1_eckey_pubkey_serialize33(&rge, tmp); for (j = secidx[i] + 1; j < rsizes[i]; j++) { secp256k1_borromean_hash(tmp, m, mlen, tmp, 33, i, j); secp256k1_scalar_set_b32(&ens, tmp, &overflow); @@ -152,9 +150,9 @@ int secp256k1_borromean_sign(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, return 0; } secp256k1_ge_set_gej_var(&rge, &rgej); - secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1); + secp256k1_eckey_pubkey_serialize33(&rge, tmp); } - secp256k1_sha256_write(&sha256_e0, tmp, size); + secp256k1_sha256_write(&sha256_e0, tmp, 33); count += rsizes[i]; } secp256k1_sha256_write(&sha256_e0, m, mlen); @@ -174,7 +172,7 @@ int secp256k1_borromean_sign(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, return 0; } secp256k1_ge_set_gej_var(&rge, &rgej); - secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1); + secp256k1_eckey_pubkey_serialize33(&rge, tmp); secp256k1_borromean_hash(tmp, m, mlen, tmp, 33, i, j + 1); secp256k1_scalar_set_b32(&ens, tmp, &overflow); if (overflow || secp256k1_scalar_is_zero(&ens)) { diff --git a/src/modules/whitelist/whitelist_impl.h b/src/modules/whitelist/whitelist_impl.h index bb244907f..edb90b4f7 100644 --- a/src/modules/whitelist/whitelist_impl.h +++ b/src/modules/whitelist/whitelist_impl.h @@ -18,9 +18,10 @@ static int secp256k1_whitelist_hash_pubkey(secp256k1_scalar* output, secp256k1_g secp256k1_ge_set_gej(&ge, pubkey); secp256k1_sha256_initialize(&sha); - if (!secp256k1_eckey_pubkey_serialize(&ge, c, &size, 1)) { + if (secp256k1_ge_is_infinity(&ge)) { return 0; } + secp256k1_eckey_pubkey_serialize33(&ge, c); secp256k1_sha256_write(&sha, c, size); secp256k1_sha256_finalize(&sha, h); secp256k1_sha256_clear(&sha); @@ -95,9 +96,7 @@ static int secp256k1_whitelist_compute_keys_and_message(const secp256k1_context* secp256k1_pubkey_load(ctx, &subkey_ge, sub_pubkey); /* commit to sub-key */ - if (!secp256k1_eckey_pubkey_serialize(&subkey_ge, c, &size, 1)) { - return 0; - } + secp256k1_eckey_pubkey_serialize33(&subkey_ge, c); secp256k1_sha256_write(&sha, c, size); for (i = 0; i < n_keys; i++) { secp256k1_ge offline_ge; @@ -106,14 +105,10 @@ static int secp256k1_whitelist_compute_keys_and_message(const secp256k1_context* /* commit to fixed keys */ secp256k1_pubkey_load(ctx, &offline_ge, &offline_pubkeys[i]); - if (!secp256k1_eckey_pubkey_serialize(&offline_ge, c, &size, 1)) { - return 0; - } + secp256k1_eckey_pubkey_serialize33(&offline_ge, c); secp256k1_sha256_write(&sha, c, size); secp256k1_pubkey_load(ctx, &online_ge, &online_pubkeys[i]); - if (!secp256k1_eckey_pubkey_serialize(&online_ge, c, &size, 1)) { - return 0; - } + secp256k1_eckey_pubkey_serialize33(&online_ge, c); secp256k1_sha256_write(&sha, c, size); /* compute tweaked keys */ diff --git a/src/secp256k1.c b/src/secp256k1.c index 9245c1260..69bc9d945 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -858,15 +858,7 @@ static void secp256k1_ge_serialize_ext(unsigned char *out33, secp256k1_ge* ge) { if (secp256k1_ge_is_infinity(ge)) { memset(out33, 0, 33); } else { - int ret; - size_t size = 33; - ret = secp256k1_eckey_pubkey_serialize(ge, out33, &size, 1); -#ifdef VERIFY - /* Serialize must succeed because the point is not at infinity */ - VERIFY_CHECK(ret && size == 33); -#else - (void) ret; -#endif + secp256k1_eckey_pubkey_serialize33(ge, out33); } } From d8e87e45f33c151949d7decfb9e8a0d02c1029a6 Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Mon, 2 Mar 2026 16:42:19 +0200 Subject: [PATCH 23/26] unit_test: bump MAX_ARGS from 150 to 200 --- src/unit_test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unit_test.h b/src/unit_test.h index bf301e539..d3b0f1538 100644 --- a/src/unit_test.h +++ b/src/unit_test.h @@ -13,7 +13,7 @@ /* Maximum number of command-line arguments. * Must be at least as large as the total number of tests * to allow specifying all tests individually. */ -#define MAX_ARGS 150 +#define MAX_ARGS 200 /* Maximum number of parallel jobs */ #define MAX_SUBPROCESSES 16 From d111d31293b479832c767946145702151897785d Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Mon, 2 Mar 2026 16:53:18 +0200 Subject: [PATCH 24/26] generator: Port bitcoin-core/secp256k1#1779 to zkp-specific code --- src/modules/generator/main_impl.h | 13 +++++++++++++ src/modules/generator/tests_impl.h | 29 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/modules/generator/main_impl.h b/src/modules/generator/main_impl.h index c3f1c027b..9ce2defb0 100644 --- a/src/modules/generator/main_impl.h +++ b/src/modules/generator/main_impl.h @@ -343,6 +343,9 @@ int secp256k1_pedersen_blind_sum(const secp256k1_context* ctx, unsigned char *bl VERIFY_CHECK(ctx != NULL); ARG_CHECK(blind_out != NULL); ARG_CHECK(blinds != NULL); + for (i = 0; i < n; i++) { + ARG_CHECK(blinds[i] != NULL); + } ARG_CHECK(npositive <= n); (void) ctx; secp256k1_scalar_set_int(&acc, 0); @@ -370,6 +373,12 @@ int secp256k1_pedersen_verify_tally(const secp256k1_context* ctx, const secp256k VERIFY_CHECK(ctx != NULL); ARG_CHECK(!pcnt || (commits != NULL)); ARG_CHECK(!ncnt || (ncommits != NULL)); + for (i = 0; i < pcnt; i++) { + ARG_CHECK(commits[i] != NULL); + } + for (i = 0; i < ncnt; i++) { + ARG_CHECK(ncommits[i] != NULL); + } (void) ctx; secp256k1_gej_set_infinity(&accj); for (i = 0; i < ncnt; i++) { @@ -394,6 +403,10 @@ int secp256k1_pedersen_blind_generator_blind_sum(const secp256k1_context* ctx, c ARG_CHECK(n_total == 0 || generator_blind != NULL); ARG_CHECK(n_total == 0 || blinding_factor != NULL); ARG_CHECK(n_total > n_inputs); + for (i = 0; i < n_total; i++) { + ARG_CHECK(generator_blind[i] != NULL); + ARG_CHECK(blinding_factor[i] != NULL); + } (void) ctx; if (n_total == 0) { diff --git a/src/modules/generator/tests_impl.h b/src/modules/generator/tests_impl.h index c9f60c0f6..14ec95dc9 100644 --- a/src/modules/generator/tests_impl.h +++ b/src/modules/generator/tests_impl.h @@ -227,6 +227,13 @@ static void test_pedersen_api(void) { CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_generator_blind_sum(CTX, NULL, &blind_ptr, &blind_out_ptr, 1, 0)); CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_generator_blind_sum(CTX, &val, NULL, &blind_out_ptr, 1, 0)); CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_generator_blind_sum(CTX, &val, &blind_ptr, NULL, 1, 0)); + /* check that NULL in array of generator_blind pointers is not allowed */ + blind_ptr = NULL; + CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_generator_blind_sum(CTX, &val, &blind_ptr, &blind_out_ptr, 1, 0)); + blind_ptr = blind; + /* check that NULL in array of blinding_factor pointers is not allowed */ + blind_out_ptr = NULL; + CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_generator_blind_sum(CTX, &val, &blind_ptr, &blind_out_ptr, 1, 0)); } static void test_pedersen_internal(void) { @@ -264,6 +271,14 @@ static void test_pedersen_internal(void) { secp256k1_scalar_get_b32(&blinds[i * 32], &s); } CHECK(secp256k1_pedersen_blind_sum(CTX, &blinds[(total - 1) * 32], bptr, total - 1, inputs)); + /* check that NULL in array of blind pointers is not allowed */ + for (i = 0; i < total - 1; i++) { + unsigned char blind_out[32]; + const unsigned char *original_ptr = bptr[i]; + bptr[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_sum(CTX, blind_out, bptr, total - 1, inputs)); + bptr[i] = original_ptr; + } for (i = 0; i < total; i++) { unsigned char result[33]; secp256k1_pedersen_commitment parse; @@ -275,6 +290,20 @@ static void test_pedersen_internal(void) { } CHECK(secp256k1_pedersen_verify_tally(CTX, cptr, inputs, &cptr[inputs], outputs)); CHECK(secp256k1_pedersen_verify_tally(CTX, &cptr[inputs], outputs, cptr, inputs)); + /* check that NULL in array of commits pointers is not allowed */ + for (i = 0; i < inputs; i++) { + const secp256k1_pedersen_commitment *original_ptr = cptr[i]; + cptr[i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_pedersen_verify_tally(CTX, cptr, inputs, &cptr[inputs], outputs)); + cptr[i] = original_ptr; + } + /* check that NULL in array of ncommits pointers is not allowed */ + for (i = 0; i < outputs; i++) { + const secp256k1_pedersen_commitment *original_ptr = cptr[inputs + i]; + cptr[inputs + i] = NULL; + CHECK_ILLEGAL(CTX, secp256k1_pedersen_verify_tally(CTX, cptr, inputs, &cptr[inputs], outputs)); + cptr[inputs + i] = original_ptr; + } if (inputs > 0 && values[0] > 0) { CHECK(!secp256k1_pedersen_verify_tally(CTX, cptr, inputs - 1, &cptr[inputs], outputs)); } From fe48cc9fa5d99a7d046e86a96c5dd270e1b7061c Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Mon, 2 Mar 2026 16:58:33 +0200 Subject: [PATCH 25/26] generator: Port bitcoin-core/secp256k1#1764 to zkp-specific code --- src/modules/generator/main_impl.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/generator/main_impl.h b/src/modules/generator/main_impl.h index 9ce2defb0..c58d66fe5 100644 --- a/src/modules/generator/main_impl.h +++ b/src/modules/generator/main_impl.h @@ -38,12 +38,13 @@ const secp256k1_generator *secp256k1_generator_h = &secp256k1_generator_h_intern static void secp256k1_generator_load(secp256k1_ge* ge, const secp256k1_generator* gen) { + secp256k1_fe x, y; int succeed; - succeed = secp256k1_fe_set_b32_limit(&ge->x, &gen->data[0]); + succeed = secp256k1_fe_set_b32_limit(&x, &gen->data[0]); VERIFY_CHECK(succeed != 0); - succeed = secp256k1_fe_set_b32_limit(&ge->y, &gen->data[32]); + succeed = secp256k1_fe_set_b32_limit(&y, &gen->data[32]); VERIFY_CHECK(succeed != 0); - ge->infinity = 0; + secp256k1_ge_set_xy(ge, &x, &y); (void) succeed; } From dc0bda5731c756a66786eccec96d9dc419b62d0f Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Mon, 2 Mar 2026 17:15:31 +0200 Subject: [PATCH 26/26] bench: Port bitcoin-core/secp256k1#1796 to zkp-specific code --- src/bench_bppp.c | 3 +++ src/bench_generator.c | 3 +++ src/bench_rangeproof.c | 3 +++ src/bench_whitelist.c | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/bench_bppp.c b/src/bench_bppp.c index b74aec6c4..63ad8ceaa 100644 --- a/src/bench_bppp.c +++ b/src/bench_bppp.c @@ -29,6 +29,9 @@ static void bench_bppp(void* arg, int iters) { int main(void) { bench_bppp_data data; int iters = get_iters(32); + if (iters == 0) { + return EXIT_FAILURE; + } data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); diff --git a/src/bench_generator.c b/src/bench_generator.c index ecccecb78..93af61290 100644 --- a/src/bench_generator.c +++ b/src/bench_generator.c @@ -50,6 +50,9 @@ static void bench_generator_generate_blinded(void* arg, int iters) { int main(void) { bench_generator_t data; int iters = get_iters(20000); + if (iters == 0) { + return EXIT_FAILURE; + } data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); diff --git a/src/bench_rangeproof.c b/src/bench_rangeproof.c index 76893456c..add1e1e15 100644 --- a/src/bench_rangeproof.c +++ b/src/bench_rangeproof.c @@ -58,6 +58,9 @@ int main(void) { data.min_bits = 32; iters = data.min_bits*get_iters(32); + if (iters == 0) { + return EXIT_FAILURE; + } run_benchmark("rangeproof_verify_bit", bench_rangeproof, bench_rangeproof_setup, NULL, &data, 10, iters); diff --git a/src/bench_whitelist.c b/src/bench_whitelist.c index 6cbe6fd60..f02f1e86a 100644 --- a/src/bench_whitelist.c +++ b/src/bench_whitelist.c @@ -69,6 +69,9 @@ int main(void) { size_t n_keys = 30; secp256k1_scalar ssub; int iters = get_iters(5); + if (iters == 0) { + return EXIT_FAILURE; + } data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);