From 399b582a5f7422bac3b1707d34ffad80e072f796 Mon Sep 17 00:00:00 2001 From: John Moffett Date: Mon, 8 Sep 2025 12:21:48 -0400 Subject: [PATCH 01/30] Split memclear into two versions secp256k1_memclear has the side effect of undefining bytes for valgrind checks. In some cases, we may want to zero bytes but allow subsequent reads. So we split memclear into memclear_explicit, which makes no guarantees about the content of the buffer on return, and memzero_explicit, which guarantees zero value on return. Change the memset in partial_sign to use memzero_explicit. --- src/ecmult_gen_impl.h | 8 ++++---- src/field_impl.h | 2 +- src/group_impl.h | 4 ++-- src/hash_impl.h | 10 +++++----- src/modules/ecdh/main_impl.h | 4 ++-- src/modules/ellswift/main_impl.h | 2 +- src/modules/musig/session_impl.h | 8 ++++---- src/modules/schnorrsig/main_impl.h | 6 +++--- src/scalar_impl.h | 2 +- src/secp256k1.c | 4 ++-- src/util.h | 19 ++++++++++++++++--- 11 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/ecmult_gen_impl.h b/src/ecmult_gen_impl.h index b943fe2ab..2159eed5e 100644 --- a/src/ecmult_gen_impl.h +++ b/src/ecmult_gen_impl.h @@ -277,8 +277,8 @@ static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context *ctx, secp25 /* Cleanup. */ secp256k1_fe_clear(&neg); secp256k1_ge_clear(&add); - secp256k1_memclear(&adds, sizeof(adds)); - secp256k1_memclear(&recoded, sizeof(recoded)); + secp256k1_memclear_explicit(&adds, sizeof(adds)); + secp256k1_memclear_explicit(&recoded, sizeof(recoded)); } /* Setup blinding values for secp256k1_ecmult_gen. */ @@ -310,7 +310,7 @@ static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const VERIFY_CHECK(seed32 != NULL); memcpy(keydata + 32, seed32, 32); secp256k1_rfc6979_hmac_sha256_initialize(&rng, keydata, 64); - secp256k1_memclear(keydata, sizeof(keydata)); + secp256k1_memclear_explicit(keydata, sizeof(keydata)); /* Compute projective blinding factor (cannot be 0). */ secp256k1_rfc6979_hmac_sha256_generate(&rng, nonce32, 32); @@ -331,7 +331,7 @@ static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const secp256k1_ge_set_gej(&ctx->ge_offset, &gb); /* Clean up. */ - secp256k1_memclear(nonce32, sizeof(nonce32)); + secp256k1_memclear_explicit(nonce32, sizeof(nonce32)); secp256k1_scalar_clear(&b); secp256k1_gej_clear(&gb); secp256k1_fe_clear(&f); diff --git a/src/field_impl.h b/src/field_impl.h index 896507a3a..7aa7de431 100644 --- a/src/field_impl.h +++ b/src/field_impl.h @@ -19,7 +19,7 @@ #endif SECP256K1_INLINE static void secp256k1_fe_clear(secp256k1_fe *a) { - secp256k1_memclear(a, sizeof(secp256k1_fe)); + secp256k1_memclear_explicit(a, sizeof(secp256k1_fe)); } SECP256K1_INLINE static int secp256k1_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { diff --git a/src/group_impl.h b/src/group_impl.h index b8f2395d9..81a24b9d5 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -337,11 +337,11 @@ static void secp256k1_ge_set_infinity(secp256k1_ge *r) { } static void secp256k1_gej_clear(secp256k1_gej *r) { - secp256k1_memclear(r, sizeof(secp256k1_gej)); + secp256k1_memclear_explicit(r, sizeof(secp256k1_gej)); } static void secp256k1_ge_clear(secp256k1_ge *r) { - secp256k1_memclear(r, sizeof(secp256k1_ge)); + secp256k1_memclear_explicit(r, sizeof(secp256k1_ge)); } static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int odd) { diff --git a/src/hash_impl.h b/src/hash_impl.h index 1065acd64..434191777 100644 --- a/src/hash_impl.h +++ b/src/hash_impl.h @@ -172,7 +172,7 @@ static void secp256k1_sha256_initialize_tagged(secp256k1_sha256 *hash, const uns } static void secp256k1_sha256_clear(secp256k1_sha256 *hash) { - secp256k1_memclear(hash, sizeof(*hash)); + secp256k1_memclear_explicit(hash, sizeof(*hash)); } static void secp256k1_hmac_sha256_initialize(secp256k1_hmac_sha256 *hash, const unsigned char *key, size_t keylen) { @@ -200,7 +200,7 @@ static void secp256k1_hmac_sha256_initialize(secp256k1_hmac_sha256 *hash, const rkey[n] ^= 0x5c ^ 0x36; } secp256k1_sha256_write(&hash->inner, rkey, sizeof(rkey)); - secp256k1_memclear(rkey, sizeof(rkey)); + secp256k1_memclear_explicit(rkey, sizeof(rkey)); } static void secp256k1_hmac_sha256_write(secp256k1_hmac_sha256 *hash, const unsigned char *data, size_t size) { @@ -211,12 +211,12 @@ static void secp256k1_hmac_sha256_finalize(secp256k1_hmac_sha256 *hash, unsigned unsigned char temp[32]; secp256k1_sha256_finalize(&hash->inner, temp); secp256k1_sha256_write(&hash->outer, temp, 32); - secp256k1_memclear(temp, sizeof(temp)); + secp256k1_memclear_explicit(temp, sizeof(temp)); secp256k1_sha256_finalize(&hash->outer, out32); } static void secp256k1_hmac_sha256_clear(secp256k1_hmac_sha256 *hash) { - secp256k1_memclear(hash, sizeof(*hash)); + secp256k1_memclear_explicit(hash, sizeof(*hash)); } static void secp256k1_rfc6979_hmac_sha256_initialize(secp256k1_rfc6979_hmac_sha256 *rng, const unsigned char *key, size_t keylen) { @@ -285,7 +285,7 @@ static void secp256k1_rfc6979_hmac_sha256_finalize(secp256k1_rfc6979_hmac_sha256 } static void secp256k1_rfc6979_hmac_sha256_clear(secp256k1_rfc6979_hmac_sha256 *rng) { - secp256k1_memclear(rng, sizeof(*rng)); + secp256k1_memclear_explicit(rng, sizeof(*rng)); } #undef Round diff --git a/src/modules/ecdh/main_impl.h b/src/modules/ecdh/main_impl.h index 842b5359e..9f2dfdd56 100644 --- a/src/modules/ecdh/main_impl.h +++ b/src/modules/ecdh/main_impl.h @@ -62,8 +62,8 @@ int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *output, const se ret = hashfp(output, x, y, data); - secp256k1_memclear(x, sizeof(x)); - secp256k1_memclear(y, sizeof(y)); + secp256k1_memclear_explicit(x, sizeof(x)); + secp256k1_memclear_explicit(y, sizeof(y)); secp256k1_scalar_clear(&s); secp256k1_ge_clear(&pt); secp256k1_gej_clear(&res); diff --git a/src/modules/ellswift/main_impl.h b/src/modules/ellswift/main_impl.h index 745a96913..0d13f7342 100644 --- a/src/modules/ellswift/main_impl.h +++ b/src/modules/ellswift/main_impl.h @@ -582,7 +582,7 @@ int secp256k1_ellswift_xdh(const secp256k1_context *ctx, unsigned char *output, /* Invoke hasher */ ret = hashfp(output, sx, ell_a64, ell_b64, data); - secp256k1_memclear(sx, sizeof(sx)); + secp256k1_memclear_explicit(sx, sizeof(sx)); secp256k1_fe_clear(&px); secp256k1_scalar_clear(&s); diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index d8dcd00c9..2c8778b3c 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -385,10 +385,10 @@ static void secp256k1_nonce_function_musig(secp256k1_scalar *k, const unsigned c secp256k1_scalar_set_b32(&k[i], buf, NULL); /* Attempt to erase secret data */ - secp256k1_memclear(buf, sizeof(buf)); + secp256k1_memclear_explicit(buf, sizeof(buf)); secp256k1_sha256_clear(&sha_tmp); } - secp256k1_memclear(rand, sizeof(rand)); + secp256k1_memclear_explicit(rand, sizeof(rand)); secp256k1_sha256_clear(&sha); } @@ -518,7 +518,7 @@ int secp256k1_musig_nonce_gen_counter(const secp256k1_context* ctx, secp256k1_mu if (!secp256k1_musig_nonce_gen_internal(ctx, secnonce, pubnonce, buf, seckey, &pubkey, msg32, keyagg_cache, extra_input32)) { return 0; } - secp256k1_memclear(seckey, sizeof(seckey)); + secp256k1_memclear_explicit(seckey, sizeof(seckey)); return 1; } @@ -679,7 +679,7 @@ int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_p ret = secp256k1_musig_secnonce_load(ctx, k, &pk, secnonce); /* Set nonce to zero to avoid nonce reuse. This will cause subsequent calls * of this function to fail */ - memset(secnonce, 0, sizeof(*secnonce)); + secp256k1_memzero_explicit(secnonce, sizeof(*secnonce)); if (!ret) { secp256k1_musig_partial_sign_clear(&sk, k); return 0; diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 13d924491..b410b19ec 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -94,7 +94,7 @@ static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *ms secp256k1_sha256_write(&sha, msg, msglen); secp256k1_sha256_finalize(&sha, nonce32); secp256k1_sha256_clear(&sha); - secp256k1_memclear(masked_key, sizeof(masked_key)); + secp256k1_memclear_explicit(masked_key, sizeof(masked_key)); return 1; } @@ -190,8 +190,8 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi secp256k1_memczero(sig64, 64, !ret); secp256k1_scalar_clear(&k); secp256k1_scalar_clear(&sk); - secp256k1_memclear(seckey, sizeof(seckey)); - secp256k1_memclear(nonce32, sizeof(nonce32)); + secp256k1_memclear_explicit(seckey, sizeof(seckey)); + secp256k1_memclear_explicit(nonce32, sizeof(nonce32)); secp256k1_gej_clear(&rj); return ret; diff --git a/src/scalar_impl.h b/src/scalar_impl.h index 0232a8c22..9965c2bab 100644 --- a/src/scalar_impl.h +++ b/src/scalar_impl.h @@ -28,7 +28,7 @@ static const secp256k1_scalar secp256k1_scalar_one = SECP256K1_SCALAR_CONST(0, 0 static const secp256k1_scalar secp256k1_scalar_zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); SECP256K1_INLINE static void secp256k1_scalar_clear(secp256k1_scalar *r) { - secp256k1_memclear(r, sizeof(secp256k1_scalar)); + secp256k1_memclear_explicit(r, sizeof(secp256k1_scalar)); } static int secp256k1_scalar_set_b32_seckey(secp256k1_scalar *r, const unsigned char *bin) { diff --git a/src/secp256k1.c b/src/secp256k1.c index 0915af779..26336a45c 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -499,7 +499,7 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m } secp256k1_rfc6979_hmac_sha256_finalize(&rng); - secp256k1_memclear(keydata, sizeof(keydata)); + secp256k1_memclear_explicit(keydata, sizeof(keydata)); secp256k1_rfc6979_hmac_sha256_clear(&rng); return 1; } @@ -550,7 +550,7 @@ static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_sc * seckey. As a result is_sec_valid is included in ret only after ret was * used as a branching variable. */ ret &= is_sec_valid; - secp256k1_memclear(nonce32, sizeof(nonce32)); + secp256k1_memclear_explicit(nonce32, sizeof(nonce32)); secp256k1_scalar_clear(&msg); secp256k1_scalar_clear(&non); secp256k1_scalar_clear(&sec); diff --git a/src/util.h b/src/util.h index 5f29f4076..94e0837da 100644 --- a/src/util.h +++ b/src/util.h @@ -219,8 +219,8 @@ static SECP256K1_INLINE void secp256k1_memczero(void *s, size_t len, int flag) { } } -/* Cleanses memory to prevent leaking sensitive info. Won't be optimized out. */ -static SECP256K1_INLINE void secp256k1_memclear(void *ptr, size_t len) { +/* Zeroes memory to prevent leaking sensitive info. Won't be optimized out. */ +static SECP256K1_INLINE void secp256k1_memzero_explicit(void *ptr, size_t len) { #if defined(_MSC_VER) /* SecureZeroMemory is guaranteed not to be optimized out by MSVC. */ SecureZeroMemory(ptr, len); @@ -242,6 +242,19 @@ static SECP256K1_INLINE void secp256k1_memclear(void *ptr, size_t len) { void *(*volatile const volatile_memset)(void *, int, size_t) = memset; volatile_memset(ptr, 0, len); #endif +} + +/* Cleanses memory to prevent leaking sensitive info. Won't be optimized out. + * The state of the memory after this call is unspecified so callers must not + * make any assumptions about its contents. + * + * In VERIFY builds, it has the side effect of marking the memory as undefined. + * This helps to detect use-after-clear bugs where code incorrectly reads from + * cleansed memory during testing. + */ +static SECP256K1_INLINE void secp256k1_memclear_explicit(void *ptr, size_t len) { + /* The current implementation zeroes, but callers must not rely on this */ + secp256k1_memzero_explicit(ptr, len); #ifdef VERIFY SECP256K1_CHECKMEM_UNDEFINE(ptr, len); #endif @@ -277,7 +290,7 @@ static SECP256K1_INLINE int secp256k1_is_zero_array(const unsigned char *s, size } ret = (acc == 0); /* acc may contain secret values. Try to explicitly clear it. */ - secp256k1_memclear(&acc, sizeof(acc)); + secp256k1_memclear_explicit(&acc, sizeof(acc)); return ret; } From 0c91c5604130a56bb1628fd12c56edf60de966b0 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Fri, 12 Sep 2025 03:53:06 +0200 Subject: [PATCH 02/30] test: introduce group order byte-array constant for deduplication --- src/modules/ecdh/tests_impl.h | 8 ++------ src/tests.c | 22 +++------------------- src/testutil.h | 8 ++++++++ 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/modules/ecdh/tests_impl.h b/src/modules/ecdh/tests_impl.h index 6888f18c6..826513110 100644 --- a/src/modules/ecdh/tests_impl.h +++ b/src/modules/ecdh/tests_impl.h @@ -90,12 +90,7 @@ static void test_ecdh_generator_basepoint(void) { static void test_bad_scalar(void) { unsigned char s_zero[32] = { 0 }; - unsigned char s_overflow[32] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, - 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, - 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41 - }; + unsigned char s_overflow[32] = { 0 }; unsigned char s_rand[32] = { 0 }; unsigned char output[32]; secp256k1_scalar rand; @@ -107,6 +102,7 @@ static void test_bad_scalar(void) { CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_rand) == 1); /* Try to multiply it by bad values */ + memcpy(s_overflow, secp256k1_group_order_bytes, 32); CHECK(secp256k1_ecdh(CTX, output, &point, s_zero, NULL, NULL) == 0); CHECK(secp256k1_ecdh(CTX, output, &point, s_overflow, NULL, NULL) == 0); /* ...and a good one */ diff --git a/src/tests.c b/src/tests.c index 28bec5904..cb3b3c424 100644 --- a/src/tests.c +++ b/src/tests.c @@ -6036,12 +6036,7 @@ static void run_ec_pubkey_parse_test(void) { } static void run_eckey_edge_case_test(void) { - const unsigned char orderc[32] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, - 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, - 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41 - }; + const unsigned char *orderc = secp256k1_group_order_bytes; const unsigned char zeros[sizeof(secp256k1_pubkey)] = {0x00}; unsigned char ctmp[33]; unsigned char ctmp2[33]; @@ -6355,13 +6350,7 @@ static int nonce_function_test_retry(unsigned char *nonce32, const unsigned char return 1; } if (counter < 5) { - static const unsigned char order[] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE, - 0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B, - 0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41 - }; - memcpy(nonce32, order, 32); + memcpy(nonce32, secp256k1_group_order_bytes, 32); if (counter == 4) { nonce32[31]++; } @@ -7379,12 +7368,7 @@ static void test_ecdsa_edge_cases(void) { /* Privkey export where pubkey is the point at infinity. */ { unsigned char privkey[300]; - unsigned char seckey[32] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, - 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, - 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41, - }; + const unsigned char *seckey = secp256k1_group_order_bytes; size_t outlen = 300; CHECK(!ec_privkey_export_der(CTX, privkey, &outlen, seckey, 0)); outlen = 300; diff --git a/src/testutil.h b/src/testutil.h index 64b3bb41c..480f6a1a0 100644 --- a/src/testutil.h +++ b/src/testutil.h @@ -11,6 +11,14 @@ #include "testrand.h" #include "util.h" +/* group order of the secp256k1 curve in 32-byte big endian representation */ +static const unsigned char secp256k1_group_order_bytes[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41 +}; + static void testutil_random_fe(secp256k1_fe *x) { unsigned char bin[32]; do { From 9cce7038635afa7e94c434f1571a8e435350cb51 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 9 Sep 2025 15:35:47 -0400 Subject: [PATCH 03/30] refactor: move 'gettime_i64()' to tests_common.h Relocate the clock time getter to tests_common.h to make it easily reusable across test programs. This will be useful for the upcoming unit test framework. Context - why not placing it inside testutil.h?: The bench program links against the production-compiled library, not its own compiled version. Therefore, `gettime_i64()` cannot be moved to testutil.h, because testutil.h calls `secp256k1_pubkey_save()`, which exists only in the internal secp256k1.c and not in the public API. --- Makefile.am | 1 + src/bench.h | 22 +--------------------- src/tests_common.h | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 src/tests_common.h diff --git a/Makefile.am b/Makefile.am index d511853b0..4cc97babc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,6 +45,7 @@ noinst_HEADERS += src/precomputed_ecmult.h noinst_HEADERS += src/precomputed_ecmult_gen.h noinst_HEADERS += src/assumptions.h noinst_HEADERS += src/checkmem.h +noinst_HEADERS += src/tests_common.h noinst_HEADERS += src/testutil.h noinst_HEADERS += src/util.h noinst_HEADERS += src/util_local_visibility.h diff --git a/src/bench.h b/src/bench.h index 232fb35fc..4e8e961b6 100644 --- a/src/bench.h +++ b/src/bench.h @@ -12,27 +12,7 @@ #include #include -#if (defined(_MSC_VER) && _MSC_VER >= 1900) -# include -#else -# include -#endif - -static int64_t gettime_i64(void) { -#if (defined(_MSC_VER) && _MSC_VER >= 1900) - /* C11 way to get wallclock time */ - struct timespec tv; - if (!timespec_get(&tv, TIME_UTC)) { - fputs("timespec_get failed!", stderr); - exit(EXIT_FAILURE); - } - return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; -#else - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL; -#endif -} +#include "tests_common.h" #define FP_EXP (6) #define FP_MULT (1000000LL) diff --git a/src/tests_common.h b/src/tests_common.h new file mode 100644 index 000000000..a341633bb --- /dev/null +++ b/src/tests_common.h @@ -0,0 +1,42 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_TESTS_COMMON_H +#define SECP256K1_TESTS_COMMON_H + +/*********************************************************************** + * Test Support Utilities + * + * This file provides general-purpose functions for tests and benchmark + * programs. Unlike testutil.h, this file is not linked to the library, + * allowing each program to choose whether to run against the production + * API or access library internals directly. + ***********************************************************************/ + +#include + +#if (defined(_MSC_VER) && _MSC_VER >= 1900) +# include +#else +# include +#endif + +static int64_t gettime_i64(void) { +#if (defined(_MSC_VER) && _MSC_VER >= 1900) + /* C11 way to get wallclock time */ + struct timespec tv; + if (!timespec_get(&tv, TIME_UTC)) { + fputs("timespec_get failed!", stderr); + exit(EXIT_FAILURE); + } + return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL; +#endif +} + +#endif /* SECP256K1_TESTS_COMMON_H */ From 7321bdf27bdc6a68875c61510c1a0d9ec4b0b7e3 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 15 Sep 2025 01:51:33 +0200 Subject: [PATCH 04/30] doc: clarify API doc of `secp256k1_ecdsa_recover` return value Co-authored-by: Tim Ruffing --- include/secp256k1_recovery.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/include/secp256k1_recovery.h b/include/secp256k1_recovery.h index 93a2e4ccb..2430f9939 100644 --- a/include/secp256k1_recovery.h +++ b/include/secp256k1_recovery.h @@ -92,7 +92,17 @@ SECP256K1_API int secp256k1_ecdsa_sign_recoverable( /** Recover an ECDSA public key from a signature. * - * Returns: 1: public key successfully recovered (which guarantees a correct signature). + * Successful public key recovery guarantees that the signature, after normalization, + * passes `secp256k1_ecdsa_verify`. Thus, explicit verification is not necessary. + * + * However, a recoverable signature that successfully passes `secp256k1_ecdsa_recover`, + * when converted to a non-recoverable signature (using + * `secp256k1_ecdsa_recoverable_signature_convert`), is not guaranteed to be + * normalized and thus not guaranteed to pass `secp256k1_ecdsa_verify`. If a + * normalized signature is required, call `secp256k1_ecdsa_signature_normalize` + * after `secp256k1_ecdsa_recoverable_signature_convert`. + * + * Returns: 1: public key successfully recovered * 0: otherwise. * Args: ctx: pointer to a context object. * Out: pubkey: pointer to the recovered public key. From dfe284ed2d24ab44705ca5ecd8f3f7e1b8e4c278 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Tue, 16 Sep 2025 23:01:06 +0200 Subject: [PATCH 05/30] bench: improve context creation in ECDH benchmark Calling `secp256k1_context_create` with `SECP256K1_FLAGS_TYPE_CONTEXT` seems to be not strictly API-compliant, as the only allowed (non-deprecated) value is `SECP256K1_CONTEXT_NONE`, even if the former happens to map to the latter currently. Fix this by not dynamically creating a context in the first place and switch to using the static context, as it is sufficient for this benchmark and presumably matches what the "no capabilities" comment intended back then. --- src/modules/ecdh/bench_impl.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/ecdh/bench_impl.h b/src/modules/ecdh/bench_impl.h index c23aaa94d..8924e1fab 100644 --- a/src/modules/ecdh/bench_impl.h +++ b/src/modules/ecdh/bench_impl.h @@ -10,7 +10,7 @@ #include "../../../include/secp256k1_ecdh.h" typedef struct { - secp256k1_context *ctx; + const secp256k1_context *ctx; secp256k1_pubkey point; unsigned char scalar[32]; } bench_ecdh_data; @@ -46,12 +46,9 @@ static void run_ecdh_bench(int iters, int argc, char** argv) { bench_ecdh_data data; int d = argc == 1; - /* create a context with no capabilities */ - data.ctx = secp256k1_context_create(SECP256K1_FLAGS_TYPE_CONTEXT); + data.ctx = secp256k1_context_static; if (d || have_flag(argc, argv, "ecdh")) run_benchmark("ecdh", bench_ecdh, bench_ecdh_setup, NULL, &data, 10, iters); - - secp256k1_context_destroy(data.ctx); } #endif /* SECP256K1_MODULE_ECDH_BENCH_H */ From ab560078aa9c7e977fade4ceae4a20ef8e5be025 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:19:12 +0100 Subject: [PATCH 06/30] build: Fix warnings in x86_64 assembly check This change fixes: - `-Wuninitialized` in both Autotools and CMake; - `-Wreturn-type` in CMake only. --- build-aux/m4/bitcoin_secp.m4 | 2 +- cmake/CheckX86_64Assembly.cmake | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build-aux/m4/bitcoin_secp.m4 b/build-aux/m4/bitcoin_secp.m4 index 048267fa6..1428d4d9b 100644 --- a/build-aux/m4/bitcoin_secp.m4 +++ b/build-aux/m4/bitcoin_secp.m4 @@ -3,7 +3,7 @@ AC_DEFUN([SECP_X86_64_ASM_CHECK],[ AC_MSG_CHECKING(for x86_64 assembly availability) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]],[[ - uint64_t a = 11, tmp; + uint64_t a = 11, tmp = 0; __asm__ __volatile__("movq \@S|@0x100000000,%1; mulq %%rsi" : "+a"(a) : "S"(tmp) : "cc", "%rdx"); ]])], [has_x86_64_asm=yes], [has_x86_64_asm=no]) AC_MSG_RESULT([$has_x86_64_asm]) diff --git a/cmake/CheckX86_64Assembly.cmake b/cmake/CheckX86_64Assembly.cmake index ae82cd476..ca18919e0 100644 --- a/cmake/CheckX86_64Assembly.cmake +++ b/cmake/CheckX86_64Assembly.cmake @@ -4,10 +4,11 @@ function(check_x86_64_assembly) check_c_source_compiles(" #include - int main() + int main(void) { - uint64_t a = 11, tmp; + uint64_t a = 11, tmp = 0; __asm__ __volatile__(\"movq $0x100000000,%1; mulq %%rsi\" : \"+a\"(a) : \"S\"(tmp) : \"cc\", \"%rdx\"); + return 0; } " HAVE_X86_64_ASM) set(HAVE_X86_64_ASM ${HAVE_X86_64_ASM} PARENT_SCOPE) From 895f53d1cf9719f34b5248afe6aab33deeb8e26c Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Wed, 20 Aug 2025 09:57:10 +0200 Subject: [PATCH 07/30] docs: Clarify that callback can be called more than once --- include/secp256k1.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/secp256k1.h b/include/secp256k1.h index f55a6ab97..ae31ff708 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -340,9 +340,9 @@ SECP256K1_API void secp256k1_context_destroy( * * On the other hand, during debug stage, one would want to be informed about * such mistakes, and the default (crashing) may be inadvisable. - * When this callback is triggered, the API function called is guaranteed not - * to cause a crash, though its return value and output arguments are - * undefined. + * Should this callback return instead of crashing, the return value and output + * arguments of the API function call are undefined. Moreover, the same API + * call may trigger the callback again in this case. * * When this function has not been called (or called with fn==NULL), then the * default handler will be used. The library provides a default handler which From 4d90585feaa52711133deb0c5a3cb32fca73d042 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Wed, 20 Aug 2025 09:59:54 +0200 Subject: [PATCH 08/30] docs: Improve API docs of _context_set_illegal_callback --- include/secp256k1.h | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/include/secp256k1.h b/include/secp256k1.h index ae31ff708..b0a13b144 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -261,7 +261,7 @@ SECP256K1_DEPRECATED("Use secp256k1_context_static instead"); * secp256k1_context_create (or secp256k1_context_preallocated_create), which will * take care of performing the self tests. * - * If the tests fail, this function will call the default error handler to abort the + * If the tests fail, this function will call the default error callback to abort the * program (see secp256k1_context_set_error_callback). */ SECP256K1_API void secp256k1_selftest(void); @@ -334,36 +334,37 @@ SECP256K1_API void secp256k1_context_destroy( * an API call. It will only trigger for violations that are mentioned * explicitly in the header. * - * The philosophy is that these shouldn't be dealt with through a - * specific return value, as calling code should not have branches to deal with - * the case that this code itself is broken. + * The philosophy is that these shouldn't be dealt with through a specific + * return value, as calling code should not have branches to deal with the case + * that this code itself is broken. * * On the other hand, during debug stage, one would want to be informed about - * such mistakes, and the default (crashing) may be inadvisable. - * Should this callback return instead of crashing, the return value and output - * arguments of the API function call are undefined. Moreover, the same API - * call may trigger the callback again in this case. - * - * When this function has not been called (or called with fn==NULL), then the - * default handler will be used. The library provides a default handler which - * writes the message to stderr and calls abort. This default handler can be + * such mistakes, and the default (crashing) may be inadvisable. Should this + * callback return instead of crashing, the return value and output arguments + * of the API function call are undefined. Moreover, the same API call may + * trigger the callback again in this case. + * + * When this function has not been called (or called with fun==NULL), then the + * default callback will be used. The library provides a default callback which + * 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 * 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); - * The library can call these default handlers even before a proper callback data + * The library may call a default callback even before a proper callback data * pointer could have been set using secp256k1_context_set_illegal_callback or * secp256k1_context_set_error_callback, e.g., when the creation of a context - * fails. In this case, the corresponding default handler will be called with + * fails. In this case, the corresponding default callback will be called with * the data pointer argument set to NULL. * * Args: ctx: pointer to a context object. * In: fun: pointer to a function to call when an illegal argument is * passed to the API, taking a message and an opaque pointer. - * (NULL restores the default handler.) - * data: the opaque pointer to pass to fun above, must be NULL for the default handler. + * (NULL restores the default callback.) + * data: the opaque pointer to pass to fun above, must be NULL for the + * default callback. * * See also secp256k1_context_set_error_callback. */ @@ -380,8 +381,8 @@ SECP256K1_API void secp256k1_context_set_illegal_callback( * to abort the program. * * This can only trigger in case of a hardware failure, miscompilation, - * memory corruption, serious bug in the library, or other error would can - * otherwise result in undefined behaviour. It will not trigger due to mere + * memory corruption, serious bug in the library, or other error that would + * result in undefined behaviour. It will not trigger due to mere * incorrect usage of the API (see secp256k1_context_set_illegal_callback * for that). After this callback returns, anything may happen, including * crashing. @@ -389,9 +390,10 @@ SECP256K1_API void secp256k1_context_set_illegal_callback( * Args: ctx: pointer to a context object. * In: fun: pointer to a function to call when an internal error occurs, * taking a message and an opaque pointer (NULL restores the - * default handler, see secp256k1_context_set_illegal_callback + * default callback, see secp256k1_context_set_illegal_callback * for details). - * data: the opaque pointer to pass to fun above, must be NULL for the default handler. + * data: the opaque pointer to pass to fun above, must be NULL for the + * default callback. * * See also secp256k1_context_set_illegal_callback. */ From 48789dafc2a866bbc639184f0387637c0decb8c5 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 3 Sep 2025 10:59:37 -0400 Subject: [PATCH 09/30] test: introduce (mini) unit test framework Lightweight unit testing framework, providing a structured way to define, execute, and report tests. It includes a central test registry, a flexible command-line argument parser of the form "--key=value" / "-k=value" / "-key=value" (facilitating future framework extensions), ability to run tests in parallel and accumulated test time logging reports. So far the supported command-line args are: - "--jobs=" or "-j=" to specify the number of parallel workers. - "--seed=" to specify the RNG seed (random if not set). - "--iterations=" or "-i=" to specify the number of iterations. Compatibility Note: To stay compatible with previous versions, the framework also supports the two original positional arguments: the iterations count and the RNG seed (in that order). --- Makefile.am | 4 +- configure.ac | 8 ++ src/CMakeLists.txt | 14 +- src/tests.c | 329 ++++++++++++++++++++++++------------------- src/unit_test.c | 342 +++++++++++++++++++++++++++++++++++++++++++++ src/unit_test.h | 120 ++++++++++++++++ 6 files changed, 674 insertions(+), 143 deletions(-) create mode 100644 src/unit_test.c create mode 100644 src/unit_test.h diff --git a/Makefile.am b/Makefile.am index 4cc97babc..dc798575e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,8 @@ noinst_HEADERS += src/assumptions.h noinst_HEADERS += src/checkmem.h noinst_HEADERS += src/tests_common.h noinst_HEADERS += src/testutil.h +noinst_HEADERS += src/unit_test.h +noinst_HEADERS += src/unit_test.c noinst_HEADERS += src/util.h noinst_HEADERS += src/util_local_visibility.h noinst_HEADERS += src/int128.h @@ -121,7 +123,7 @@ if USE_TESTS TESTS += noverify_tests noinst_PROGRAMS += noverify_tests noverify_tests_SOURCES = src/tests.c -noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) +noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) $(TEST_DEFINES) noverify_tests_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB) noverify_tests_LDFLAGS = -static if !ENABLE_COVERAGE diff --git a/configure.ac b/configure.ac index 2f156ddc2..6028ee288 100644 --- a/configure.ac +++ b/configure.ac @@ -443,6 +443,14 @@ if test x"$enable_experimental" = x"no"; then fi fi +# Check for concurrency support (tests only) +if test "x$enable_tests" != x"no"; then + AC_CHECK_HEADERS([sys/types.h sys/wait.h unistd.h]) + AS_IF([test "x$ac_cv_header_sys_types_h" = xyes && test "x$ac_cv_header_sys_wait_h" = xyes && + test "x$ac_cv_header_unistd_h" = xyes], [TEST_DEFINES="-DSUPPORTS_CONCURRENCY=1"], TEST_DEFINES="") + AC_SUBST(TEST_DEFINES) +fi + ### ### Generate output ### diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa3b2903e..ecbbbbe8e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,15 +134,27 @@ if(SECP256K1_BUILD_BENCHMARK) endif() if(SECP256K1_BUILD_TESTS) + include(CheckIncludeFile) + check_include_file(sys/types.h HAVE_SYS_TYPES_H) + check_include_file(sys/wait.h HAVE_SYS_WAIT_H) + check_include_file(unistd.h HAVE_UNISTD_H) + + set(TEST_DEFINITIONS "") + if(HAVE_SYS_TYPES_H AND HAVE_SYS_WAIT_H AND HAVE_UNISTD_H) + list(APPEND TEST_DEFINITIONS SUPPORTS_CONCURRENCY=1) + endif() + add_executable(noverify_tests tests.c) target_link_libraries(noverify_tests secp256k1_precomputed secp256k1_asm) + target_compile_definitions(noverify_tests PRIVATE ${TEST_DEFINITIONS}) add_test(NAME secp256k1_noverify_tests COMMAND noverify_tests) if(NOT CMAKE_BUILD_TYPE STREQUAL "Coverage") add_executable(tests tests.c) - target_compile_definitions(tests PRIVATE VERIFY) + target_compile_definitions(tests PRIVATE VERIFY ${TEST_DEFINITIONS}) target_link_libraries(tests secp256k1_precomputed secp256k1_asm) add_test(NAME secp256k1_tests COMMAND tests) endif() + unset(TEST_DEFINITIONS) endif() if(SECP256K1_BUILD_EXHAUSTIVE_TESTS) diff --git a/src/tests.c b/src/tests.c index 28bec5904..efcdbdc8b 100644 --- a/src/tests.c +++ b/src/tests.c @@ -25,6 +25,8 @@ #include "checkmem.h" #include "testutil.h" #include "util.h" +#include "unit_test.h" +#include "unit_test.c" #include "../contrib/lax_der_parsing.c" #include "../contrib/lax_der_privatekey_parsing.c" @@ -37,7 +39,6 @@ #define CONDITIONAL_TEST(cnt, nam) if (COUNT < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else -static int COUNT = 16; static secp256k1_context *CTX = NULL; static secp256k1_context *STATIC_CTX = NULL; @@ -227,6 +228,12 @@ static void run_static_context_tests(int use_prealloc) { } } +static void run_all_static_context_tests(void) +{ + run_static_context_tests(0); + run_static_context_tests(1); +} + static void run_proper_context_tests(int use_prealloc) { int32_t dummy = 0; secp256k1_context *my_ctx, *my_ctx_fresh; @@ -349,6 +356,12 @@ static void run_proper_context_tests(int use_prealloc) { secp256k1_context_preallocated_destroy(NULL); } +static void run_all_proper_context_tests(void) +{ + run_proper_context_tests(0); + run_proper_context_tests(1); +} + static void run_scratch_tests(void) { const size_t adj_alloc = ((500 + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; @@ -7666,179 +7679,213 @@ static void run_cmov_tests(void) { ge_storage_cmov_test(); } -int main(int argc, char **argv) { - /* Disable buffering for stdout to improve reliability of getting - * diagnostic information. Happens right at the start of main because - * setbuf must be used before any other operation on the stream. */ - setbuf(stdout, NULL); - /* Also disable buffering for stderr because it's not guaranteed that it's - * unbuffered on all systems. */ - setbuf(stderr, NULL); - - /* find iteration count */ - if (argc > 1) { - COUNT = strtol(argv[1], NULL, 0); - } else { - const char* env = getenv("SECP256K1_TEST_ITERS"); - if (env && strlen(env) > 0) { - COUNT = strtol(env, NULL, 0); - } - } - if (COUNT <= 0) { - fputs("An iteration count of 0 or less is not allowed.\n", stderr); - return EXIT_FAILURE; - } - printf("test count = %i\n", COUNT); - - /* run test RNG tests (must run before we really initialize the test RNG) */ - run_xoshiro256pp_tests(); +/* --------------------------------------------------------- */ +/* Test Registry */ +/* --------------------------------------------------------- */ - /* find random seed */ - testrand_init(argc > 2 ? argv[2] : NULL); +/* --- Special test cases that must run before RNG initialization --- */ +static const struct tf_test_entry tests_no_rng[] = { + CASE(xoshiro256pp_tests), +}; +static const struct tf_test_module registry_modules_no_rng = MAKE_TEST_MODULE(no_rng); + +/* --- Standard test cases start here --- */ +static const struct tf_test_entry tests_general[] = { + CASE(selftest_tests), + CASE(all_proper_context_tests), + CASE(all_static_context_tests), + CASE(deprecated_context_flags_test), + CASE(scratch_tests), +}; - /*** Setup test environment ***/ +static const struct tf_test_entry tests_integer[] = { +#ifdef SECP256K1_WIDEMUL_INT128 + CASE(int128_tests), +#endif + CASE(ctz_tests), + CASE(modinv_tests), + CASE(inverse_tests), +}; - /* Create a global context available to all tests */ - CTX = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - /* Randomize the context only with probability 15/16 - to make sure we test without context randomization from time to time. - TODO Reconsider this when recalibrating the tests. */ - if (testrand_bits(4)) { - unsigned char rand32[32]; - testrand256(rand32); - CHECK(secp256k1_context_randomize(CTX, rand32)); - } - /* Make a writable copy of secp256k1_context_static in order to test the effect of API functions - that write to the context. The API does not support cloning the static context, so we use - memcpy instead. The user is not supposed to copy a context but we should still ensure that - the API functions handle copies of the static context gracefully. */ - STATIC_CTX = malloc(sizeof(*secp256k1_context_static)); - CHECK(STATIC_CTX != NULL); - memcpy(STATIC_CTX, secp256k1_context_static, sizeof(secp256k1_context)); - CHECK(!secp256k1_context_is_proper(STATIC_CTX)); +static const struct tf_test_entry tests_hash[] = { + CASE(sha256_known_output_tests), + CASE(sha256_counter_tests), + CASE(hmac_sha256_tests), + CASE(rfc6979_hmac_sha256_tests), + CASE(tagged_sha256_tests), +}; - /*** Run actual tests ***/ +static const struct tf_test_entry tests_scalar[] = { + CASE(scalar_tests), +}; - /* selftest tests */ - run_selftest_tests(); +static const struct tf_test_entry tests_field[] = { + CASE(field_half), + CASE(field_misc), + CASE(field_convert), + CASE(field_be32_overflow), + CASE(fe_mul), + CASE(sqr), + CASE(sqrt), +}; - /* context tests */ - run_proper_context_tests(0); run_proper_context_tests(1); - run_static_context_tests(0); run_static_context_tests(1); - run_deprecated_context_flags_test(); +static const struct tf_test_entry tests_group[] = { + CASE(ge), + CASE(gej), + CASE(group_decompress), +}; - /* scratch tests */ - run_scratch_tests(); +static const struct tf_test_entry tests_ecmult[] = { + CASE(ecmult_pre_g), + CASE(wnaf), + CASE(point_times_order), + CASE(ecmult_near_split_bound), + CASE(ecmult_chain), + CASE(ecmult_constants), + CASE(ecmult_gen_blind), + CASE(ecmult_const_tests), + CASE(ecmult_multi_tests), + CASE(ec_combine), +}; - /* integer arithmetic tests */ -#ifdef SECP256K1_WIDEMUL_INT128 - run_int128_tests(); -#endif - run_ctz_tests(); - run_modinv_tests(); - run_inverse_tests(); - - /* sorting tests */ - run_hsort_tests(); - - /* hash tests */ - run_sha256_known_output_tests(); - run_sha256_counter_tests(); - run_hmac_sha256_tests(); - run_rfc6979_hmac_sha256_tests(); - run_tagged_sha256_tests(); - - /* scalar tests */ - run_scalar_tests(); - - /* field tests */ - run_field_half(); - run_field_misc(); - run_field_convert(); - run_field_be32_overflow(); - run_fe_mul(); - run_sqr(); - run_sqrt(); - - /* group tests */ - run_ge(); - run_gej(); - run_group_decompress(); - - /* ecmult tests */ - run_ecmult_pre_g(); - run_wnaf(); - run_point_times_order(); - run_ecmult_near_split_bound(); - run_ecmult_chain(); - run_ecmult_constants(); - run_ecmult_gen_blind(); - run_ecmult_const_tests(); - run_ecmult_multi_tests(); - run_ec_combine(); - - /* endomorphism tests */ - run_endomorphism_tests(); - - /* EC point parser test */ - run_ec_pubkey_parse_test(); - - /* EC key edge cases */ - run_eckey_edge_case_test(); - - /* EC key arithmetic test */ - run_eckey_negate_test(); +static const struct tf_test_entry tests_ec[] = { + CASE(endomorphism_tests), + CASE(ec_pubkey_parse_test), + CASE(eckey_edge_case_test), + CASE(eckey_negate_test), +}; #ifdef ENABLE_MODULE_ECDH - /* ecdh tests */ - run_ecdh_tests(); +static const struct tf_test_entry tests_ecdh[] = { + CASE(ecdh_tests), +}; #endif - /* ecdsa tests */ - run_ec_illegal_argument_tests(); - run_pubkey_comparison(); - run_pubkey_sort(); - run_random_pubkeys(); - run_ecdsa_der_parse(); - run_ecdsa_sign_verify(); - run_ecdsa_end_to_end(); - run_ecdsa_edge_cases(); - run_ecdsa_wycheproof(); +static const struct tf_test_entry tests_ecdsa[] = { + CASE(ec_illegal_argument_tests), + CASE(pubkey_comparison), + CASE(pubkey_sort), + CASE(random_pubkeys), + CASE(ecdsa_der_parse), + CASE(ecdsa_sign_verify), + CASE(ecdsa_end_to_end), + CASE(ecdsa_edge_cases), + CASE(ecdsa_wycheproof), +}; #ifdef ENABLE_MODULE_RECOVERY +static const struct tf_test_entry tests_recovery[] = { /* ECDSA pubkey recovery tests */ - run_recovery_tests(); + CASE(recovery_tests), +}; #endif #ifdef ENABLE_MODULE_EXTRAKEYS - run_extrakeys_tests(); +static const struct tf_test_entry tests_extrakeys[] = { + CASE(extrakeys_tests), +}; #endif #ifdef ENABLE_MODULE_SCHNORRSIG - run_schnorrsig_tests(); +static const struct tf_test_entry tests_schnorrsig[] = { + CASE(schnorrsig_tests), +}; #endif #ifdef ENABLE_MODULE_MUSIG - run_musig_tests(); +static const struct tf_test_entry tests_musig[] = { + CASE(musig_tests), +}; #endif #ifdef ENABLE_MODULE_ELLSWIFT - run_ellswift_tests(); +static const struct tf_test_entry tests_ellswift[] = { + CASE(ellswift_tests), +}; #endif - /* util tests */ - run_secp256k1_memczero_test(); - run_secp256k1_is_zero_array_test(); - run_secp256k1_byteorder_tests(); +static const struct tf_test_entry tests_utils[] = { + CASE(hsort_tests), + CASE(secp256k1_memczero_test), + CASE(secp256k1_is_zero_array_test), + CASE(secp256k1_byteorder_tests), + CASE(cmov_tests), +}; - run_cmov_tests(); +/* Register test modules */ +static const struct tf_test_module registry_modules[] = { + MAKE_TEST_MODULE(general), + MAKE_TEST_MODULE(integer), + MAKE_TEST_MODULE(hash), + MAKE_TEST_MODULE(scalar), + MAKE_TEST_MODULE(field), + MAKE_TEST_MODULE(group), + MAKE_TEST_MODULE(ecmult), + MAKE_TEST_MODULE(ec), +#ifdef ENABLE_MODULE_ECDH + MAKE_TEST_MODULE(ecdh), +#endif + MAKE_TEST_MODULE(ecdsa), +#ifdef ENABLE_MODULE_RECOVERY + MAKE_TEST_MODULE(recovery), +#endif +#ifdef ENABLE_MODULE_EXTRAKEYS + MAKE_TEST_MODULE(extrakeys), +#endif +#ifdef ENABLE_MODULE_SCHNORRSIG + MAKE_TEST_MODULE(schnorrsig), +#endif +#ifdef ENABLE_MODULE_MUSIG + MAKE_TEST_MODULE(musig), +#endif +#ifdef ENABLE_MODULE_ELLSWIFT + MAKE_TEST_MODULE(ellswift), +#endif + MAKE_TEST_MODULE(utils), +}; - /*** Tear down test environment ***/ +/* Setup test environment */ +static int setup(void) { + /* Create a global context available to all tests */ + CTX = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + /* Randomize the context only with probability 15/16 + to make sure we test without context randomization from time to time. + TODO Reconsider this when recalibrating the tests. */ + if (testrand_bits(4)) { + unsigned char rand32[32]; + testrand256(rand32); + CHECK(secp256k1_context_randomize(CTX, rand32)); + } + /* Make a writable copy of secp256k1_context_static in order to test the effect of API functions + that write to the context. The API does not support cloning the static context, so we use + memcpy instead. The user is not supposed to copy a context but we should still ensure that + the API functions handle copies of the static context gracefully. */ + STATIC_CTX = malloc(sizeof(*secp256k1_context_static)); + CHECK(STATIC_CTX != NULL); + memcpy(STATIC_CTX, secp256k1_context_static, sizeof(secp256k1_context)); + CHECK(!secp256k1_context_is_proper(STATIC_CTX)); + return 0; +} + +/* Shutdown test environment */ +static int teardown(void) { free(STATIC_CTX); secp256k1_context_destroy(CTX); + return 0; +} - testrand_finish(); +int main(int argc, char **argv) { + struct tf_framework tf = {0}; + tf.registry_modules = registry_modules; + tf.num_modules = sizeof(registry_modules) / sizeof(registry_modules[0]); + tf.registry_no_rng = ®istry_modules_no_rng; - printf("no problems found\n"); - return EXIT_SUCCESS; + /* Add context creation/destruction functions */ + tf.fn_setup = setup; + tf.fn_teardown = teardown; + + /* Init and run framework */ + if (tf_init(&tf, argc, argv) != 0) return EXIT_FAILURE; + return tf_run(&tf); } + diff --git a/src/unit_test.c b/src/unit_test.c new file mode 100644 index 000000000..914594bc1 --- /dev/null +++ b/src/unit_test.c @@ -0,0 +1,342 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#include +#include +#include + +#if defined(SUPPORTS_CONCURRENCY) +#include +#include +#include +#endif + +#include "unit_test.h" +#include "testrand.h" +#include "tests_common.h" + +#define UNUSED(x) (void)(x) + +/* Number of times certain tests will run */ +int COUNT = 16; + +static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf); +static int parse_iterations(const char* key, const char* value, struct tf_framework* tf); +static int parse_seed(const char* key, const char* value, struct tf_framework* tf); + +/* Mapping table: key -> handler */ +typedef int (*ArgHandler)(const char* key, const char* value, struct tf_framework* tf); +struct ArgMap { + const char* key; + ArgHandler handler; +}; + +/* + * Main entry point for handling command-line arguments. + * + * Developers should extend this map whenever new command-line + * options are introduced. Each new argument should be validated, + * converted to the appropriate type, and stored in 'tf->args' struct. + */ +static struct ArgMap arg_map[] = { + { "j", parse_jobs_count }, { "jobs", parse_jobs_count }, + { "i", parse_iterations }, { "iterations", parse_iterations }, + { "seed", parse_seed }, + { NULL, NULL } /* sentinel */ +}; + +/* Display options that are not printed elsewhere */ +static void print_args(const struct tf_args* args) { + printf("iterations = %d\n", COUNT); + printf("jobs = %d. %s execution.\n", args->num_processes, args->num_processes > 1 ? "Parallel" : "Sequential"); +} + +/* Main entry point for reading environment variables */ +static int read_env(struct tf_framework* tf) { + const char* env_iter = getenv("SECP256K1_TEST_ITERS"); + if (env_iter && strlen(env_iter) > 0) { + return parse_iterations("i", env_iter, tf); + } + return 0; +} + +static int parse_arg(const char* key, const char* value, struct tf_framework* tf) { + int i; + for (i = 0; arg_map[i].key != NULL; i++) { + if (strcmp(key, arg_map[i].key) == 0) { + return arg_map[i].handler(key, value, tf); + } + } + /* Unknown key: report just so typos don't silently pass. */ + fprintf(stderr, "Unknown argument '-%s=%s'\n", key, value); + return -1; +} + +static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf) { + char* ptr_val; + long val = strtol(value, &ptr_val, 10); /* base 10 */ + if (*ptr_val != '\0') { + fprintf(stderr, "Invalid number for -%s=%s\n", key, value); + return -1; + } + if (val < 0 || val > MAX_SUBPROCESSES) { + fprintf(stderr, "Arg '-%s' out of range: '%ld'. Range: 0..%d\n", key, val, MAX_SUBPROCESSES); + return -1; + } + tf->args.num_processes = (int) val; + return 0; +} + +static int parse_iterations(const char* key, const char* value, struct tf_framework* tf) { + UNUSED(key); UNUSED(tf); + if (!value) return 0; + COUNT = (int) strtol(value, NULL, 0); + if (COUNT <= 0) { + fputs("An iteration count of 0 or less is not allowed.\n", stderr); + return -1; + } + return 0; +} + +static int parse_seed(const char* key, const char* value, struct tf_framework* tf) { + UNUSED(key); + tf->args.custom_seed = (!value || strcmp(value, "NULL") == 0) ? NULL : value; + return 0; +} + +/* Strip up to two leading dashes */ +static const char* normalize_key(const char* arg, const char** err_msg) { + const char* key; + if (!arg || arg[0] != '-') { + *err_msg = "missing initial dash"; + return NULL; + } + /* single-dash short option */ + if (arg[1] != '-') return arg + 1; + + /* double-dash checks now */ + if (arg[2] == '\0') { + *err_msg = "missing option name after double dash"; + return NULL; + } + + if (arg[2] == '-') { + *err_msg = "too many leading dashes"; + return NULL; + } + + key = arg + 2; + if (key[1] == '\0') { + *err_msg = "short option cannot use double dash"; + return NULL; + } + return key; +} + +/* Read args: all must be in the form -key=value, --key=value or -key=value */ +static int read_args(int argc, char** argv, int start, struct tf_framework* tf) { + int i; + const char* key; + const char* value; + char* eq; + const char* err_msg = "unknown error"; + for (i = start; i < argc; i++) { + char* raw_arg = argv[i]; + if (!raw_arg || raw_arg[0] != '-') { + fprintf(stderr, "Invalid arg '%s': must start with '-'\n", raw_arg ? raw_arg : "(null)"); + return -1; + } + + key = normalize_key(raw_arg, &err_msg); + if (!key || *key == '\0') { + fprintf(stderr, "Invalid arg '%s': %s. Must be -k=value or --key=value\n", raw_arg, err_msg); + return -1; + } + + eq = strchr(raw_arg, '='); + if (!eq || eq == raw_arg + 1) { + fprintf(stderr, "Invalid arg '%s': must be -k=value or --key=value\n", raw_arg); + return -1; + } + + *eq = '\0'; /* split key and value */ + value = eq + 1; + if (!value || *value == '\0') { /* value is empty */ + fprintf(stderr, "Invalid arg '%s': value cannot be empty\n", raw_arg); + return -1; + } + + if (parse_arg(key, value, tf) != 0) return -1; + } + return 0; +} + +static void run_test(const struct tf_test_entry* t) { + printf("Running %s..\n", t->name); + t->func(); + printf("%s PASSED\n", t->name); +} + +/* Process tests in sequential order */ +static int run_sequential(struct tf_framework* tf) { + tf_test_ref ref; + const struct tf_test_module* mdl; + for (ref.group = 0; ref.group < tf->num_modules; ref.group++) { + mdl = &tf->registry_modules[ref.group]; + for (ref.idx = 0; ref.idx < mdl->size; ref.idx++) { + run_test(&mdl->data[ref.idx]); + } + } + return EXIT_SUCCESS; +} + +#if defined(SUPPORTS_CONCURRENCY) +/* Process tests in parallel */ +static int run_concurrent(struct tf_framework* tf) { + /* Sub-processes info */ + pid_t workers[MAX_SUBPROCESSES]; + int pipefd[2]; + int status = EXIT_SUCCESS; + int it; /* loop iterator */ + tf_test_ref ref; /* test index */ + + if (pipe(pipefd) != 0) { + perror("Error during pipe setup"); + return EXIT_FAILURE; + } + + /* Launch worker processes */ + for (it = 0; it < tf->args.num_processes; it++) { + pid_t pid = fork(); + if (pid < 0) { + perror("Error during process fork"); + return EXIT_FAILURE; + } + if (pid == 0) { + /* Child worker: read jobs from the shared pipe */ + close(pipefd[1]); /* children never write */ + while (read(pipefd[0], &ref, sizeof(ref)) == sizeof(ref)) { + run_test(&tf->registry_modules[ref.group].data[ref.idx]); + } + _exit(EXIT_SUCCESS); /* finish child process */ + } else { + /* Parent: save worker pid */ + workers[it] = pid; + } + } + + /* Parent: write all tasks into the pipe */ + close(pipefd[0]); /* close read end */ + for (ref.group = 0; ref.group < tf->num_modules; ref.group++) { + const struct tf_test_module* mdl = &tf->registry_modules[ref.group]; + for (ref.idx = 0; ref.idx < mdl->size; ref.idx++) { + if (write(pipefd[1], &ref, sizeof(ref)) == -1) { + perror("Error during workload distribution"); + close(pipefd[1]); + return EXIT_FAILURE; + } + } + } + /* Close write end to signal EOF */ + close(pipefd[1]); + /* Wait for all workers */ + for (it = 0; it < tf->args.num_processes; it++) { + int ret = 0; + if (waitpid(workers[it], &ret, 0) == -1 || ret != 0) { + status = EXIT_FAILURE; + } + } + + return status; +} +#endif + +static int tf_init(struct tf_framework* tf, int argc, char** argv) +{ + /* Caller must set the registry and its size before calling tf_init */ + if (tf->registry_modules == NULL || tf->num_modules <= 0) { + fprintf(stderr, "Error: tests registry not provided or empty\n"); + return EXIT_FAILURE; + } + + /* Initialize command-line options */ + tf->args.num_processes = 0; + tf->args.custom_seed = NULL; + + /* Disable buffering for stdout to improve reliability of getting + * diagnostic information. Happens right at the start of main because + * setbuf must be used before any other operation on the stream. */ + setbuf(stdout, NULL); + /* Also disable buffering for stderr because it's not guaranteed that it's + * unbuffered on all systems. */ + setbuf(stderr, NULL); + + /* Parse env args */ + if (read_env(tf) != 0) return EXIT_FAILURE; + + /* Parse command-line args */ + if (argc > 1) { + int named_arg_start = 1; /* index to begin processing named arguments */ + if (argc - 1 > MAX_ARGS) { /* first arg is always the binary path */ + fprintf(stderr, "Too many command-line arguments (max: %d)\n", MAX_ARGS); + return EXIT_FAILURE; + } + + /* Compatibility Note: The first two args were the number of iterations and the seed. */ + /* If provided, parse them and adjust the starting index for named arguments accordingly. */ + if (argv[1][0] != '-') { + int has_seed = argc > 2 && argv[2][0] != '-'; + if (parse_iterations("i", argv[1], tf) != 0) return EXIT_FAILURE; + if (has_seed) parse_seed("seed", argv[2], tf); + named_arg_start = has_seed ? 3 : 2; + } + if (read_args(argc, argv, named_arg_start, tf) != 0) { + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} + +static int tf_run(struct tf_framework* tf) { + /* Process exit status */ + int status; + /* Loop iterator */ + int it; + /* Initial test time */ + int64_t start_time = gettime_i64(); + + /* Log configuration */ + print_args(&tf->args); + + /* Run test RNG tests (must run before we really initialize the test RNG) */ + /* Note: currently, these tests are executed sequentially because there */ + /* is really only one test. */ + for (it = 0; tf->registry_no_rng && it < tf->registry_no_rng->size; it++) { + run_test(&tf->registry_no_rng->data[it]); + } + + /* Initialize test RNG and library contexts */ + testrand_init(tf->args.custom_seed); + if (tf->fn_setup && tf->fn_setup() != 0) return EXIT_FAILURE; + + /* Check whether to process tests sequentially or concurrently */ + if (tf->args.num_processes <= 1) { + status = run_sequential(tf); + } else { +#if defined(SUPPORTS_CONCURRENCY) + status = run_concurrent(tf); +#else + fputs("Parallel execution not supported on your system. Running sequentially...\n", stderr); + status = run_sequential(tf); +#endif + } + + /* Print accumulated time */ + printf("Total execution time: %.3f seconds\n", (double)(gettime_i64() - start_time) / 1000000); + if (tf->fn_teardown && tf->fn_teardown() != 0) return EXIT_FAILURE; + + return status; +} diff --git a/src/unit_test.h b/src/unit_test.h new file mode 100644 index 000000000..f1374a0d7 --- /dev/null +++ b/src/unit_test.h @@ -0,0 +1,120 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_UNIT_TEST_H +#define SECP256K1_UNIT_TEST_H + +/* --------------------------------------------------------- */ +/* Configurable constants */ +/* --------------------------------------------------------- */ + +/* 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 +/* Maximum number of parallel jobs */ +#define MAX_SUBPROCESSES 16 + +/* --------------------------------------------------------- */ +/* Test Framework Registry Macros */ +/* --------------------------------------------------------- */ + +#define CASE(name) { #name, run_##name } + +#define MAKE_TEST_MODULE(name) { \ + #name, \ + tests_##name, \ + sizeof(tests_##name) / sizeof(tests_##name[0]) \ +} + +/* --------------------------------------------------------- */ +/* Test Framework API */ +/* --------------------------------------------------------- */ + +typedef void (*test_fn)(void); + +struct tf_test_entry { + const char* name; + test_fn func; +}; + +struct tf_test_module { + const char* name; + const struct tf_test_entry* data; + int size; +}; + +typedef int (*setup_ctx_fn)(void); +typedef int (*teardown_fn)(void); + +/* Reference to a test in the registry. Group index and test index */ +typedef struct { + int group; + int idx; +} tf_test_ref; + +/* --- Command-line args --- */ +struct tf_args { + /* 0 => sequential; 1..MAX_SUBPROCESSES => parallel workers */ + int num_processes; + /* Specific RNG seed */ + const char* custom_seed; +}; + +/* --------------------------------------------------------- */ +/* Public API */ +/* --------------------------------------------------------- */ + +struct tf_framework { + /* Command-line args */ + struct tf_args args; + /* Test modules registry */ + const struct tf_test_module* registry_modules; + /* Num of modules */ + int num_modules; + /* Registry for tests that require no RNG init */ + const struct tf_test_module* registry_no_rng; + /* Specific context setup and teardown functions */ + setup_ctx_fn fn_setup; + teardown_fn fn_teardown; +}; + +/* + * Initialize the test framework. + * + * Must be called before tf_run() and before any output is performed to + * stdout or stderr, because this function disables buffering on both + * streams to ensure reliable diagnostic output. + * + * Parses command-line arguments and configures the framework context. + * The caller must initialize the following members of 'tf' before calling: + * - tf->registry_modules + * - tf->num_modules + * + * Side effects: + * - stdout and stderr are set to unbuffered mode via setbuf(). + * This allows immediate flushing of diagnostic messages but may + * affect performance for other output operations. + * + * Returns: + * EXIT_SUCCESS (0) on success, + * EXIT_FAILURE (non-zero) on error. + */ +static int tf_init(struct tf_framework* tf, int argc, char** argv); + +/* + * Run tests based on the provided test framework context. + * + * This function uses the configuration stored in the tf_framework + * (targets, number of processes, iteration count, etc.) to determine + * which tests to execute and how to execute them. + * + * Returns: + * EXIT_SUCCESS (0) if all tests passed, + * EXIT_FAILURE (non-zero) otherwise. + */ +static int tf_run(struct tf_framework* tf); + +#endif /* SECP256K1_UNIT_TEST_H */ From 9ec3bfe22dc662875e6fca2419713eae105dabee Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 9 Sep 2025 11:45:17 -0400 Subject: [PATCH 10/30] test: adapt modules to the new test infrastructure This not only provides a structural improvement but also allows us to (1) specify individual tests to run and (2) execute each of them concurrently. --- src/modules/ecdh/tests_impl.h | 17 ++++++----- src/modules/ellswift/tests_impl.h | 7 +++++ src/modules/extrakeys/tests_impl.h | 21 +++++++------ src/modules/musig/tests_impl.h | 47 ++++++++++++++--------------- src/modules/recovery/tests_impl.h | 25 +++++++-------- src/modules/schnorrsig/tests_impl.h | 31 ++++++++++--------- src/tests.c | 38 +---------------------- src/unit_test.h | 13 ++++++++ 8 files changed, 93 insertions(+), 106 deletions(-) diff --git a/src/modules/ecdh/tests_impl.h b/src/modules/ecdh/tests_impl.h index 6888f18c6..c1a5e73c8 100644 --- a/src/modules/ecdh/tests_impl.h +++ b/src/modules/ecdh/tests_impl.h @@ -7,6 +7,8 @@ #ifndef SECP256K1_MODULE_ECDH_TESTS_H #define SECP256K1_MODULE_ECDH_TESTS_H +#include "../../unit_test.h" + static int ecdh_hash_function_test_xpassthru(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { (void)y; (void)data; @@ -182,12 +184,13 @@ static void test_ecdh_wycheproof(void) { } } -static void run_ecdh_tests(void) { - test_ecdh_api(); - test_ecdh_generator_basepoint(); - test_bad_scalar(); - test_result_basepoint(); - test_ecdh_wycheproof(); -} +/* --- Test registry --- */ +static const struct tf_test_entry tests_ecdh[] = { + CASE1(test_ecdh_api), + CASE1(test_ecdh_generator_basepoint), + CASE1(test_bad_scalar), + CASE1(test_result_basepoint), + CASE1(test_ecdh_wycheproof), +}; #endif /* SECP256K1_MODULE_ECDH_TESTS_H */ diff --git a/src/modules/ellswift/tests_impl.h b/src/modules/ellswift/tests_impl.h index b90fd0ab8..63c36e7ff 100644 --- a/src/modules/ellswift/tests_impl.h +++ b/src/modules/ellswift/tests_impl.h @@ -7,6 +7,7 @@ #define SECP256K1_MODULE_ELLSWIFT_TESTS_H #include "../../../include/secp256k1_ellswift.h" +#include "../../unit_test.h" struct ellswift_xswiftec_inv_test { int enc_bitmap; @@ -433,4 +434,10 @@ void run_ellswift_tests(void) { } } +/* --- Test registry --- */ +/* TODO: subdivide test in cases */ +static const struct tf_test_entry tests_ellswift[] = { + CASE(ellswift_tests), +}; + #endif diff --git a/src/modules/extrakeys/tests_impl.h b/src/modules/extrakeys/tests_impl.h index ab4ef4a74..abebd1106 100644 --- a/src/modules/extrakeys/tests_impl.h +++ b/src/modules/extrakeys/tests_impl.h @@ -8,6 +8,7 @@ #define SECP256K1_MODULE_EXTRAKEYS_TESTS_H #include "../../../include/secp256k1_extrakeys.h" +#include "../../unit_test.h" static void test_xonly_pubkey(void) { secp256k1_pubkey pk; @@ -467,17 +468,17 @@ static void test_keypair_add(void) { } } -static void run_extrakeys_tests(void) { +/* --- Test registry --- */ +static const struct tf_test_entry tests_extrakeys[] = { /* xonly key test cases */ - test_xonly_pubkey(); - test_xonly_pubkey_tweak(); - test_xonly_pubkey_tweak_check(); - test_xonly_pubkey_tweak_recursive(); - test_xonly_pubkey_comparison(); - + CASE1(test_xonly_pubkey), + CASE1(test_xonly_pubkey_tweak), + CASE1(test_xonly_pubkey_tweak_check), + CASE1(test_xonly_pubkey_tweak_recursive), + CASE1(test_xonly_pubkey_comparison), /* keypair tests */ - test_keypair(); - test_keypair_add(); -} + CASE1(test_keypair), + CASE1(test_keypair_add), +}; #endif diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index b57b26264..b4ba18549 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -20,6 +20,7 @@ #include "../../group.h" #include "../../hash.h" #include "../../util.h" +#include "../../unit_test.h" #include "vectors.h" @@ -36,7 +37,7 @@ static int create_keypair_and_pk(secp256k1_keypair *keypair, secp256k1_pubkey *p /* Just a simple (non-tweaked) 2-of-2 MuSig aggregate, sign, verify * test. */ -static void musig_simple_test(void) { +static void musig_simple_test_internal(void) { unsigned char sk[2][32]; secp256k1_keypair keypair[2]; secp256k1_musig_pubnonce pubnonce[2]; @@ -629,7 +630,7 @@ static void musig_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const /* Create aggregate public key P[0], tweak multiple times (using xonly and * plain tweaking) and test signing. */ -static void musig_tweak_test(void) { +static void musig_tweak_test_internal(void) { unsigned char sk[2][32]; secp256k1_pubkey pk[2]; const secp256k1_pubkey *pk_ptr[2]; @@ -1114,28 +1115,24 @@ static void musig_test_static_nonce_gen_counter(void) { CHECK(secp256k1_memcmp_var(pubnonce66, expected_pubnonce, sizeof(pubnonce66)) == 0); } -static void run_musig_tests(void) { - int i; - - for (i = 0; i < COUNT; i++) { - musig_simple_test(); - } - musig_api_tests(); - musig_nonce_test(); - for (i = 0; i < COUNT; i++) { - /* Run multiple times to ensure that pk and nonce have different y - * parities */ - musig_tweak_test(); - } - sha256_tag_test(); - musig_test_vectors_keyagg(); - musig_test_vectors_noncegen(); - musig_test_vectors_nonceagg(); - musig_test_vectors_signverify(); - musig_test_vectors_tweak(); - musig_test_vectors_sigagg(); - - musig_test_static_nonce_gen_counter(); -} +/* --- Test registry --- */ +REPEAT_TEST(musig_simple_test) +/* Run multiple times to ensure that pk and nonce have different y parities */ +REPEAT_TEST(musig_tweak_test) + +static const struct tf_test_entry tests_musig[] = { + CASE1(musig_simple_test), + CASE1(musig_api_tests), + CASE1(musig_nonce_test), + CASE1(musig_tweak_test), + CASE1(sha256_tag_test), + CASE1(musig_test_vectors_keyagg), + CASE1(musig_test_vectors_noncegen), + CASE1(musig_test_vectors_nonceagg), + CASE1(musig_test_vectors_signverify), + CASE1(musig_test_vectors_tweak), + CASE1(musig_test_vectors_sigagg), + CASE1(musig_test_static_nonce_gen_counter), +}; #endif diff --git a/src/modules/recovery/tests_impl.h b/src/modules/recovery/tests_impl.h index 7a28a3ce6..09554a242 100644 --- a/src/modules/recovery/tests_impl.h +++ b/src/modules/recovery/tests_impl.h @@ -7,6 +7,8 @@ #ifndef SECP256K1_MODULE_RECOVERY_TESTS_H #define SECP256K1_MODULE_RECOVERY_TESTS_H +#include "../../unit_test.h" + static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { (void) msg32; (void) key32; @@ -28,7 +30,7 @@ static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned c return testrand_bits(1); } -static void test_ecdsa_recovery_api(void) { +static void test_ecdsa_recovery_api_internal(void) { /* Setup contexts that just count errors */ secp256k1_pubkey pubkey; secp256k1_pubkey recpubkey; @@ -92,7 +94,7 @@ static void test_ecdsa_recovery_api(void) { CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, sig, recid) == 0); } -static void test_ecdsa_recovery_end_to_end(void) { +static void test_ecdsa_recovery_end_to_end_internal(void) { unsigned char extra[32] = {0x00}; unsigned char privkey[32]; unsigned char message[32]; @@ -324,15 +326,14 @@ static void test_ecdsa_recovery_edge_cases(void) { } } -static void run_recovery_tests(void) { - int i; - for (i = 0; i < COUNT; i++) { - test_ecdsa_recovery_api(); - } - for (i = 0; i < 64*COUNT; i++) { - test_ecdsa_recovery_end_to_end(); - } - test_ecdsa_recovery_edge_cases(); -} +/* --- Test registry --- */ +REPEAT_TEST(test_ecdsa_recovery_api) +REPEAT_TEST_MULT(test_ecdsa_recovery_end_to_end, 64) + +static const struct tf_test_entry tests_recovery[] = { + CASE1(test_ecdsa_recovery_api), + CASE1(test_ecdsa_recovery_end_to_end), + CASE1(test_ecdsa_recovery_edge_cases) +}; #endif /* SECP256K1_MODULE_RECOVERY_TESTS_H */ diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 5abbeefe0..9a1b15f0b 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -8,6 +8,7 @@ #define SECP256K1_MODULE_SCHNORRSIG_TESTS_H #include "../../../include/secp256k1_schnorrsig.h" +#include "../../unit_test.h" /* Checks that a bit flip in the n_flip-th argument (that has n_bytes many * bytes) changes the hash function @@ -802,7 +803,7 @@ static int nonce_function_overflowing(unsigned char *nonce32, const unsigned cha return 1; } -static void test_schnorrsig_sign(void) { +static void test_schnorrsig_sign_internal(void) { unsigned char sk[32]; secp256k1_xonly_pubkey pk; secp256k1_keypair keypair; @@ -852,7 +853,7 @@ static void test_schnorrsig_sign(void) { /* Creates N_SIGS valid signatures and verifies them with verify and * verify_batch (TODO). Then flips some bits and checks that verification now * fails. */ -static void test_schnorrsig_sign_verify(void) { +static void test_schnorrsig_sign_verify_internal(void) { unsigned char sk[32]; unsigned char msg[N_SIGS][32]; unsigned char sig[N_SIGS][64]; @@ -965,18 +966,18 @@ static void test_schnorrsig_taproot(void) { CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); } -static void run_schnorrsig_tests(void) { - int i; - run_nonce_function_bip340_tests(); - - test_schnorrsig_api(); - test_schnorrsig_sha256_tagged(); - test_schnorrsig_bip_vectors(); - for (i = 0; i < COUNT; i++) { - test_schnorrsig_sign(); - test_schnorrsig_sign_verify(); - } - test_schnorrsig_taproot(); -} +/* --- Test registry --- */ +REPEAT_TEST(test_schnorrsig_sign) +REPEAT_TEST(test_schnorrsig_sign_verify) + +static const struct tf_test_entry tests_schnorrsig[] = { + CASE(nonce_function_bip340_tests), + CASE1(test_schnorrsig_api), + CASE1(test_schnorrsig_sha256_tagged), + CASE1(test_schnorrsig_bip_vectors), + CASE1(test_schnorrsig_sign), + CASE1(test_schnorrsig_sign_verify), + CASE1(test_schnorrsig_taproot), +}; #endif diff --git a/src/tests.c b/src/tests.c index efcdbdc8b..5f4f6b0f2 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7755,12 +7755,6 @@ static const struct tf_test_entry tests_ec[] = { CASE(eckey_negate_test), }; -#ifdef ENABLE_MODULE_ECDH -static const struct tf_test_entry tests_ecdh[] = { - CASE(ecdh_tests), -}; -#endif - static const struct tf_test_entry tests_ecdsa[] = { CASE(ec_illegal_argument_tests), CASE(pubkey_comparison), @@ -7773,37 +7767,6 @@ static const struct tf_test_entry tests_ecdsa[] = { CASE(ecdsa_wycheproof), }; -#ifdef ENABLE_MODULE_RECOVERY -static const struct tf_test_entry tests_recovery[] = { - /* ECDSA pubkey recovery tests */ - CASE(recovery_tests), -}; -#endif - -#ifdef ENABLE_MODULE_EXTRAKEYS -static const struct tf_test_entry tests_extrakeys[] = { - CASE(extrakeys_tests), -}; -#endif - -#ifdef ENABLE_MODULE_SCHNORRSIG -static const struct tf_test_entry tests_schnorrsig[] = { - CASE(schnorrsig_tests), -}; -#endif - -#ifdef ENABLE_MODULE_MUSIG -static const struct tf_test_entry tests_musig[] = { - CASE(musig_tests), -}; -#endif - -#ifdef ENABLE_MODULE_ELLSWIFT -static const struct tf_test_entry tests_ellswift[] = { - CASE(ellswift_tests), -}; -#endif - static const struct tf_test_entry tests_utils[] = { CASE(hsort_tests), CASE(secp256k1_memczero_test), @@ -7827,6 +7790,7 @@ static const struct tf_test_module registry_modules[] = { #endif MAKE_TEST_MODULE(ecdsa), #ifdef ENABLE_MODULE_RECOVERY + /* ECDSA pubkey recovery tests */ MAKE_TEST_MODULE(recovery), #endif #ifdef ENABLE_MODULE_EXTRAKEYS diff --git a/src/unit_test.h b/src/unit_test.h index f1374a0d7..7052fdc79 100644 --- a/src/unit_test.h +++ b/src/unit_test.h @@ -22,6 +22,7 @@ /* --------------------------------------------------------- */ #define CASE(name) { #name, run_##name } +#define CASE1(name) { #name, name } #define MAKE_TEST_MODULE(name) { \ #name, \ @@ -29,6 +30,18 @@ sizeof(tests_##name) / sizeof(tests_##name[0]) \ } +/* Macro to wrap a test internal function with a COUNT loop (iterations number) */ +#define REPEAT_TEST(fn) REPEAT_TEST_MULT(fn, 1) +#define REPEAT_TEST_MULT(fn, multiplier) \ + static void fn(void) { \ + int i; \ + int repeat = COUNT * (multiplier); \ + for (i = 0; i < repeat; i++) \ + fn##_internal(); \ + } + + + /* --------------------------------------------------------- */ /* Test Framework API */ /* --------------------------------------------------------- */ From 0302c1a3d73137fcf75a7ca0f0f3cbcec0368ec8 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 3 Sep 2025 15:27:23 -0400 Subject: [PATCH 11/30] test: add --help for command-line options Add a help message for the test suite, documenting available options, defaults, and backward-compatible positional arguments. --- src/unit_test.c | 28 ++++++++++++++++++++++++++++ src/unit_test.h | 2 ++ 2 files changed, 30 insertions(+) diff --git a/src/unit_test.c b/src/unit_test.c index 914594bc1..04878eecd 100644 --- a/src/unit_test.c +++ b/src/unit_test.c @@ -74,6 +74,23 @@ static int parse_arg(const char* key, const char* value, struct tf_framework* tf return -1; } +static void help(void) { + printf("Usage: ./tests [options]\n\n"); + printf("Run the test suite for the project with optional configuration.\n\n"); + printf("Options:\n"); + printf(" --help, -h Show this help message\n"); + printf(" --jobs=, -j= Number of parallel worker processes (default: 0 = sequential)\n"); + printf(" --iterations=, -i= Number of iterations for each test (default: 16)\n"); + printf(" --seed= Set a specific RNG seed (default: random)\n"); + printf("\n"); + printf("Notes:\n"); + printf(" - All arguments must be provided in the form '--key=value', '-key=value' or '-k=value'.\n"); + printf(" - Single or double dashes are allowed for multi character options.\n"); + printf(" - Unknown arguments are reported but ignored.\n"); + printf(" - Sequential execution occurs if -jobs=0 or unspecified.\n"); + printf(" - Iterations and seed can also be passed as positional arguments before any other argument for backward compatibility.\n"); +} + static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf) { char* ptr_val; long val = strtol(value, &ptr_val, 10); /* base 10 */ @@ -157,6 +174,11 @@ static int read_args(int argc, char** argv, int start, struct tf_framework* tf) eq = strchr(raw_arg, '='); if (!eq || eq == raw_arg + 1) { + /* Allowed options without value */ + if (strcmp(key, "h") == 0 || strcmp(key, "help") == 0) { + tf->args.help = 1; + return 0; + } fprintf(stderr, "Invalid arg '%s': must be -k=value or --key=value\n", raw_arg); return -1; } @@ -264,6 +286,7 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) /* Initialize command-line options */ tf->args.num_processes = 0; tf->args.custom_seed = NULL; + tf->args.help = 0; /* Disable buffering for stdout to improve reliability of getting * diagnostic information. Happens right at the start of main because @@ -295,6 +318,11 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) if (read_args(argc, argv, named_arg_start, tf) != 0) { return EXIT_FAILURE; } + + if (tf->args.help) { + help(); + exit(EXIT_SUCCESS); + } } return EXIT_SUCCESS; diff --git a/src/unit_test.h b/src/unit_test.h index 7052fdc79..1eefb016e 100644 --- a/src/unit_test.h +++ b/src/unit_test.h @@ -74,6 +74,8 @@ struct tf_args { int num_processes; /* Specific RNG seed */ const char* custom_seed; + /* Whether to print the help msg */ + int help; }; /* --------------------------------------------------------- */ From 953f7b008865ed503f36b040c4af4db96c4d54d9 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 3 Sep 2025 17:18:23 -0400 Subject: [PATCH 12/30] test: support running specific tests/modules targets Add support for specifying single tests or modules to run via the "--target" or "-t" command-line option. Multiple targets can be provided; only the specified tests or all tests in the specified module/s will run instead of the full suite. Examples: -t= runs an specific test. -t= runs all tests within the specified module. Both options can be provided multiple times. --- src/unit_test.c | 96 +++++++++++++++++++++++++++++++++++++++---------- src/unit_test.h | 13 ++++--- 2 files changed, 85 insertions(+), 24 deletions(-) diff --git a/src/unit_test.c b/src/unit_test.c index 04878eecd..513bc2cc6 100644 --- a/src/unit_test.c +++ b/src/unit_test.c @@ -25,6 +25,7 @@ int COUNT = 16; static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf); static int parse_iterations(const char* key, const char* value, struct tf_framework* tf); static int parse_seed(const char* key, const char* value, struct tf_framework* tf); +static int parse_target(const char* key, const char* value, struct tf_framework* tf); /* Mapping table: key -> handler */ typedef int (*ArgHandler)(const char* key, const char* value, struct tf_framework* tf); @@ -41,6 +42,7 @@ struct ArgMap { * converted to the appropriate type, and stored in 'tf->args' struct. */ static struct ArgMap arg_map[] = { + { "t", parse_target }, { "target", parse_target }, { "j", parse_jobs_count }, { "jobs", parse_jobs_count }, { "i", parse_iterations }, { "iterations", parse_iterations }, { "seed", parse_seed }, @@ -82,6 +84,8 @@ static void help(void) { printf(" --jobs=, -j= Number of parallel worker processes (default: 0 = sequential)\n"); printf(" --iterations=, -i= Number of iterations for each test (default: 16)\n"); printf(" --seed= Set a specific RNG seed (default: random)\n"); + printf(" --target=, -t= Run a specific test (can be provided multiple times)\n"); + printf(" --target=, -t= Run all tests within a specific module (can be provided multiple times)\n"); printf("\n"); printf("Notes:\n"); printf(" - All arguments must be provided in the form '--key=value', '-key=value' or '-k=value'.\n"); @@ -152,6 +156,34 @@ static const char* normalize_key(const char* arg, const char** err_msg) { return key; } +static int parse_target(const char* key, const char* value, struct tf_framework* tf) { + int group, idx; + const struct tf_test_entry* entry; + UNUSED(key); + /* Find test index in the registry */ + for (group = 0; group < tf->num_modules; group++) { + const struct tf_test_module* module = &tf->registry_modules[group]; + int add_all = strcmp(value, module->name) == 0; /* select all from module */ + for (idx = 0; idx < module->size; idx++) { + entry = &module->data[idx]; + if (add_all || strcmp(value, entry->name) == 0) { + if (tf->args.targets.size >= MAX_ARGS) { + fprintf(stderr, "Too many -target args (max: %d)\n", MAX_ARGS); + return -1; + } + tf->args.targets.slots[tf->args.targets.size++] = entry; + /* Matched a single test, we're done */ + if (!add_all) return 0; + } + } + /* If add_all was true, we added all tests in the module, so return */ + if (add_all) return 0; + } + fprintf(stderr, "Error: target '%s' not found (missing or module disabled).\n" + "Run program with -print_tests option to display available tests and modules.\n", value); + return -1; +} + /* Read args: all must be in the form -key=value, --key=value or -key=value */ static int read_args(int argc, char** argv, int start, struct tf_framework* tf) { int i; @@ -203,18 +235,16 @@ static void run_test(const struct tf_test_entry* t) { /* Process tests in sequential order */ static int run_sequential(struct tf_framework* tf) { - tf_test_ref ref; - const struct tf_test_module* mdl; - for (ref.group = 0; ref.group < tf->num_modules; ref.group++) { - mdl = &tf->registry_modules[ref.group]; - for (ref.idx = 0; ref.idx < mdl->size; ref.idx++) { - run_test(&mdl->data[ref.idx]); - } + int it; + for (it = 0; it < tf->args.targets.size; it++) { + run_test(tf->args.targets.slots[it]); } return EXIT_SUCCESS; } #if defined(SUPPORTS_CONCURRENCY) +static const int MAX_TARGETS = 255; + /* Process tests in parallel */ static int run_concurrent(struct tf_framework* tf) { /* Sub-processes info */ @@ -222,7 +252,15 @@ static int run_concurrent(struct tf_framework* tf) { int pipefd[2]; int status = EXIT_SUCCESS; int it; /* loop iterator */ - tf_test_ref ref; /* test index */ + unsigned char idx; /* test index */ + + if (tf->args.targets.size > MAX_TARGETS) { + fprintf(stderr, "Internal Error: the number of targets (%d) exceeds the maximum supported (%d). " + "If you need more, extend 'run_concurrent()' to handle additional targets.\n", + tf->args.targets.size, MAX_TARGETS); + exit(EXIT_FAILURE); + } + if (pipe(pipefd) != 0) { perror("Error during pipe setup"); @@ -239,8 +277,8 @@ static int run_concurrent(struct tf_framework* tf) { if (pid == 0) { /* Child worker: read jobs from the shared pipe */ close(pipefd[1]); /* children never write */ - while (read(pipefd[0], &ref, sizeof(ref)) == sizeof(ref)) { - run_test(&tf->registry_modules[ref.group].data[ref.idx]); + while (read(pipefd[0], &idx, sizeof(idx)) == sizeof(idx)) { + run_test(tf->args.targets.slots[(int)idx]); } _exit(EXIT_SUCCESS); /* finish child process */ } else { @@ -251,14 +289,12 @@ static int run_concurrent(struct tf_framework* tf) { /* Parent: write all tasks into the pipe */ close(pipefd[0]); /* close read end */ - for (ref.group = 0; ref.group < tf->num_modules; ref.group++) { - const struct tf_test_module* mdl = &tf->registry_modules[ref.group]; - for (ref.idx = 0; ref.idx < mdl->size; ref.idx++) { - if (write(pipefd[1], &ref, sizeof(ref)) == -1) { - perror("Error during workload distribution"); - close(pipefd[1]); - return EXIT_FAILURE; - } + for (it = 0; it < tf->args.targets.size; it++) { + idx = (unsigned char)it; + if (write(pipefd[1], &idx, sizeof(idx)) == -1) { + perror("Error during workload distribution"); + close(pipefd[1]); + return EXIT_FAILURE; } } /* Close write end to signal EOF */ @@ -287,6 +323,7 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) tf->args.num_processes = 0; tf->args.custom_seed = NULL; tf->args.help = 0; + tf->args.targets.size = 0; /* Disable buffering for stdout to improve reliability of getting * diagnostic information. Happens right at the start of main because @@ -331,11 +368,30 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) static int tf_run(struct tf_framework* tf) { /* Process exit status */ int status; + /* Whether to run all tests */ + int run_all; /* Loop iterator */ int it; /* Initial test time */ int64_t start_time = gettime_i64(); + /* Populate targets with all tests if none were explicitly specified */ + run_all = tf->args.targets.size == 0; + if (run_all) { + int group, idx; + for (group = 0; group < tf->num_modules; group++) { + const struct tf_test_module* module = &tf->registry_modules[group]; + for (idx = 0; idx < module->size; idx++) { + if (tf->args.targets.size >= MAX_ARGS) { + fprintf(stderr, "Internal Error: Number of tests (%d) exceeds MAX_ARGS (%d). " + "Increase MAX_ARGS to accommodate all tests.\n", tf->args.targets.size, MAX_ARGS); + return EXIT_FAILURE; + } + tf->args.targets.slots[tf->args.targets.size++] = &module->data[idx]; + } + } + } + /* Log configuration */ print_args(&tf->args); @@ -343,7 +399,9 @@ static int tf_run(struct tf_framework* tf) { /* Note: currently, these tests are executed sequentially because there */ /* is really only one test. */ for (it = 0; tf->registry_no_rng && it < tf->registry_no_rng->size; it++) { - run_test(&tf->registry_no_rng->data[it]); + if (run_all) { /* future: support filtering */ + run_test(&tf->registry_no_rng->data[it]); + } } /* Initialize test RNG and library contexts */ diff --git a/src/unit_test.h b/src/unit_test.h index 1eefb016e..e76b8c011 100644 --- a/src/unit_test.h +++ b/src/unit_test.h @@ -62,11 +62,12 @@ struct tf_test_module { typedef int (*setup_ctx_fn)(void); typedef int (*teardown_fn)(void); -/* Reference to a test in the registry. Group index and test index */ -typedef struct { - int group; - int idx; -} tf_test_ref; +struct tf_targets { + /* Target tests indexes */ + const struct tf_test_entry* slots[MAX_ARGS]; + /* Next available slot */ + int size; +}; /* --- Command-line args --- */ struct tf_args { @@ -76,6 +77,8 @@ struct tf_args { const char* custom_seed; /* Whether to print the help msg */ int help; + /* Target tests indexes */ + struct tf_targets targets; }; /* --------------------------------------------------------- */ From 95b9953ea44ad6273dd8ddd2040844a54936ed71 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 8 Sep 2025 14:17:12 -0400 Subject: [PATCH 13/30] test: Add option to display all available tests Useful option to avoid opening the large tests.c file just to find the test case you want to run. --- src/unit_test.c | 31 ++++++++++++++++++++++++++++++- src/unit_test.h | 2 ++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/unit_test.c b/src/unit_test.c index 513bc2cc6..4eace5266 100644 --- a/src/unit_test.c +++ b/src/unit_test.c @@ -81,6 +81,7 @@ static void help(void) { printf("Run the test suite for the project with optional configuration.\n\n"); printf("Options:\n"); printf(" --help, -h Show this help message\n"); + printf(" --list_tests, -l Display list of all available tests and modules\n"); printf(" --jobs=, -j= Number of parallel worker processes (default: 0 = sequential)\n"); printf(" --iterations=, -i= Number of iterations for each test (default: 16)\n"); printf(" --seed= Set a specific RNG seed (default: random)\n"); @@ -95,6 +96,24 @@ static void help(void) { printf(" - Iterations and seed can also be passed as positional arguments before any other argument for backward compatibility.\n"); } +/* Print all tests in registry */ +static void print_test_list(struct tf_framework* tf) { + int m, t, total = 0; + printf("\nAvailable tests (%d modules):\n", tf->num_modules); + printf("========================================\n"); + for (m = 0; m < tf->num_modules; m++) { + const struct tf_test_module* mod = &tf->registry_modules[m]; + printf("Module: %s (%d tests)\n", mod->name, mod->size); + for (t = 0; t < mod->size; t++) { + printf("\t[%3d] %s\n", total + 1, mod->data[t].name); + total++; + } + printf("----------------------------------------\n"); + } + printf("\nRun specific module: ./tests -t=\n"); + printf("Run specific test: ./tests -t=\n\n"); +} + static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf) { char* ptr_val; long val = strtol(value, &ptr_val, 10); /* base 10 */ @@ -180,7 +199,7 @@ static int parse_target(const char* key, const char* value, struct tf_framework* if (add_all) return 0; } fprintf(stderr, "Error: target '%s' not found (missing or module disabled).\n" - "Run program with -print_tests option to display available tests and modules.\n", value); + "Run program with -list_tests option to display available tests and modules.\n", value); return -1; } @@ -211,6 +230,10 @@ static int read_args(int argc, char** argv, int start, struct tf_framework* tf) tf->args.help = 1; return 0; } + if (strcmp(key, "l") == 0 || strcmp(key, "list_tests") == 0) { + tf->args.list_tests = 1; + return 0; + } fprintf(stderr, "Invalid arg '%s': must be -k=value or --key=value\n", raw_arg); return -1; } @@ -324,6 +347,7 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) tf->args.custom_seed = NULL; tf->args.help = 0; tf->args.targets.size = 0; + tf->args.list_tests = 0; /* Disable buffering for stdout to improve reliability of getting * diagnostic information. Happens right at the start of main because @@ -360,6 +384,11 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) help(); exit(EXIT_SUCCESS); } + + if (tf->args.list_tests) { + print_test_list(tf); + exit(EXIT_SUCCESS); + } } return EXIT_SUCCESS; diff --git a/src/unit_test.h b/src/unit_test.h index e76b8c011..b41e9f751 100644 --- a/src/unit_test.h +++ b/src/unit_test.h @@ -77,6 +77,8 @@ struct tf_args { const char* custom_seed; /* Whether to print the help msg */ int help; + /* Whether to print the tests list msg */ + int list_tests; /* Target tests indexes */ struct tf_targets targets; }; From 2f4546ce5610f4f7841fc2dc2eef68dbfcdcc761 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 9 Sep 2025 11:02:05 -0400 Subject: [PATCH 14/30] test: add --log option to display tests execution When enabled (--log=1), shows test start, completion, and execution time. --- src/unit_test.c | 32 +++++++++++++++++++++++++++----- src/unit_test.h | 5 +++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/unit_test.c b/src/unit_test.c index 4eace5266..a1858a117 100644 --- a/src/unit_test.c +++ b/src/unit_test.c @@ -26,6 +26,7 @@ static int parse_jobs_count(const char* key, const char* value, struct tf_framew static int parse_iterations(const char* key, const char* value, struct tf_framework* tf); static int parse_seed(const char* key, const char* value, struct tf_framework* tf); static int parse_target(const char* key, const char* value, struct tf_framework* tf); +static int parse_logging(const char* key, const char* value, struct tf_framework* tf); /* Mapping table: key -> handler */ typedef int (*ArgHandler)(const char* key, const char* value, struct tf_framework* tf); @@ -46,6 +47,7 @@ static struct ArgMap arg_map[] = { { "j", parse_jobs_count }, { "jobs", parse_jobs_count }, { "i", parse_iterations }, { "iterations", parse_iterations }, { "seed", parse_seed }, + { "log", parse_logging }, { NULL, NULL } /* sentinel */ }; @@ -87,6 +89,7 @@ static void help(void) { printf(" --seed= Set a specific RNG seed (default: random)\n"); printf(" --target=, -t= Run a specific test (can be provided multiple times)\n"); printf(" --target=, -t= Run all tests within a specific module (can be provided multiple times)\n"); + printf(" --log=<0|1> Enable or disable test execution logging (default: 0 = disabled)\n"); printf("\n"); printf("Notes:\n"); printf(" - All arguments must be provided in the form '--key=value', '-key=value' or '-k=value'.\n"); @@ -146,6 +149,12 @@ static int parse_seed(const char* key, const char* value, struct tf_framework* t return 0; } +static int parse_logging(const char* key, const char* value, struct tf_framework* tf) { + UNUSED(key); + tf->args.logging = value && strcmp(value, "1") == 0; + return 0; +} + /* Strip up to two leading dashes */ static const char* normalize_key(const char* arg, const char** err_msg) { const char* key; @@ -250,17 +259,20 @@ static int read_args(int argc, char** argv, int start, struct tf_framework* tf) return 0; } -static void run_test(const struct tf_test_entry* t) { +static void run_test_log(const struct tf_test_entry* t) { + int64_t start_time = gettime_i64(); printf("Running %s..\n", t->name); t->func(); - printf("%s PASSED\n", t->name); + printf("Test %s PASSED (%.3f sec)\n", t->name, (double)(gettime_i64() - start_time) / 1000000); } +static void run_test(const struct tf_test_entry* t) { t->func(); } + /* Process tests in sequential order */ static int run_sequential(struct tf_framework* tf) { int it; for (it = 0; it < tf->args.targets.size; it++) { - run_test(tf->args.targets.slots[it]); + tf->fn_run_test(tf->args.targets.slots[it]); } return EXIT_SUCCESS; } @@ -301,7 +313,7 @@ static int run_concurrent(struct tf_framework* tf) { /* Child worker: read jobs from the shared pipe */ close(pipefd[1]); /* children never write */ while (read(pipefd[0], &idx, sizeof(idx)) == sizeof(idx)) { - run_test(tf->args.targets.slots[(int)idx]); + tf->fn_run_test(tf->args.targets.slots[(int)idx]); } _exit(EXIT_SUCCESS); /* finish child process */ } else { @@ -348,6 +360,7 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) tf->args.help = 0; tf->args.targets.size = 0; tf->args.list_tests = 0; + tf->args.logging = 0; /* Disable buffering for stdout to improve reliability of getting * diagnostic information. Happens right at the start of main because @@ -391,6 +404,7 @@ static int tf_init(struct tf_framework* tf, int argc, char** argv) } } + tf->fn_run_test = tf->args.logging ? run_test_log : run_test; return EXIT_SUCCESS; } @@ -403,6 +417,12 @@ static int tf_run(struct tf_framework* tf) { int it; /* Initial test time */ int64_t start_time = gettime_i64(); + /* Verify 'tf_init' has been called */ + if (!tf->fn_run_test) { + fprintf(stderr, "Error: No test runner set. You must call 'tf_init' first to initialize the framework " + "or manually assign 'fn_run_test' before calling 'tf_run'.\n"); + return EXIT_FAILURE; + } /* Populate targets with all tests if none were explicitly specified */ run_all = tf->args.targets.size == 0; @@ -421,6 +441,8 @@ static int tf_run(struct tf_framework* tf) { } } + if (!tf->args.logging) printf("Tests running silently. Use '-log=1' to enable detailed logging\n"); + /* Log configuration */ print_args(&tf->args); @@ -429,7 +451,7 @@ static int tf_run(struct tf_framework* tf) { /* is really only one test. */ for (it = 0; tf->registry_no_rng && it < tf->registry_no_rng->size; it++) { if (run_all) { /* future: support filtering */ - run_test(&tf->registry_no_rng->data[it]); + tf->fn_run_test(&tf->registry_no_rng->data[it]); } } diff --git a/src/unit_test.h b/src/unit_test.h index b41e9f751..bf301e539 100644 --- a/src/unit_test.h +++ b/src/unit_test.h @@ -61,6 +61,7 @@ struct tf_test_module { typedef int (*setup_ctx_fn)(void); typedef int (*teardown_fn)(void); +typedef void (*run_test_fn)(const struct tf_test_entry*); struct tf_targets { /* Target tests indexes */ @@ -81,6 +82,8 @@ struct tf_args { int list_tests; /* Target tests indexes */ struct tf_targets targets; + /* Enable test execution logging */ + int logging; }; /* --------------------------------------------------------- */ @@ -99,6 +102,8 @@ struct tf_framework { /* Specific context setup and teardown functions */ setup_ctx_fn fn_setup; teardown_fn fn_teardown; + /* Test runner function (can be customized) */ + run_test_fn fn_run_test; }; /* From 122014edb38762acbf8c98edae0ce54d630b072b Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:08:09 +0100 Subject: [PATCH 15/30] ci: Add `scope` parameter to `cache-{to,from}` options This change fixes an issue where only the latest image cache was available. --- .github/actions/run-in-docker-action/action.yml | 8 ++++++-- .github/workflows/ci.yml | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/actions/run-in-docker-action/action.yml b/.github/actions/run-in-docker-action/action.yml index 74933686a..f9bd4746e 100644 --- a/.github/actions/run-in-docker-action/action.yml +++ b/.github/actions/run-in-docker-action/action.yml @@ -7,6 +7,10 @@ inputs: tag: description: 'A tag of an image' required: true + scope: + description: 'A cached image scope' + required: false + default: ${{ runner.arch }} command: description: 'A command to run in a container' required: false @@ -24,7 +28,7 @@ runs: file: ${{ inputs.dockerfile }} tags: ${{ inputs.tag }} load: true - cache-from: type=gha + cache-from: type=gha,scope=${{ inputs.scope }} - uses: docker/build-push-action@v5 id: retry_builder @@ -34,7 +38,7 @@ runs: file: ${{ inputs.dockerfile }} tags: ${{ inputs.tag }} load: true - cache-from: type=gha + cache-from: type=gha,scope=${{ inputs.scope }} - # Workaround for https://github.com/google/sanitizers/issues/1614 . # The underlying issue has been fixed in clang 18.1.3. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9783251d6..83b382407 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,8 +71,8 @@ jobs: with: file: ./ci/linux-debian.Dockerfile tags: ${{ matrix.arch }}-debian-image - cache-from: type=gha - cache-to: type=gha,mode=min + cache-from: type=gha,scope=${{ runner.arch }} + cache-to: type=gha,scope=${{ runner.arch }},mode=min x86_64-debian: name: "x86_64: Linux (Debian stable)" From b2a95a420ff67ae0e162aaea022e1142e3701df8 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:54:24 +0100 Subject: [PATCH 16/30] ci: Drop `tags` input for `docker/build-push-action` The `tags` input is unused for caching. --- .../actions/run-in-docker-action/action.yml | 8 ++--- .github/workflows/ci.yml | 31 ++++--------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/.github/actions/run-in-docker-action/action.yml b/.github/actions/run-in-docker-action/action.yml index f9bd4746e..2af23643f 100644 --- a/.github/actions/run-in-docker-action/action.yml +++ b/.github/actions/run-in-docker-action/action.yml @@ -4,9 +4,6 @@ inputs: dockerfile: description: 'A Dockerfile that defines an image' required: true - tag: - description: 'A tag of an image' - required: true scope: description: 'A cached image scope' required: false @@ -26,7 +23,6 @@ runs: with: context: . file: ${{ inputs.dockerfile }} - tags: ${{ inputs.tag }} load: true cache-from: type=gha,scope=${{ inputs.scope }} @@ -36,7 +32,6 @@ runs: with: context: . file: ${{ inputs.dockerfile }} - tags: ${{ inputs.tag }} load: true cache-from: type=gha,scope=${{ inputs.scope }} @@ -51,7 +46,8 @@ runs: $(echo '${{ toJSON(env) }}' | jq -r 'keys[] | "--env \(.) "') \ --volume ${{ github.workspace }}:${{ github.workspace }} \ --workdir ${{ github.workspace }} \ - ${{ inputs.tag }} bash -c " + $(docker images -q | head -n1) \ + bash -c " git config --global --add safe.directory ${{ github.workspace }} ${{ inputs.command }} " diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83b382407..49f16b875 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,6 @@ jobs: uses: docker/build-push-action@v5 with: file: ./ci/linux-debian.Dockerfile - tags: ${{ matrix.arch }}-debian-image cache-from: type=gha,scope=${{ runner.arch }} cache-to: type=gha,scope=${{ runner.arch }},mode=min @@ -116,7 +115,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -152,7 +150,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -184,7 +181,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -225,7 +221,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -265,7 +260,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: arm64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -297,7 +291,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -313,28 +306,22 @@ jobs: fail-fast: false matrix: include: - - docker_arch: x64 - runner: ubuntu-latest + - runner: ubuntu-latest binary_arch: x64 env_vars: { CC: 'clang', ASM: 'auto' } - - docker_arch: x64 - runner: ubuntu-latest + - runner: ubuntu-latest binary_arch: i686 env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'auto' } - - docker_arch: arm64 - runner: ubuntu-24.04-arm + - runner: ubuntu-24.04-arm binary_arch: arm64 env_vars: { CC: 'clang', ASM: 'auto' } - - docker_arch: x64 - runner: ubuntu-latest + - runner: ubuntu-latest binary_arch: x64 env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 } - - docker_arch: x64 - runner: ubuntu-latest + - runner: ubuntu-latest binary_arch: i686 env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 } - - docker_arch: arm64 - runner: ubuntu-24.04-arm + - runner: ubuntu-24.04-arm binary_arch: arm64 env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 } @@ -360,7 +347,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: ${{ matrix.docker_arch }}-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -404,7 +390,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -455,7 +440,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -498,7 +482,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -714,7 +697,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image - name: Print logs uses: ./.github/actions/print-logs @@ -733,7 +715,6 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile - tag: x64-debian-image command: | g++ -Werror include/*.h clang -Werror -x c++-header include/*.h From 70ae177ca06b4d24c73c411d8524507d68aff45a Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:54:37 +0100 Subject: [PATCH 17/30] ci: Bump `docker/build-push-action` version See https://github.com/docker/build-push-action/releases. --- .github/actions/run-in-docker-action/action.yml | 4 ++-- .github/workflows/ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/run-in-docker-action/action.yml b/.github/actions/run-in-docker-action/action.yml index 2af23643f..d3ab44b18 100644 --- a/.github/actions/run-in-docker-action/action.yml +++ b/.github/actions/run-in-docker-action/action.yml @@ -17,7 +17,7 @@ runs: steps: - uses: docker/setup-buildx-action@v3 - - uses: docker/build-push-action@v5 + - uses: docker/build-push-action@v6 id: main_builder continue-on-error: true with: @@ -26,7 +26,7 @@ runs: load: true cache-from: type=gha,scope=${{ inputs.scope }} - - uses: docker/build-push-action@v5 + - uses: docker/build-push-action@v6 id: retry_builder if: steps.main_builder.outcome == 'failure' with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49f16b875..f22ef2a3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: network=host - name: Build container - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: file: ./ci/linux-debian.Dockerfile cache-from: type=gha,scope=${{ runner.arch }} From f163c35897db9a4adc2a5b37926942817a4fdeb7 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:54:47 +0100 Subject: [PATCH 18/30] ci: Set `DEBIAN_FRONTEND=noninteractive` This suppresses `debconf: unable to initialize frontend: ...` warnings. --- ci/linux-debian.Dockerfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ci/linux-debian.Dockerfile b/ci/linux-debian.Dockerfile index 8ced83f20..22b6d35c9 100644 --- a/ci/linux-debian.Dockerfile +++ b/ci/linux-debian.Dockerfile @@ -21,7 +21,7 @@ RUN dpkg --add-architecture i386 && \ # dpkg-dev: to make pkg-config work in cross-builds # llvm: for llvm-symbolizer, which is used by clang's UBSan for symbolized stack traces -RUN apt-get update && apt-get install --no-install-recommends -y \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ git ca-certificates \ make automake libtool pkg-config dpkg-dev valgrind qemu-user \ gcc clang llvm libclang-rt-dev libc6-dbg \ @@ -34,14 +34,15 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ gcc-mingw-w64-i686-win32 wine32 \ python3-full && \ if ! ( dpkg --print-architecture | grep --quiet "arm64" ) ; then \ - apt-get install --no-install-recommends -y \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 ;\ fi && \ apt-get clean && rm -rf /var/lib/apt/lists/* # Build and install gcc snapshot ARG GCC_SNAPSHOT_MAJOR=16 -RUN apt-get update && apt-get install --no-install-recommends -y wget libgmp-dev libmpfr-dev libmpc-dev flex && \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + wget libgmp-dev libmpfr-dev libmpc-dev flex && \ mkdir gcc && cd gcc && \ wget --progress=dot:giga --https-only --recursive --accept '*.tar.xz' --level 1 --no-directories "https://gcc.gnu.org/pub/gcc/snapshots/LATEST-${GCC_SNAPSHOT_MAJOR}" && \ wget "https://gcc.gnu.org/pub/gcc/snapshots/LATEST-${GCC_SNAPSHOT_MAJOR}/sha512.sum" && \ @@ -62,7 +63,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y wget libgmp-dev # Install clang snapshot, see https://apt.llvm.org/ RUN \ # Setup GPG keys of LLVM repository - apt-get update && apt-get install --no-install-recommends -y wget && \ + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y wget && \ wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && \ # Add repository for this Debian release . /etc/os-release && echo "deb http://apt.llvm.org/${VERSION_CODENAME} llvm-toolchain-${VERSION_CODENAME} main" >> /etc/apt/sources.list && \ @@ -70,7 +71,7 @@ RUN \ # Determine the version number of the LLVM development branch LLVM_VERSION=$(apt-cache search --names-only '^clang-[0-9]+$' | sort -V | tail -1 | cut -f1 -d" " | cut -f2 -d"-" ) && \ # Install - apt-get install --no-install-recommends -y "clang-${LLVM_VERSION}" && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y "clang-${LLVM_VERSION}" && \ # Create symlink ln -s "/usr/bin/clang-${LLVM_VERSION}" /usr/bin/clang-snapshot && \ # Clean up From 6894c964f3504ab2bc09b46e17ba30c725d232f3 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:19:16 +0100 Subject: [PATCH 19/30] Fix Clang 21+ `-Wuninitialized-const-pointer` warning when using MSan Co-authored-by: Tim Ruffing --- src/checkmem.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/checkmem.h b/src/checkmem.h index 7e333ce5f..08eae47d7 100644 --- a/src/checkmem.h +++ b/src/checkmem.h @@ -48,7 +48,17 @@ # if __has_feature(memory_sanitizer) # include # define SECP256K1_CHECKMEM_ENABLED 1 -# define SECP256K1_CHECKMEM_UNDEFINE(p, len) __msan_allocated_memory((p), (len)) +# if defined(__clang__) && ((__clang_major__ == 21 && __clang_minor__ >= 1) || __clang_major__ >= 22) +# define SECP256K1_CHECKMEM_UNDEFINE(p, len) do { \ + /* Work around https://github.com/llvm/llvm-project/issues/160094 */ \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wuninitialized-const-pointer\"") \ + __msan_allocated_memory((p), (len)); \ + _Pragma("clang diagnostic pop") \ + } while(0) +# else +# define SECP256K1_CHECKMEM_UNDEFINE(p, len) __msan_allocated_memory((p), (len)) +# endif # define SECP256K1_CHECKMEM_DEFINE(p, len) __msan_unpoison((p), (len)) # define SECP256K1_CHECKMEM_MSAN_DEFINE(p, len) __msan_unpoison((p), (len)) # define SECP256K1_CHECKMEM_CHECK(p, len) __msan_check_mem_is_initialized((p), (len)) From 53585f93b7e6fe3933c77216f45e7d4f785436b5 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:19:45 +0100 Subject: [PATCH 20/30] ci: Use clang-snapshot in "MSan" job --- .github/workflows/ci.yml | 5 ++++- ci/linux-debian.Dockerfile | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f22ef2a3c..ad3568d1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -417,6 +417,9 @@ jobs: # when ctime_tests when enabled. CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g' CTIMETESTS: 'no' + cc: + - 'clang' + - 'clang-snapshot' env: ECDH: 'yes' @@ -425,7 +428,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' - CC: 'clang' + CC: ${{ matrix.cc }} SECP256K1_TEST_ITERS: 32 ASM: 'no' WITH_VALGRIND: 'no' diff --git a/ci/linux-debian.Dockerfile b/ci/linux-debian.Dockerfile index 22b6d35c9..a575d9b1c 100644 --- a/ci/linux-debian.Dockerfile +++ b/ci/linux-debian.Dockerfile @@ -71,7 +71,7 @@ RUN \ # Determine the version number of the LLVM development branch LLVM_VERSION=$(apt-cache search --names-only '^clang-[0-9]+$' | sort -V | tail -1 | cut -f1 -d" " | cut -f2 -d"-" ) && \ # Install - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y "clang-${LLVM_VERSION}" && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y "clang-${LLVM_VERSION}" "libclang-rt-${LLVM_VERSION}-dev" && \ # Create symlink ln -s "/usr/bin/clang-${LLVM_VERSION}" /usr/bin/clang-snapshot && \ # Clean up From 574c2f3080eeb65995a1c1a430ed7f84803afd26 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:47:23 +0100 Subject: [PATCH 21/30] ci: Use YAML anchor and aliases for repeated "Checkout" steps --- .github/workflows/ci.yml | 54 ++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f22ef2a3c..4e0a028c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,8 @@ jobs: CC: ${{ matrix.cc }} steps: - - name: Checkout + - &CHECKOUT + name: Checkout uses: actions/checkout@v4 - name: CI script @@ -143,8 +144,7 @@ jobs: CC: ${{ matrix.cc }} steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script uses: ./.github/actions/run-in-docker-action @@ -174,8 +174,7 @@ jobs: CTIMETESTS: 'no' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script uses: ./.github/actions/run-in-docker-action @@ -213,8 +212,7 @@ jobs: CTIMETESTS: 'no' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script env: ${{ matrix.configuration.env_vars }} @@ -253,8 +251,7 @@ jobs: - 'clang-snapshot' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script uses: ./.github/actions/run-in-docker-action @@ -284,8 +281,7 @@ jobs: CTIMETESTS: 'no' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script uses: ./.github/actions/run-in-docker-action @@ -339,8 +335,7 @@ jobs: SECP256K1_TEST_ITERS: 2 steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script env: ${{ matrix.env_vars }} @@ -382,8 +377,7 @@ jobs: SYMBOL_CHECK: 'no' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script env: ${{ matrix.configuration.env_vars }} @@ -432,8 +426,7 @@ jobs: SYMBOL_CHECK: 'no' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script env: ${{ matrix.configuration.env_vars }} @@ -474,8 +467,7 @@ jobs: HOST: 'i686-w64-mingw32' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script env: ${{ matrix.configuration.env_vars }} @@ -514,8 +506,7 @@ jobs: - BUILD: 'distcheck' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: Install Homebrew packages run: | @@ -566,8 +557,7 @@ jobs: - BUILD: 'distcheck' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: Install Homebrew packages run: | @@ -626,8 +616,7 @@ jobs: cpp_flags: '/DSECP256K1_MSVC_MULH_TEST_OVERRIDE' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: Generate buildsystem run: cmake -E env CFLAGS="/WX ${{ matrix.configuration.cpp_flags }}" cmake -B build -DSECP256K1_ENABLE_MODULE_RECOVERY=ON -DSECP256K1_BUILD_EXAMPLES=ON ${{ matrix.configuration.cmake_options }} @@ -662,8 +651,7 @@ jobs: runs-on: windows-2022 steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: Add cl.exe to PATH uses: ilammy/msvc-dev-cmd@v1 @@ -690,8 +678,7 @@ jobs: ELLSWIFT: 'yes' steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script uses: ./.github/actions/run-in-docker-action @@ -708,8 +695,7 @@ jobs: needs: docker_cache steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script uses: ./.github/actions/run-in-docker-action @@ -727,8 +713,7 @@ jobs: options: --user root steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - name: CI script run: | @@ -739,8 +724,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - *CHECKOUT - run: ./autogen.sh && ./configure --enable-dev-mode && make distcheck From a889cd93dfe5558aaa0320e241555bb96483c92b Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:49:05 +0100 Subject: [PATCH 22/30] ci: Bump `actions/checkout` version See https://github.com/actions/checkout/releases. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e0a028c5..6f7b51dd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: steps: - &CHECKOUT name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: CI script env: ${{ matrix.configuration.env_vars }} From 4b644da199587cdf9554fb48c0da7643c3ce7f30 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:51:22 +0100 Subject: [PATCH 23/30] ci: Use YAML anchor and aliases for repeated "Print logs" steps --- .github/workflows/ci.yml | 55 ++++++++++------------------------------ 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f7b51dd6..7195b7daf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,8 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs + - &PRINT_LOGS + name: Print logs uses: ./.github/actions/print-logs if: ${{ !cancelled() }} @@ -151,9 +152,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS s390x_debian: name: "s390x (big-endian): Linux (Debian stable, QEMU)" @@ -181,10 +180,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} - + - *PRINT_LOGS arm32_debian: name: "ARM32: Linux (Debian stable, QEMU)" @@ -220,9 +216,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS arm64-debian: name: "arm64: Linux (Debian stable)" @@ -258,9 +252,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS ppc64le_debian: name: "ppc64le: Linux (Debian stable, QEMU)" @@ -288,10 +280,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} - + - *PRINT_LOGS valgrind_debian: name: "Valgrind ${{ matrix.binary_arch }} (memcheck)" @@ -343,9 +332,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS sanitizers_debian: name: "UBSan, ASan, LSan" @@ -385,9 +372,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS msan_debian: name: "MSan" @@ -434,10 +419,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} - + - *PRINT_LOGS mingw_debian: name: ${{ matrix.configuration.job_name }} @@ -475,9 +457,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS x86_64-macos-native: name: "x86_64: macOS Ventura, Valgrind" @@ -526,9 +506,7 @@ jobs: python3 -m pip install lief python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS arm64-macos-native: name: "ARM64: macOS Sonoma" @@ -578,10 +556,7 @@ jobs: python3 -m pip install lief python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} - + - *PRINT_LOGS win64-native: name: ${{ matrix.configuration.job_name }} @@ -685,9 +660,7 @@ jobs: with: dockerfile: ./ci/linux-debian.Dockerfile - - name: Print logs - uses: ./.github/actions/print-logs - if: ${{ !cancelled() }} + - *PRINT_LOGS cxx_headers_debian: name: "C++ (public headers)" From dff1bc107d593b12e824cbc04b0d220f2ffeb967 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:23:54 +0100 Subject: [PATCH 24/30] ci, refactor: Generalize use of `matrix.configuration.env_vars` --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7195b7daf..07a574d05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,8 @@ jobs: strategy: fail-fast: false matrix: + configuration: + - env_vars: {} cc: - 'i686-linux-gnu-gcc' - 'clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include' @@ -148,6 +150,7 @@ jobs: - *CHECKOUT - name: CI script + env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile @@ -159,6 +162,11 @@ jobs: runs-on: ubuntu-latest needs: docker_cache + strategy: + matrix: + configuration: + - env_vars: {} + env: WRAPPER_CMD: 'qemu-s390x' SECP256K1_TEST_ITERS: 16 @@ -176,6 +184,7 @@ jobs: - *CHECKOUT - name: CI script + env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile @@ -238,6 +247,8 @@ jobs: strategy: fail-fast: false matrix: + configuration: + - env_vars: {} cc: - 'gcc' - 'clang' @@ -248,6 +259,7 @@ jobs: - *CHECKOUT - name: CI script + env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile @@ -259,6 +271,11 @@ jobs: runs-on: ubuntu-latest needs: docker_cache + strategy: + matrix: + configuration: + - env_vars: {} + env: WRAPPER_CMD: 'qemu-ppc64le' SECP256K1_TEST_ITERS: 16 @@ -276,6 +293,7 @@ jobs: - *CHECKOUT - name: CI script + env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile @@ -283,14 +301,14 @@ jobs: - *PRINT_LOGS valgrind_debian: - name: "Valgrind ${{ matrix.binary_arch }} (memcheck)" - runs-on: ${{ matrix.runner }} + name: "Valgrind ${{ matrix.configuration.binary_arch }} (memcheck)" + runs-on: ${{ matrix.configuration.runner }} needs: docker_cache strategy: fail-fast: false matrix: - include: + configuration: - runner: ubuntu-latest binary_arch: x64 env_vars: { CC: 'clang', ASM: 'auto' } @@ -327,7 +345,7 @@ jobs: - *CHECKOUT - name: CI script - env: ${{ matrix.env_vars }} + env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile @@ -640,6 +658,11 @@ jobs: runs-on: ubuntu-latest needs: docker_cache + strategy: + matrix: + configuration: + - env_vars: {} + env: CC: 'g++' CFLAGS: '-fpermissive -g' @@ -656,6 +679,7 @@ jobs: - *CHECKOUT - name: CI script + env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile From 1decc49a1f08564064420468fc48b9aebd3989d3 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:24:25 +0100 Subject: [PATCH 25/30] ci: Use YAML anchor and aliases for repeated "CI script" steps --- .github/workflows/ci.yml | 90 +++++++--------------------------------- 1 file changed, 15 insertions(+), 75 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07a574d05..832729415 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,8 @@ jobs: name: Checkout uses: actions/checkout@v5 - - name: CI script + - &CI_SCRIPT_IN_DOCKER + name: CI script env: ${{ matrix.configuration.env_vars }} uses: ./.github/actions/run-in-docker-action with: @@ -148,13 +149,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS s390x_debian: @@ -182,13 +177,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS arm32_debian: @@ -218,13 +207,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS arm64-debian: @@ -257,13 +240,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS ppc64le_debian: @@ -291,13 +268,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS valgrind_debian: @@ -343,13 +314,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS sanitizers_debian: @@ -383,13 +348,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS msan_debian: @@ -430,13 +389,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS mingw_debian: @@ -468,13 +421,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS x86_64-macos-native: @@ -514,7 +461,8 @@ jobs: - name: Install and cache Valgrind uses: ./.github/actions/install-homebrew-valgrind - - name: CI script + - &CI_SCRIPT_ON_HOST + name: CI script env: ${{ matrix.env_vars }} run: ./ci/ci.sh @@ -560,9 +508,7 @@ jobs: brew install --quiet automake libtool gcc ln -s $(brew --prefix gcc)/bin/gcc-?? /usr/local/bin/gcc - - name: CI script - env: ${{ matrix.env_vars }} - run: ./ci/ci.sh + - *CI_SCRIPT_ON_HOST - name: Symbol check env: @@ -677,13 +623,7 @@ jobs: steps: - *CHECKOUT - - - name: CI script - env: ${{ matrix.configuration.env_vars }} - uses: ./.github/actions/run-in-docker-action - with: - dockerfile: ./ci/linux-debian.Dockerfile - + - *CI_SCRIPT_IN_DOCKER - *PRINT_LOGS cxx_headers_debian: From 15d014804e5927b0f9dd8ed4fdc0be2a45f74e24 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:24:36 +0100 Subject: [PATCH 26/30] ci: Drop default for `inputs.command` in `run-in-docker-action` This change decreases coupling. --- .github/actions/run-in-docker-action/action.yml | 3 +-- .github/workflows/ci.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-in-docker-action/action.yml b/.github/actions/run-in-docker-action/action.yml index d3ab44b18..5d46ca1b0 100644 --- a/.github/actions/run-in-docker-action/action.yml +++ b/.github/actions/run-in-docker-action/action.yml @@ -10,8 +10,7 @@ inputs: default: ${{ runner.arch }} command: description: 'A command to run in a container' - required: false - default: ./ci/ci.sh + required: true runs: using: "composite" steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 832729415..8508224e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,6 +117,7 @@ jobs: uses: ./.github/actions/run-in-docker-action with: dockerfile: ./ci/linux-debian.Dockerfile + command: ./ci/ci.sh - &PRINT_LOGS name: Print logs From c09519f0e383da3c36e33522ca8708c17103d96a Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:20:38 +0100 Subject: [PATCH 27/30] ci: Drop workaround for Valgrind older than 3.20.0 --- ci/ci.sh | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/ci/ci.sh b/ci/ci.sh index 08e84efd4..515c14cd0 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -52,22 +52,6 @@ if [ -n "$WRAPPER_CMD" ]; then $WRAPPER_CMD --version fi -# Workaround for https://bugs.kde.org/show_bug.cgi?id=452758 (fixed in valgrind 3.20.0). -case "${CC:-undefined}" in - clang*) - if [ "$CTIMETESTS" = "yes" ] && [ "$WITH_VALGRIND" = "yes" ] - then - export CFLAGS="${CFLAGS:+$CFLAGS }-gdwarf-4" - else - case "$WRAPPER_CMD" in - valgrind*) - export CFLAGS="${CFLAGS:+$CFLAGS }-gdwarf-4" - ;; - esac - fi - ;; -esac - ./autogen.sh ./configure \ From 8bc50b72ff600f1b3cc05003c2de35a355512aab Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:31:21 +0100 Subject: [PATCH 28/30] ci: Switch to macOS 15 Sequoia Intel-based image The `macos-13` image has been deprecated and will be unavailable soon. See: https://github.com/actions/runner-images/issues/13045. --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dceab4f8a..617675159 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -429,9 +429,8 @@ jobs: - *PRINT_LOGS x86_64-macos-native: - name: "x86_64: macOS Ventura, Valgrind" - # See: https://github.com/actions/runner-images#available-images. - runs-on: macos-13 + name: "x86_64: macOS Sequoia, Valgrind" + runs-on: macos-15-intel env: CC: 'clang' From 7699fe9aa6dbf9f7b3273ae919c0ca6eafba953e Mon Sep 17 00:00:00 2001 From: DarkWindman Date: Thu, 26 Feb 2026 14:35:11 +0200 Subject: [PATCH 29/30] modules: Port bitcoin-core/secp256k1#1735 to zkp-specific code --- src/modules/ecdsa_s2c/main_impl.h | 2 +- src/modules/generator/pedersen_impl.h | 2 +- src/modules/rangeproof/borromean_impl.h | 2 +- src/modules/rangeproof/rangeproof_impl.h | 8 ++++---- src/modules/surjection/surjection_impl.h | 4 ++-- src/modules/whitelist/main_impl.h | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/modules/ecdsa_s2c/main_impl.h b/src/modules/ecdsa_s2c/main_impl.h index 471ae8fb1..59a4cdfa6 100644 --- a/src/modules/ecdsa_s2c/main_impl.h +++ b/src/modules/ecdsa_s2c/main_impl.h @@ -182,7 +182,7 @@ int secp256k1_ecdsa_anti_exfil_signer_commit(const secp256k1_context* ctx, secp2 secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); secp256k1_ge_set_gej(&r, &rj); secp256k1_ecdsa_s2c_opening_save(opening, &r); - secp256k1_memclear(nonce32, 32); + secp256k1_memclear_explicit(nonce32, 32); secp256k1_scalar_clear(&k); return 1; } diff --git a/src/modules/generator/pedersen_impl.h b/src/modules/generator/pedersen_impl.h index f5526eb6a..7d2a8a275 100644 --- a/src/modules/generator/pedersen_impl.h +++ b/src/modules/generator/pedersen_impl.h @@ -28,7 +28,7 @@ static void secp256k1_pedersen_scalar_set_u64(secp256k1_scalar *sec, uint64_t va value <<= 8; } secp256k1_scalar_set_b32(sec, data, NULL); - secp256k1_memclear(data, 32); + secp256k1_memclear_explicit(data, 32); } static void secp256k1_pedersen_ecmult_small(secp256k1_gej *r, uint64_t gn, const secp256k1_ge* genp) { diff --git a/src/modules/rangeproof/borromean_impl.h b/src/modules/rangeproof/borromean_impl.h index 3ca861084..4906ce85f 100644 --- a/src/modules/rangeproof/borromean_impl.h +++ b/src/modules/rangeproof/borromean_impl.h @@ -192,7 +192,7 @@ int secp256k1_borromean_sign(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_scalar_clear(&ens); secp256k1_ge_clear(&rge); secp256k1_gej_clear(&rgej); - secp256k1_memclear(tmp, 33); + secp256k1_memclear_explicit(tmp, 33); return 1; } diff --git a/src/modules/rangeproof/rangeproof_impl.h b/src/modules/rangeproof/rangeproof_impl.h index fc12ad4f4..476da5eb0 100644 --- a/src/modules/rangeproof/rangeproof_impl.h +++ b/src/modules/rangeproof/rangeproof_impl.h @@ -107,7 +107,7 @@ SECP256K1_INLINE static int secp256k1_rangeproof_genrand(secp256k1_scalar *sec, secp256k1_rfc6979_hmac_sha256_finalize(&rng); secp256k1_rfc6979_hmac_sha256_clear(&rng); secp256k1_scalar_clear(&acc); - secp256k1_memclear(tmp, 32); + secp256k1_memclear_explicit(tmp, 32); return ret; } @@ -270,7 +270,7 @@ SECP256K1_INLINE static int secp256k1_rangeproof_sign_impl(const secp256k1_ecmul if (!secp256k1_rangeproof_genrand(sec, s, prep, rsizes, rings, nonce, commit, proof, len, genp)) { return 0; } - secp256k1_memclear(prep, 4096); + secp256k1_memclear_explicit(prep, 4096); for (i = 0; i < rings; i++) { /* Sign will overwrite the non-forged signature, move that random value into the nonce. */ k[i] = s[i * 4 + secidx[i]]; @@ -332,7 +332,7 @@ SECP256K1_INLINE static int secp256k1_rangeproof_sign_impl(const secp256k1_ecmul } VERIFY_CHECK(len <= *plen); *plen = len; - secp256k1_memclear(prep, 4096); + secp256k1_memclear_explicit(prep, 4096); return 1; } @@ -473,7 +473,7 @@ SECP256K1_INLINE static int secp256k1_rangeproof_rewind_inner(secp256k1_scalar * } } *mlen = offset; - secp256k1_memclear(prep, 4096); + secp256k1_memclear_explicit(prep, 4096); for (i = 0; i < 128; i++) { secp256k1_scalar_clear(&s_orig[i]); } diff --git a/src/modules/surjection/surjection_impl.h b/src/modules/surjection/surjection_impl.h index cc0ad3009..0776e4c82 100644 --- a/src/modules/surjection/surjection_impl.h +++ b/src/modules/surjection/surjection_impl.h @@ -55,11 +55,11 @@ SECP256K1_INLINE static int secp256k1_surjection_genrand(secp256k1_scalar *s, si secp256k1_sha256_clear(&sha256_en); secp256k1_scalar_set_b32(&s[i], sec_input, &overflow); if (overflow == 1) { - secp256k1_memclear(sec_input, 32); + secp256k1_memclear_explicit(sec_input, 32); return 0; } } - secp256k1_memclear(sec_input, 32); + secp256k1_memclear_explicit(sec_input, 32); return 1; } diff --git a/src/modules/whitelist/main_impl.h b/src/modules/whitelist/main_impl.h index ce94d23a7..301d24766 100644 --- a/src/modules/whitelist/main_impl.h +++ b/src/modules/whitelist/main_impl.h @@ -54,7 +54,7 @@ int secp256k1_whitelist_sign(const secp256k1_context* ctx, secp256k1_whitelist_s break; } secp256k1_scalar_set_b32(&non, nonce32, &overflow); - secp256k1_memclear(nonce32, 32); + secp256k1_memclear_explicit(nonce32, 32); if (overflow || secp256k1_scalar_is_zero(&non)) { count++; continue; @@ -80,7 +80,7 @@ int secp256k1_whitelist_sign(const secp256k1_context* ctx, secp256k1_whitelist_s break; } } - secp256k1_memclear(seckey32, 32); + secp256k1_memclear_explicit(seckey32, 32); } /* Actually sign */ if (ret) { From 7111d365fbe210fb346f31abf68121b9911c7f0f Mon Sep 17 00:00:00 2001 From: DarkWindman Date: Thu, 26 Feb 2026 15:28:51 +0200 Subject: [PATCH 30/30] modules, tests: Port bitcoin-core/secp256k1#1734 to zkp-specific code --- src/modules/bppp/tests_impl.h | 28 +++++++++------- src/modules/ecdsa_adaptor/tests_impl.h | 36 ++++++++++----------- src/modules/ecdsa_s2c/tests_impl.h | 21 ++++++------ src/modules/generator/tests_impl.h | 29 ++++++++--------- src/modules/rangeproof/tests_impl.h | 36 +++++++++++---------- src/modules/schnorrsig_halfagg/tests_impl.h | 34 ++++++++++--------- src/modules/surjection/tests_impl.h | 26 +++++++++------ src/modules/whitelist/tests_impl.h | 25 ++++++++------ src/tests.c | 25 ++++++++++++++ 9 files changed, 152 insertions(+), 108 deletions(-) diff --git a/src/modules/bppp/tests_impl.h b/src/modules/bppp/tests_impl.h index 6764694cb..85d1c1ca7 100644 --- a/src/modules/bppp/tests_impl.h +++ b/src/modules/bppp/tests_impl.h @@ -15,6 +15,7 @@ #include "bppp_transcript_impl.h" #include "test_vectors/verify.h" #include "test_vectors/prove.h" +#include "../../unit_test.h" static void test_bppp_generators_api(void) { secp256k1_bppp_generators *gens; @@ -649,15 +650,7 @@ static void norm_arg_prove_vectors(void) { #undef IDX_TO_TEST -static void run_bppp_tests(void) { - test_log_exp(); - test_norm_util_helpers(); - test_serialize_two_points(); - test_bppp_generators_api(); - test_bppp_generators_fixed(); - test_bppp_tagged_hash(); - - norm_arg_verify_zero_len(); +static void norm_arg_test_all(void) { norm_arg_test(1, 1); norm_arg_test(1, 64); norm_arg_test(64, 1); @@ -665,9 +658,20 @@ static void run_bppp_tests(void) { norm_arg_test(32, 64); norm_arg_test(64, 32); norm_arg_test(64, 64); - - norm_arg_verify_vectors(); - norm_arg_prove_vectors(); } +/* --- Test registry --- */ +static const struct tf_test_entry tests_bppp[] = { + CASE1(test_log_exp), + CASE1(test_norm_util_helpers), + CASE1(test_serialize_two_points), + CASE1(test_bppp_generators_api), + CASE1(test_bppp_generators_fixed), + CASE1(test_bppp_tagged_hash), + CASE1(norm_arg_verify_zero_len), + CASE1(norm_arg_test_all), + CASE1(norm_arg_verify_vectors), + CASE1(norm_arg_prove_vectors), +}; + #endif diff --git a/src/modules/ecdsa_adaptor/tests_impl.h b/src/modules/ecdsa_adaptor/tests_impl.h index a33602238..e2293adc6 100644 --- a/src/modules/ecdsa_adaptor/tests_impl.h +++ b/src/modules/ecdsa_adaptor/tests_impl.h @@ -2,6 +2,7 @@ #define SECP256K1_MODULE_ECDSA_ADAPTOR_TESTS_H #include "../../../include/secp256k1_ecdsa_adaptor.h" +#include "../../unit_test.h" static void rand_scalar(secp256k1_scalar *scalar) { unsigned char buf32[32]; @@ -27,7 +28,7 @@ static void dleq_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_byt CHECK(secp256k1_scalar_eq(&k1, &k2) == 0); } -static void dleq_tests(void) { +static void dleq_tests_internal(void) { secp256k1_scalar s, e, sk, k; secp256k1_ge gen2, p1, p2; unsigned char *args[5]; @@ -850,7 +851,7 @@ static void test_ecdsa_adaptor_api(void) { CHECK_ILLEGAL(CTX, secp256k1_ecdsa_adaptor_recover(CTX, deckey, &sig, asig, &zero_pk)); } -static void adaptor_tests(void) { +static void adaptor_tests_internal(void) { unsigned char seckey[32]; secp256k1_pubkey pubkey; unsigned char msg[32]; @@ -1050,7 +1051,7 @@ static void adaptor_tests(void) { } } -static void multi_hop_lock_tests(void) { +static void multi_hop_lock_tests_internal(void) { unsigned char seckey_a[32]; unsigned char seckey_b[32]; unsigned char pop[32]; @@ -1124,21 +1125,18 @@ static void multi_hop_lock_tests(void) { CHECK(secp256k1_memcmp_var(buf, pop, 32) == 0); } -static void run_ecdsa_adaptor_tests(void) { - int i; - run_nonce_function_ecdsa_adaptor_tests(); - - test_ecdsa_adaptor_api(); - test_ecdsa_adaptor_spec_vectors(); - for (i = 0; i < COUNT; i++) { - dleq_tests(); - } - for (i = 0; i < COUNT; i++) { - adaptor_tests(); - } - for (i = 0; i < COUNT; i++) { - multi_hop_lock_tests(); - } -} +/* --- Test registry --- */ +REPEAT_TEST(dleq_tests) +REPEAT_TEST(adaptor_tests) +REPEAT_TEST(multi_hop_lock_tests) + +static const struct tf_test_entry tests_ecdsa_adaptor[] = { + CASE1(run_nonce_function_ecdsa_adaptor_tests), + CASE1(test_ecdsa_adaptor_api), + CASE1(test_ecdsa_adaptor_spec_vectors), + CASE1(dleq_tests), + CASE1(adaptor_tests), + CASE1(multi_hop_lock_tests), +}; #endif /* SECP256K1_MODULE_ECDSA_ADAPTOR_TESTS_H */ diff --git a/src/modules/ecdsa_s2c/tests_impl.h b/src/modules/ecdsa_s2c/tests_impl.h index f84443f31..d13e51647 100644 --- a/src/modules/ecdsa_s2c/tests_impl.h +++ b/src/modules/ecdsa_s2c/tests_impl.h @@ -8,6 +8,7 @@ #define SECP256K1_MODULE_ECDSA_S2C_TESTS_H #include "../../../include/secp256k1_ecdsa_s2c.h" +#include "../../unit_test.h" static void test_ecdsa_s2c_tagged_hash(void) { unsigned char tag_data[] = {'s', '2', 'c', '/', 'e', 'c', 'd', 's', 'a', '/', 'd', 'a', 't', 'a'}; @@ -323,15 +324,15 @@ static void test_ecdsa_anti_exfil(void) { } } -static void run_ecdsa_s2c_tests(void) { - run_s2c_opening_test(); - test_ecdsa_s2c_tagged_hash(); - test_ecdsa_s2c_api(); - test_ecdsa_s2c_fixed_vectors(); - test_ecdsa_s2c_sign_verify(); - - test_ecdsa_anti_exfil_signer_commit(); - test_ecdsa_anti_exfil(); -} +/* --- Test registry --- */ +static const struct tf_test_entry tests_ecdsa_s2c[] = { + CASE1(run_s2c_opening_test), + CASE1(test_ecdsa_s2c_tagged_hash), + CASE1(test_ecdsa_s2c_api), + CASE1(test_ecdsa_s2c_fixed_vectors), + CASE1(test_ecdsa_s2c_sign_verify), + CASE1(test_ecdsa_anti_exfil_signer_commit), + CASE1(test_ecdsa_anti_exfil) +}; #endif /* SECP256K1_MODULE_ECDSA_S2C_TESTS_H */ diff --git a/src/modules/generator/tests_impl.h b/src/modules/generator/tests_impl.h index bd8a0d928..c9f60c0f6 100644 --- a/src/modules/generator/tests_impl.h +++ b/src/modules/generator/tests_impl.h @@ -14,6 +14,7 @@ #include "../../scalar.h" #include "../../testrand.h" #include "../../util.h" +#include "../../unit_test.h" #include "../../../include/secp256k1_generator.h" @@ -228,7 +229,7 @@ static void test_pedersen_api(void) { CHECK_ILLEGAL(CTX, secp256k1_pedersen_blind_generator_blind_sum(CTX, &val, &blind_ptr, NULL, 1, 0)); } -static void test_pedersen(void) { +static void test_pedersen_internal(void) { secp256k1_pedersen_commitment commits[19]; const secp256k1_pedersen_commitment *cptr[19]; unsigned char blinds[32*19]; @@ -310,19 +311,17 @@ static void test_pedersen_commitment_fixed_vector(void) { CHECK(!secp256k1_pedersen_commitment_parse(CTX, &parse, result)); } - -static void run_generator_tests(void) { - int i; - - test_shallue_van_de_woestijne(); - test_generator_fixed_vector(); - test_generator_api(); - test_generator_generate(); - test_pedersen_api(); - test_pedersen_commitment_fixed_vector(); - for (i = 0; i < COUNT / 2 + 1; i++) { - test_pedersen(); - } -} +/* --- Test registry --- */ +REPEAT_TEST(test_pedersen) + +static const struct tf_test_entry tests_generator[] = { + CASE1(test_shallue_van_de_woestijne), + CASE1(test_generator_fixed_vector), + CASE1(test_generator_api), + CASE1(test_generator_generate), + CASE1(test_pedersen), + CASE1(test_pedersen_api), + CASE1(test_pedersen_commitment_fixed_vector), +}; #endif diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index e0686f904..74d9f3fdb 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -13,10 +13,11 @@ #include "../../scalar.h" #include "../../testrand.h" #include "../../util.h" +#include "../../unit_test.h" #include "../../../include/secp256k1_rangeproof.h" -static void test_rangeproof_api(void) { +static void test_rangeproof_api_internal(void) { unsigned char proof[5134]; unsigned char blind[32]; secp256k1_pedersen_commitment commit; @@ -121,7 +122,7 @@ static void test_rangeproof_api(void) { CHECK(secp256k1_rangeproof_max_size(CTX, UINT64_MAX, 0) == 5134); } -static void test_borromean(void) { +static void test_borromean_internal(void) { unsigned char e0[32]; secp256k1_scalar s[64]; secp256k1_gej pubs[64]; @@ -1346,24 +1347,25 @@ static void test_rangeproof_fixed_vectors_reproducible(void) { } } -static void run_rangeproof_tests(void) { - int i; - for (i = 0; i < COUNT; i++) { - test_rangeproof_api(); - } - +static void test_single_value_proof_all(void) { test_single_value_proof(0); test_single_value_proof(12345678); test_single_value_proof(UINT64_MAX); - - test_rangeproof_fixed_vectors(); - test_rangeproof_fixed_vectors_reproducible(); - for (i = 0; i < COUNT / 2 + 1; i++) { - test_borromean(); - } - test_rangeproof(); - test_rangeproof_null_blinder(); - test_multiple_generators(); } +/* --- Test registry --- */ +REPEAT_TEST(test_rangeproof_api) +REPEAT_TEST(test_borromean) + +static const struct tf_test_entry tests_rangeproof[] = { + CASE1(test_rangeproof_api), + CASE1(test_single_value_proof_all), + CASE1(test_rangeproof_fixed_vectors), + CASE1(test_rangeproof_fixed_vectors_reproducible), + CASE1(test_borromean), + CASE1(test_rangeproof), + CASE1(test_rangeproof_null_blinder), + CASE1(test_multiple_generators), +}; + #endif diff --git a/src/modules/schnorrsig_halfagg/tests_impl.h b/src/modules/schnorrsig_halfagg/tests_impl.h index b93999b64..29d39b2c8 100644 --- a/src/modules/schnorrsig_halfagg/tests_impl.h +++ b/src/modules/schnorrsig_halfagg/tests_impl.h @@ -2,6 +2,7 @@ #define SECP256K1_MODULE_SCHNORRSIG_HALFAGG_TESTS_H #include "../../../include/secp256k1_schnorrsig_halfagg.h" +#include "../../unit_test.h" #define N_MAX 50 @@ -34,7 +35,7 @@ void test_schnorrsig_aggregate_input_helper(secp256k1_xonly_pubkey *pubkeys, uns * aggregate some of them in one shot, and then * aggregate the others incrementally to the already aggregated ones. * The aggregate signature should verify after both steps. */ -void test_schnorrsig_aggregate(void) { +void test_schnorrsig_aggregate_internal(void) { secp256k1_xonly_pubkey pubkeys[N_MAX]; unsigned char msgs32[N_MAX*32]; unsigned char sigs64[N_MAX*64]; @@ -165,7 +166,7 @@ void test_schnorrsig_aggverify_spec_vectors(void) { } } -static void test_schnorrsig_aggregate_api(void) { +static void test_schnorrsig_aggregate_api_internal(void) { size_t n = testrand_int(N_MAX + 1); size_t n_initial = testrand_int(n + 1); size_t n_new = n - n_initial; @@ -241,7 +242,7 @@ static void test_schnorrsig_aggregate_api(void) { /* In this test, we make sure that trivial attempts to break * the security of verification do not work. */ -static void test_schnorrsig_aggregate_unforge(void) { +static void test_schnorrsig_aggregate_unforge_internal(void) { secp256k1_xonly_pubkey pubkeys[N_MAX]; unsigned char msgs32[N_MAX*32]; unsigned char sigs64[N_MAX*64]; @@ -297,7 +298,7 @@ static void test_schnorrsig_aggregate_unforge(void) { /* In this test, we make sure that the algorithms properly reject * for overflowing and non parseable values. */ -static void test_schnorrsig_aggregate_overflow(void) { +static void test_schnorrsig_aggregate_overflow_internal(void) { secp256k1_xonly_pubkey pubkeys[N_MAX]; unsigned char msgs32[N_MAX*32]; unsigned char sigs64[N_MAX*64]; @@ -317,19 +318,20 @@ static void test_schnorrsig_aggregate_overflow(void) { } } -static void run_schnorrsig_halfagg_tests(void) { - int i; +/* --- Test registry --- */ +REPEAT_TEST(test_schnorrsig_aggregate) +REPEAT_TEST(test_schnorrsig_aggregate_api) +REPEAT_TEST(test_schnorrsig_aggregate_unforge) +REPEAT_TEST(test_schnorrsig_aggregate_overflow) - test_schnorrsig_sha256_tagged_aggregate(); - test_schnorrsig_aggverify_spec_vectors(); - - for (i = 0; i < COUNT; i++) { - test_schnorrsig_aggregate(); - test_schnorrsig_aggregate_api(); - test_schnorrsig_aggregate_unforge(); - test_schnorrsig_aggregate_overflow(); - } -} +static const struct tf_test_entry tests_schnorrsig_halfagg[] = { + CASE1(test_schnorrsig_sha256_tagged_aggregate), + CASE1(test_schnorrsig_aggverify_spec_vectors), + CASE1(test_schnorrsig_aggregate), + CASE1(test_schnorrsig_aggregate_api), + CASE1(test_schnorrsig_aggregate_unforge), + CASE1(test_schnorrsig_aggregate_overflow), +}; #undef N_MAX diff --git a/src/modules/surjection/tests_impl.h b/src/modules/surjection/tests_impl.h index e609d1708..731e2fd4b 100644 --- a/src/modules/surjection/tests_impl.h +++ b/src/modules/surjection/tests_impl.h @@ -9,6 +9,7 @@ #include "../../testrand.h" #include "../../group.h" +#include "../../unit_test.h" #include "../../../include/secp256k1_generator.h" #include "../../../include/secp256k1_rangeproof.h" #include "../../../include/secp256k1_surjectionproof.h" @@ -627,22 +628,29 @@ static void test_fixed_vectors(void) { CHECK(!secp256k1_surjectionproof_parse(CTX, &proof, bad, total5_used3_len)); } -static void run_surjection_tests(void) { - test_surjectionproof_api(); - test_input_eq_output(); - test_fixed_vectors(); - +static void test_input_selection_all(void) { test_input_selection(0); test_input_selection(1); test_input_selection(5); test_input_selection(SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS); +} - test_input_selection_distribution(); +static void test_gen_verify_all(void) { test_gen_verify(10, 3); test_gen_verify(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS); - test_no_used_inputs_verify(); - test_bad_serialize(); - test_bad_parse(); } +/* --- Test registry --- */ +static const struct tf_test_entry tests_surjection[] = { + CASE1(test_surjectionproof_api), + CASE1(test_input_eq_output), + CASE1(test_fixed_vectors), + CASE1(test_input_selection_all), + CASE1(test_input_selection_distribution), + CASE1(test_gen_verify_all), + CASE1(test_no_used_inputs_verify), + CASE1(test_bad_serialize), + CASE1(test_bad_parse), +}; + #endif diff --git a/src/modules/whitelist/tests_impl.h b/src/modules/whitelist/tests_impl.h index 38f91e185..9cbb8287d 100644 --- a/src/modules/whitelist/tests_impl.h +++ b/src/modules/whitelist/tests_impl.h @@ -8,6 +8,7 @@ #define SECP256K1_MODULE_WHITELIST_TESTS_H #include "../../../include/secp256k1_whitelist.h" +#include "../../unit_test.h" static void test_whitelist_end_to_end_internal(const unsigned char *summed_seckey, const unsigned char *online_seckey, const secp256k1_pubkey *online_pubkeys, const secp256k1_pubkey *offline_pubkeys, const secp256k1_pubkey *sub_pubkey, const size_t signer_i, const size_t n_keys) { unsigned char serialized[32 + 4 + 32 * SECP256K1_WHITELIST_MAX_N_KEYS] = {0}; @@ -148,16 +149,20 @@ static void test_whitelist_bad_serialize(void) { CHECK(secp256k1_whitelist_signature_serialize(CTX, serialized, &serialized_len, &sig) == 0); } -static void run_whitelist_tests(void) { - int i; - test_whitelist_bad_parse(); - test_whitelist_bad_serialize(); - for (i = 0; i < COUNT; i++) { - test_whitelist_end_to_end(1, 1); - test_whitelist_end_to_end(10, 1); - test_whitelist_end_to_end(50, 1); - test_whitelist_end_to_end(SECP256K1_WHITELIST_MAX_N_KEYS, 0); - } +static void test_whitelist_end_to_end_all_internal(void) { + test_whitelist_end_to_end(1, 1); + test_whitelist_end_to_end(10, 1); + test_whitelist_end_to_end(50, 1); + test_whitelist_end_to_end(SECP256K1_WHITELIST_MAX_N_KEYS, 0); } +/* --- Test registry --- */ +REPEAT_TEST(test_whitelist_end_to_end_all) + +static const struct tf_test_entry tests_whitelist[] = { + CASE1(test_whitelist_bad_parse), + CASE1(test_whitelist_bad_serialize), + CASE1(test_whitelist_end_to_end_all), +}; + #endif diff --git a/src/tests.c b/src/tests.c index f7ef46fea..7c1a03f8f 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7982,6 +7982,31 @@ static const struct tf_test_module registry_modules[] = { #endif #ifdef ENABLE_MODULE_ELLSWIFT MAKE_TEST_MODULE(ellswift), +#endif + /* --- ZKP-SPECIFIC MODULES --- */ +#ifdef ENABLE_MODULE_SCHNORRSIG_HALFAGG + MAKE_TEST_MODULE(schnorrsig_halfagg), +#endif +#ifdef ENABLE_MODULE_BPPP + MAKE_TEST_MODULE(bppp), +#endif +#ifdef ENABLE_MODULE_GENERATOR + MAKE_TEST_MODULE(generator), +#endif +#ifdef ENABLE_MODULE_RANGEPROOF + MAKE_TEST_MODULE(rangeproof), +#endif +#ifdef ENABLE_MODULE_WHITELIST + MAKE_TEST_MODULE(whitelist), +#endif +#ifdef ENABLE_MODULE_SURJECTIONPROOF + MAKE_TEST_MODULE(surjection), +#endif +#ifdef ENABLE_MODULE_ECDSA_ADAPTOR + MAKE_TEST_MODULE(ecdsa_adaptor), +#endif +#ifdef ENABLE_MODULE_ECDSA_S2C + MAKE_TEST_MODULE(ecdsa_s2c), #endif MAKE_TEST_MODULE(utils), };