From 800233a2d467600753ffeb0fd67d718e861182ba Mon Sep 17 00:00:00 2001 From: siv2r Date: Mon, 30 Jun 2025 18:00:06 +0530 Subject: [PATCH 1/9] batch: Initialize an experimental batch module This commit adds the foundational configuration, build scripts, and an initial structure for experimental batch module. --- CMakeLists.txt | 5 +++++ Makefile.am | 4 ++++ README.md | 1 + configure.ac | 13 +++++++++++++ include/secp256k1_batch.h | 25 +++++++++++++++++++++++++ src/CMakeLists.txt | 5 +++++ src/modules/batch/Makefile.am.include | 2 ++ src/modules/batch/main_impl.h | 6 ++++++ src/secp256k1.c | 4 ++++ 9 files changed, 65 insertions(+) create mode 100644 include/secp256k1_batch.h create mode 100644 src/modules/batch/Makefile.am.include create mode 100644 src/modules/batch/main_impl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 02abaa970f..babdf024b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) +option(SECP256K1_ENABLE_MODULE_BATCH "Enable batch module (experimental)." OFF) option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) if(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS) @@ -116,6 +117,9 @@ if(NOT SECP256K1_EXPERIMENTAL) if(SECP256K1_ASM STREQUAL "arm32") message(FATAL_ERROR "ARM32 assembly is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") endif() + if(SECP256K1_ENABLE_MODULE_BATCH) + message(FATAL_ERROR "The batch module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") + endif() endif() set(SECP256K1_VALGRIND "AUTO" CACHE STRING "Build with extra checks for running inside Valgrind. [default=AUTO]") @@ -284,6 +288,7 @@ message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRA message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") +message(" batch (experimental) ................ ${SECP256K1_ENABLE_MODULE_BATCH}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB") diff --git a/Makefile.am b/Makefile.am index d511853b05..90b06e403c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -311,3 +311,7 @@ endif if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif + +if ENABLE_MODULE_BATCH +include src/modules/batch/Makefile.am.include +endif diff --git a/README.md b/README.md index 3d3118adf9..22c2bc68e7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Features: * Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). * Optional module for ElligatorSwift key exchange according to [BIP-324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki). * Optional module for MuSig2 Schnorr multi-signatures according to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki). +* Optional module for Batch Verification (experimental). Implementation details ---------------------- diff --git a/configure.ac b/configure.ac index 406227fcd1..137113110f 100644 --- a/configure.ac +++ b/configure.ac @@ -191,6 +191,10 @@ AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) +AC_ARG_ENABLE(module_batch, + AS_HELP_STRING([--enable-module-batch],[enable batch verification module (experimental) [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_batch], [no], [yes])]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) @@ -397,6 +401,10 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" # Processing must be done in a reverse topological sorting of the dependency graph # (dependent module first). +if test x"$enable_module_batch" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_BATCH=1" +fi + if test x"$enable_module_ellswift" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi @@ -441,6 +449,9 @@ if test x"$enable_experimental" = x"no"; then if test x"$set_asm" = x"arm32"; then AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.]) fi + if test x"$enable_module_batch" = x"yes"; then + AC_MSG_ERROR([batch verification module is experimental. Use --enable-experimental to allow.]) + fi fi ### @@ -462,6 +473,7 @@ AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x" AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_BATCH], [test x"$enable_module_batch" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) @@ -486,6 +498,7 @@ echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module musig = $enable_module_musig" echo " module ellswift = $enable_module_ellswift" +echo " module batch = $enable_module_batch" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/include/secp256k1_batch.h b/include/secp256k1_batch.h new file mode 100644 index 0000000000..9d467fd08f --- /dev/null +++ b/include/secp256k1_batch.h @@ -0,0 +1,25 @@ +#ifndef SECP256K1_BATCH_H +#define SECP256K1_BATCH_H + +#include "secp256k1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** This module implements a Batch Verification object that supports: + * + * 1. Schnorr signatures compliant with Bitcoin Improvement Proposal 340 + * "Schnorr Signatures for secp256k1" + * (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). + * + * 2. Taproot commitments compliant with Bitcoin Improvement Proposal 341 + * "Taproot: SegWit version 1 spending rules" + * (https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki). + */ + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_BATCH_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8f1d2ee3ba..e2624050d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,11 @@ set_property(TARGET secp256k1 PROPERTY PUBLIC_HEADER # Processing must be done in a topological sorting of the dependency graph # (dependent module first). +if(SECP256K1_ENABLE_MODULE_BATCH) + add_compile_definitions(ENABLE_MODULE_BATCH=1) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_batch.h) +endif() + if(SECP256K1_ENABLE_MODULE_ELLSWIFT) add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ellswift.h) diff --git a/src/modules/batch/Makefile.am.include b/src/modules/batch/Makefile.am.include new file mode 100644 index 0000000000..ddad7594fb --- /dev/null +++ b/src/modules/batch/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_batch.h +noinst_HEADERS += src/modules/batch/main_impl.h \ No newline at end of file diff --git a/src/modules/batch/main_impl.h b/src/modules/batch/main_impl.h new file mode 100644 index 0000000000..1bbbb83061 --- /dev/null +++ b/src/modules/batch/main_impl.h @@ -0,0 +1,6 @@ +#ifndef SECP256K1_MODULE_BATCH_MAIN_H +#define SECP256K1_MODULE_BATCH_MAIN_H + +#include "../../../include/secp256k1_batch.h" + +#endif /* SECP256K1_MODULE_BATCH_MAIN_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index 0915af7797..9f5b119562 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -817,3 +817,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_ELLSWIFT # include "modules/ellswift/main_impl.h" #endif + +#ifdef ENABLE_MODULE_BATCH +# include "modules/batch/main_impl.h" +#endif From 535a94b6d2ad9cda2838273c583f0a3f668615b8 Mon Sep 17 00:00:00 2001 From: siv2r Date: Mon, 30 Jun 2025 18:40:45 +0530 Subject: [PATCH 2/9] batch: Add create and destroy APIs This commit adds the batch_create and batch_destroy APIs. Relevant Links: 1. batch_scratch_size allocation formula is taken from bench ecmult: https://github.com/bitcoin-core/secp256k1/blob/694ce8fb2d1fd8a3d641d7c33705691d41a2a860/src/bench_ecmult.c#L312. 2. aux_rand16 param in batch_create enables synthetic randomness for randomizer generation: https://github.com/sipa/bips/issues/204. --- include/secp256k1_batch.h | 45 ++++++++++++ src/modules/batch/main_impl.h | 135 ++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/include/secp256k1_batch.h b/include/secp256k1_batch.h index 9d467fd08f..9399fdb9a9 100644 --- a/include/secp256k1_batch.h +++ b/include/secp256k1_batch.h @@ -18,6 +18,51 @@ extern "C" { * (https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki). */ +/** Opaque data structure that holds information required for the batch verification. + * + * The purpose of this structure is to store elliptic curve points, their scalar + * coefficients, and scalar coefficient of generator point participating in Multi-Scalar + * Point Multiplication computation, which is done by `secp256k1_ecmult_strauss_batch_internal` + */ +typedef struct secp256k1_batch_struct secp256k1_batch; + +/** Create a secp256k1 batch object object (in dynamically allocated memory). + * + * This function uses malloc to allocate memory. It is guaranteed that malloc is + * called at most twice for every call of this function. + * + * Returns: a newly created batch object. + * Args: ctx: an existing `secp256k1_context` object. Not to be confused + * with the batch object object that this function creates. + * In: max_terms: Max number of (scalar, curve point) pairs that the batch + * object can store. + * 1. `batch_add_schnorrsig` - adds two scalar-point pairs to the batch + * 2. `batch_add_xonpub_tweak_check` - adds one scalar-point pair to the batch + * Hence, for adding n schnorrsigs and m tweak checks, `max_terms` + * should be set to 2*n + m. + * aux_rand16: 16 bytes of fresh randomness. While recommended to provide + * this, it is only supplemental to security and can be NULL. A + * NULL argument is treated the same as an all-zero one. + */ +SECP256K1_API secp256k1_batch* secp256k1_batch_create( + const secp256k1_context* ctx, + size_t max_terms, + const unsigned char *aux_rand16 +) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT; + +/** Destroy a secp256k1 batch object (created in dynamically allocated memory). + * + * The batch object's pointer may not be used afterwards. + * + * Args: ctx: a secp256k1 context object. + * batch: an existing batch object to destroy, constructed + * using `secp256k1_batch_create` + */ +SECP256K1_API void secp256k1_batch_destroy( + const secp256k1_context* ctx, + secp256k1_batch* batch +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + #ifdef __cplusplus } #endif diff --git a/src/modules/batch/main_impl.h b/src/modules/batch/main_impl.h index 1bbbb83061..08240736ba 100644 --- a/src/modules/batch/main_impl.h +++ b/src/modules/batch/main_impl.h @@ -3,4 +3,139 @@ #include "../../../include/secp256k1_batch.h" +/** Opaque data structure that holds information required for the batch verification. + * + * Members: + * data: scratch space object that contains points (_gej) and their + * respective scalars. To be used in Multi-Scalar Multiplication + * algorithms such as Strauss and Pippenger. + * scalars: pointer to scalars allocated on the scratch space. + * points: pointer to points allocated on the scratch space. + * sc_g: scalar corresponding to the generator point (G) in Multi-Scalar + * Multiplication equation. + * sha256: contains hash of all the inputs (schnorrsig/tweaks) present in + * the batch object, expect the first input. Used for generating a random secp256k1_scalar + * for each term added by secp256k1_batch_add_*. + * sha256: contains hash of all inputs (except the first one) present in the batch. + * `secp256k1_batch_add_` APIs use these for randomizing the scalar (i.e., multiplying + * it with a newly generated scalar) before adding it to the batch. + * len: number of scalar-point pairs present in the batch. + * capacity: max number of scalar-point pairs that the batch can hold. + * result: tells whether the given set of inputs (schnorrsigs or tweak checks) is valid + * or invalid. 1 = valid and 0 = invalid. By default, this is set to 1 + * during batch object creation (i.e., `secp256k1_batch_create`). + * + * The following struct name is typedef as secp256k1_batch (in include/secp256k1_batch.h). + */ +struct secp256k1_batch_struct{ + secp256k1_scratch *data; + secp256k1_scalar *scalars; + secp256k1_gej *points; + secp256k1_scalar sc_g; + secp256k1_sha256 sha256; + size_t len; + size_t capacity; + int result; +}; + +static size_t secp256k1_batch_scratch_size(int max_terms) { + size_t ret = secp256k1_strauss_scratch_size(max_terms) + STRAUSS_SCRATCH_OBJECTS*16; + VERIFY_CHECK(ret != 0); + + return ret; +} + +/** Allocates space for `batch->capacity` number of scalars and points on batch + * object's scratch space */ +static int secp256k1_batch_scratch_alloc(const secp256k1_callback* error_callback, secp256k1_batch* batch) { + size_t checkpoint = secp256k1_scratch_checkpoint(error_callback, batch->data); + size_t count = batch->capacity; + + VERIFY_CHECK(count > 0); + + batch->scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(error_callback, batch->data, count*sizeof(secp256k1_scalar)); + batch->points = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, batch->data, count*sizeof(secp256k1_gej)); + + /* If scalar or point allocation fails, restore scratch space to previous state */ + if (batch->scalars == NULL || batch->points == NULL) { + secp256k1_scratch_apply_checkpoint(error_callback, batch->data, checkpoint); + return 0; + } + + return 1; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0340/batch")||SHA256("BIP0340/batch"). */ +static void secp256k1_batch_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x79e3e0d2ul; + sha->s[1] = 0x12284f32ul; + sha->s[2] = 0xd7d89e1cul; + sha->s[3] = 0x6491ea9aul; + sha->s[4] = 0xad823b2ful; + sha->s[5] = 0xfacfe0b6ul; + sha->s[6] = 0x342b78baul; + sha->s[7] = 0x12ece87cul; + + sha->bytes = 64; +} + +secp256k1_batch* secp256k1_batch_create(const secp256k1_context* ctx, size_t max_terms, const unsigned char *aux_rand16) { + size_t batch_size; + secp256k1_batch* batch; + size_t batch_scratch_size; + unsigned char zeros[16] = {0}; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(max_terms != 0); + + batch_size = sizeof(secp256k1_batch); + batch = (secp256k1_batch *)checked_malloc(&ctx->error_callback, batch_size); + batch_scratch_size = secp256k1_batch_scratch_size(max_terms); + if (batch != NULL) { + /* create scratch space inside batch object, if that fails return NULL*/ + batch->data = secp256k1_scratch_create(&ctx->error_callback, batch_scratch_size); + if (batch->data == NULL) { + return NULL; + } + /* allocate memeory for `max_terms` number of scalars and points on scratch space */ + batch->capacity = max_terms; + if (!secp256k1_batch_scratch_alloc(&ctx->error_callback, batch)) { + /* if scratch memory allocation fails, free all the previous the allocated memory + and return NULL */ + secp256k1_scratch_destroy(&ctx->error_callback, batch->data); + free(batch); + return NULL; + } + + /* set remaining data members */ + secp256k1_scalar_clear(&batch->sc_g); + secp256k1_batch_sha256_tagged(&batch->sha256); + if (aux_rand16 != NULL) { + secp256k1_sha256_write(&batch->sha256, aux_rand16, 16); + } else { + /* use 16 bytes of 0x0000...000, if no fresh randomness provided */ + secp256k1_sha256_write(&batch->sha256, zeros, 16); + } + batch->len = 0; + batch->result = 1; + } + + return batch; +} + +void secp256k1_batch_destroy(const secp256k1_context *ctx, secp256k1_batch *batch) { + VERIFY_CHECK(ctx != NULL); + + if (batch != NULL) { + if(batch->data != NULL) { + /* can't destroy a scratch space with non-zero size */ + secp256k1_scratch_apply_checkpoint(&ctx->error_callback, batch->data, 0); + secp256k1_scratch_destroy(&ctx->error_callback, batch->data); + } + free(batch); + } +} + #endif /* SECP256K1_MODULE_BATCH_MAIN_H */ From b47d8f3877a695a04091d6194a78ba6a471ae7dd Mon Sep 17 00:00:00 2001 From: siv2r Date: Mon, 30 Jun 2025 20:29:04 +0530 Subject: [PATCH 3/9] batch, ecmult: Add batch_verify and refactor strauss_batch This commit refactors ecmult_strauss_batch and adds _batch_verify API. The current ecmult_strauss_batch only works on empty scratch space. To make batch_verify compatible, we need ecmult_strauss_batch to support a scratch space pre-filled with scalars and points. So, it was refactored to do exactly that. The batch_verify API always uses the Strauss algorithm. It doesn't switch to Pippenger (unlike ecmult_multi_var). ecmult_pippenger_batch represents points as secp256k1_ge whereas ecmult_strauss_batch represents points as secp256k1_gej. This makes supporting both Pippenger and Strauss difficult (at least with the current batch object design). Hence, batch_verify only supports Strauss for simplicity. --- include/secp256k1_batch.h | 15 +++++++ src/ecmult_impl.h | 73 ++++++++++++++++++++++++++--------- src/modules/batch/main_impl.h | 44 +++++++++++++++++++++ 3 files changed, 113 insertions(+), 19 deletions(-) diff --git a/include/secp256k1_batch.h b/include/secp256k1_batch.h index 9399fdb9a9..0f1de05c32 100644 --- a/include/secp256k1_batch.h +++ b/include/secp256k1_batch.h @@ -63,6 +63,21 @@ SECP256K1_API void secp256k1_batch_destroy( secp256k1_batch* batch ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); +/** Verify the set of schnorr signatures or tweaked pubkeys present in the secp256k1_batch. + * + * Returns: 1: every schnorrsig/tweak (in batch) is valid + * 0: atleaset one of the schnorrsig/tweak (in batch) is invalid + * + * In particular, returns 1 if the batch object is empty (does not contain any schnorrsigs/tweaks). + * + * Args: ctx: a secp256k1 context object (can be initialized for none). + * batch: a secp256k1 batch object that contains a set of schnorrsigs/tweaks. + */ +SECP256K1_API int secp256k1_batch_verify( + const secp256k1_context *ctx, + secp256k1_batch *batch +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + #ifdef __cplusplus } #endif diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index 0b53b3fcb9..248a4cd8b9 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -358,16 +358,27 @@ static void secp256k1_ecmult(secp256k1_gej *r, const secp256k1_gej *a, const sec secp256k1_ecmult_strauss_wnaf(&state, r, 1, a, na, ng); } -static size_t secp256k1_strauss_scratch_size(size_t n_points) { - static const size_t point_size = (sizeof(secp256k1_ge) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar); - return n_points*point_size; +/** Allocate strauss state on the scratch space */ +static int secp256k1_strauss_scratch_alloc_state(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, struct secp256k1_strauss_state *state, size_t n_points) { + const size_t scratch_checkpoint = secp256k1_scratch_checkpoint(error_callback, scratch); + + /* We allocate three objects on the scratch space. If these allocations + * change, make sure to check if this affects STRAUSS_SCRATCH_OBJECTS + * constant and strauss_scratch_size. */ + state->aux = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe)); + state->pre_a = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge)); + state->ps = (struct secp256k1_strauss_point_state*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(struct secp256k1_strauss_point_state)); + + if (state->aux == NULL || state->pre_a == NULL || state->ps == NULL) { + secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); + return 0; + } + return 1; } -static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n_points, size_t cb_offset) { - secp256k1_gej* points; - secp256k1_scalar* scalars; +/** Run ecmult_strauss_wnaf on the given points and scalars */ +static int secp256k1_ecmult_strauss_batch_internal(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, secp256k1_scalar *scalars, secp256k1_gej *points, const secp256k1_scalar *inp_g_sc, size_t n_points) { struct secp256k1_strauss_state state; - size_t i; const size_t scratch_checkpoint = secp256k1_scratch_checkpoint(error_callback, scratch); secp256k1_gej_set_infinity(r); @@ -375,16 +386,30 @@ static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callba return 1; } - /* We allocate STRAUSS_SCRATCH_OBJECTS objects on the scratch space. If these - * allocations change, make sure to update the STRAUSS_SCRATCH_OBJECTS - * constant and strauss_scratch_size accordingly. */ + if(!secp256k1_strauss_scratch_alloc_state(error_callback, scratch, &state, n_points)) { + return 0; + } + + secp256k1_ecmult_strauss_wnaf(&state, r, n_points, points, scalars, inp_g_sc); + secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); + return 1; +} + +/** Run ecmult_strauss_wnaf on the given points and scalars. Returns 0 if the + * scratch space is empty. `n_points` number of scalars and points are + * extracted from `cbdata` using `cb` and stored on the scratch space. + */ +static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n_points, size_t cb_offset) { + secp256k1_gej* points; + secp256k1_scalar* scalars; + size_t i; + const size_t scratch_checkpoint = secp256k1_scratch_checkpoint(error_callback, scratch); + /* We allocate STRAUSS_SCRATCH_OBJECTS objects on the scratch space in + * total. If these allocations change, make sure to update the + * STRAUSS_SCRATCH_OBJECTS constant and strauss_scratch_size accordingly. */ points = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_gej)); scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_scalar)); - state.aux = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe)); - state.pre_a = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge)); - state.ps = (struct secp256k1_strauss_point_state*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(struct secp256k1_strauss_point_state)); - - if (points == NULL || scalars == NULL || state.aux == NULL || state.pre_a == NULL || state.ps == NULL) { + if (points == NULL || scalars == NULL) { secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); return 0; } @@ -397,20 +422,30 @@ static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callba } secp256k1_gej_set_ge(&points[i], &point); } - secp256k1_ecmult_strauss_wnaf(&state, r, n_points, points, scalars, inp_g_sc); + + secp256k1_ecmult_strauss_batch_internal(error_callback, scratch, r, scalars, points, inp_g_sc, n_points); secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); return 1; } -/* Wrapper for secp256k1_ecmult_multi_func interface */ -static int secp256k1_ecmult_strauss_batch_single(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n) { - return secp256k1_ecmult_strauss_batch(error_callback, scratch, r, inp_g_sc, cb, cbdata, n, 0); +/** Return the scratch size that is allocated by a call to strauss_batch + * (ignoring padding required for alignment). */ +static size_t secp256k1_strauss_scratch_size(size_t n_points) { + static const size_t point_size = (sizeof(secp256k1_ge) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar); + return n_points*point_size; } +/** Return the maximum number of points that can be provided to strauss_batch + * with a given scratch space. */ static size_t secp256k1_strauss_max_points(const secp256k1_callback* error_callback, secp256k1_scratch *scratch) { return secp256k1_scratch_max_allocation(error_callback, scratch, STRAUSS_SCRATCH_OBJECTS) / secp256k1_strauss_scratch_size(1); } +/* Wrapper for secp256k1_ecmult_multi_func interface */ +static int secp256k1_ecmult_strauss_batch_single(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n) { + return secp256k1_ecmult_strauss_batch(error_callback, scratch, r, inp_g_sc, cb, cbdata, n, 0); +} + /** Convert a number to WNAF notation. * The number becomes represented by sum(2^{wi} * wnaf[i], i=0..WNAF_SIZE(w)+1) - return_val. * It has the following guarantees: diff --git a/src/modules/batch/main_impl.h b/src/modules/batch/main_impl.h index 08240736ba..408daec4da 100644 --- a/src/modules/batch/main_impl.h +++ b/src/modules/batch/main_impl.h @@ -45,6 +45,14 @@ static size_t secp256k1_batch_scratch_size(int max_terms) { return ret; } +/** Clears the scalar and points allocated on the batch object's scratch space */ +static void secp256k1_batch_scratch_clear(secp256k1_batch* batch) { + secp256k1_scalar_clear(&batch->sc_g); + /* setting the len = 0 will suffice (instead of clearing the memory) + * since, there are no secrets stored on the scratch space */ + batch->len = 0; +} + /** Allocates space for `batch->capacity` number of scalars and points on batch * object's scratch space */ static int secp256k1_batch_scratch_alloc(const secp256k1_callback* error_callback, secp256k1_batch* batch) { @@ -138,4 +146,40 @@ void secp256k1_batch_destroy(const secp256k1_context *ctx, secp256k1_batch *batc } } +/** verifies the inputs (schnorrsig or tweak_check) by performing multi-scalar point + * multiplication on the scalars (`batch->scalars`) and points (`batch->points`) + * present in the batch. Uses `secp256k1_ecmult_strauss_batch_internal` to perform + * the multi-multiplication. + * + * Fails if: + * 0 != -(s1 + a2*s2 + ... + au*su)G + * + R1 + a2*R2 + ... + au*Ru + e1*P1 + (a2*e2)P2 + ... + (au*eu)Pu. + */ +int secp256k1_batch_verify(const secp256k1_context *ctx, secp256k1_batch *batch) { + secp256k1_gej resj; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(batch != NULL); + + if(batch->result == 0) { + return 0; + } + + if (batch->len > 0) { + int strauss_ret = secp256k1_ecmult_strauss_batch_internal(&ctx->error_callback, batch->data, &resj, batch->scalars, batch->points, &batch->sc_g, batch->len); + int mid_res = secp256k1_gej_is_infinity(&resj); + + /* `_strauss_batch_internal` should not fail due to insufficient memory. + * `batch_create` will allocate memeory needed by `_strauss_batch_internal`. */ + VERIFY_CHECK(strauss_ret != 0); + /* Silence −Wunused-variable when VERIFY is off */ + (void)strauss_ret; + + batch->result = batch->result && mid_res; + secp256k1_batch_scratch_clear(batch); + } + + return batch->result; +} + #endif /* SECP256K1_MODULE_BATCH_MAIN_H */ From f818fc0e4b12cefc7f233d6d8aff63f5bb149a99 Mon Sep 17 00:00:00 2001 From: siv2r Date: Mon, 30 Jun 2025 22:09:23 +0530 Subject: [PATCH 4/9] batch: Add batch_add_* APIs This commit adds the batch APIs: 1. batch_add_schnorrsig Adds a Schnorr signature to the batch. 2. batch_add_xonlypub_tweak_check Adds a tweaked x-only pubkey check to the batch. 3. batch_usable Checks if a batch can be used by _batch_add_* APIs. **Side Note:** Exposing batch_add_schnorrsig in the secp256k1_schnorrsig.h header file (with batch module header guards) will force the user to define ENABLE_MODULE_BATCH during their code compilation. Hence, it is in a standalone secp256k1_schnorrsig_batch.h header file. A similar argument could be made for batch_add_xonlypub_tweak_check. --- include/secp256k1_batch.h | 25 ++++ include/secp256k1_schnorrsig_batch.h | 42 ++++++ include/secp256k1_tweak_check_batch.h | 50 +++++++ src/CMakeLists.txt | 6 + src/modules/batch/main_impl.h | 15 ++ src/modules/extrakeys/Makefile.am.include | 6 + src/modules/extrakeys/batch_add_impl.h | 151 ++++++++++++++++++++ src/modules/schnorrsig/Makefile.am.include | 6 + src/modules/schnorrsig/batch_add_impl.h | 158 +++++++++++++++++++++ src/secp256k1.c | 6 + 10 files changed, 465 insertions(+) create mode 100644 include/secp256k1_schnorrsig_batch.h create mode 100644 include/secp256k1_tweak_check_batch.h create mode 100644 src/modules/extrakeys/batch_add_impl.h create mode 100644 src/modules/schnorrsig/batch_add_impl.h diff --git a/include/secp256k1_batch.h b/include/secp256k1_batch.h index 0f1de05c32..419dcde85d 100644 --- a/include/secp256k1_batch.h +++ b/include/secp256k1_batch.h @@ -63,6 +63,31 @@ SECP256K1_API void secp256k1_batch_destroy( secp256k1_batch* batch ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); +/** Checks if a batch can be used by the `secp256k1_batch_add_*` APIs. + * + * Returns: 1: batch can be used by `secp256k1_batch_add_*` APIs. + * 0: batch cannot be used by `secp256k1_batch_add_*` APIs. + * + * Args: ctx: a secp256k1 context object (can be initialized for none). + * batch: a secp256k1 batch object that contains a set of schnorrsigs/tweaks. + * + * You are advised to check if `secp256k1_batch_usable` returns 1 before calling + * any `secp256k1_batch_add_*` API. We recommend this because `secp256k1_batch_add_*` + * will fail in two cases: + * - case 1: unparsable input (schnorrsig or tweak check) + * - case 2: unusable (or invalid) batch + * Calling `secp256k1_batch_usable` beforehand helps eliminate case 2 if + * `secp256k1_batch_add_*` fails. + * + * If you ignore the above advice, all the secp256k1_batch APIs will still + * work correctly. It simply makes it hard to understand the reason behind + * `secp256k1_batch_add_*` failure (if occurs). + */ +SECP256K1_API int secp256k1_batch_usable( + const secp256k1_context *ctx, + const secp256k1_batch *batch +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + /** Verify the set of schnorr signatures or tweaked pubkeys present in the secp256k1_batch. * * Returns: 1: every schnorrsig/tweak (in batch) is valid diff --git a/include/secp256k1_schnorrsig_batch.h b/include/secp256k1_schnorrsig_batch.h new file mode 100644 index 0000000000..ffd8399ee7 --- /dev/null +++ b/include/secp256k1_schnorrsig_batch.h @@ -0,0 +1,42 @@ +#ifndef SECP256K1_SCHNORRSIG_BATCH_H +#define SECP256K1_SCHNORRSIG_BATCH_H + +#include "secp256k1.h" +#include "secp256k1_schnorrsig.h" +#include "secp256k1_batch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** This header file implements batch verification functionality for Schnorr + * signature (see include/secp256k1_schnorrsig.h). + */ + +/** Adds a Schnorr signature to the batch object (secp256k1_batch) + * defined in the Batch module (see include/secp256k1_batch.h). + * + * Returns: 1: successfully added the signature to the batch + * 0: unparseable signature or unusable batch (according to + * secp256k1_batch_usable). + * Args: ctx: a secp256k1 context object (can be initialized for none). + * batch: a secp256k1 batch object created using `secp256k1_batch_create`. + * In: sig64: pointer to the 64-byte signature to verify. + * msg: the message being verified. Can only be NULL if msglen is 0. + * msglen: length of the message. + * pubkey: pointer to an x-only public key to verify with (cannot be NULL). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_batch_add_schnorrsig( + const secp256k1_context* ctx, + secp256k1_batch *batch, + const unsigned char *sig64, + const unsigned char *msg, + size_t msglen, + const secp256k1_xonly_pubkey *pubkey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(6); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_SCHNORRSIG_BATCH_H */ diff --git a/include/secp256k1_tweak_check_batch.h b/include/secp256k1_tweak_check_batch.h new file mode 100644 index 0000000000..4ae9027fa4 --- /dev/null +++ b/include/secp256k1_tweak_check_batch.h @@ -0,0 +1,50 @@ +#ifndef SECP256K1_TWEAK_CHECK_BATCH_H +#define SECP256K1_TWEAK_CHECK_BATCH_H + +#include "secp256k1.h" +#include "secp256k1_extrakeys.h" +#include "secp256k1_batch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** This header file implements batch verification functionality for + * x-only tweaked public key check (see include/secp256k1_extrakeys.h). + */ + +/** Adds a x-only tweaked pubkey check to the batch object (secp256k1_batch) + * defined in the Batch module (see include/secp256k1_batch.h). + * + * The tweaked pubkey is represented by its 32-byte x-only serialization and + * its pk_parity, which can both be obtained by converting the result of + * tweak_add to a secp256k1_xonly_pubkey. + * + * Returns: 1: successfully added the tweaked pubkey check to the batch + * 0: unparseable tweaked pubkey check or unusable batch (according to + * secp256k1_batch_usable). + * Args: ctx: pointer to a context object initialized for verification. + * batch: a secp256k1 batch object created using `secp256k1_batch_create`. + * In: tweaked_pubkey32: pointer to a serialized xonly_pubkey. + * tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization + * is passed in as tweaked_pubkey32). This must match the + * pk_parity value that is returned when calling + * secp256k1_xonly_pubkey_from_pubkey with the tweaked pubkey, or + * the final secp256k1_batch_verify on this batch will fail. + * internal_pubkey: pointer to an x-only public key object to apply the tweak to. + * tweak32: pointer to a 32-byte tweak. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_batch_add_xonlypub_tweak_check( + const secp256k1_context* ctx, + secp256k1_batch *batch, + const unsigned char *tweaked_pubkey32, + int tweaked_pk_parity, + const secp256k1_xonly_pubkey *internal_pubkey, + const unsigned char *tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_TWEAK_CHECK_BATCH_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2624050d0..d2ea39525f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,11 +33,17 @@ if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON) add_compile_definitions(ENABLE_MODULE_SCHNORRSIG=1) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig.h) + if(SECP256K1_ENABLE_MODULE_BATCH) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig_batch.h) + endif() endif() if(SECP256K1_ENABLE_MODULE_EXTRAKEYS) add_compile_definitions(ENABLE_MODULE_EXTRAKEYS=1) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_extrakeys.h) + if(SECP256K1_ENABLE_MODULE_BATCH) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_tweak_check_batch.h) + endif() endif() if(SECP256K1_ENABLE_MODULE_RECOVERY) diff --git a/src/modules/batch/main_impl.h b/src/modules/batch/main_impl.h index 408daec4da..e831e1200b 100644 --- a/src/modules/batch/main_impl.h +++ b/src/modules/batch/main_impl.h @@ -3,6 +3,14 @@ #include "../../../include/secp256k1_batch.h" +/* Assume two batch objects (batch1 and batch2) and we call + * `batch_add_tweak_check` on batch1 and `batch_add_schnorrsig` on batch2. + * In this case, the same randomizer will be generated if the input bytes to + * batch1 and batch2 are the same (even though we use different `batch_add_` funcs). + * Including this tag during randomizer generation (to differentiate btw + * `batch_add_` funcs) will prevent such mishaps. */ +enum batch_add_type {schnorrsig = 1, tweak_check = 2}; + /** Opaque data structure that holds information required for the batch verification. * * Members: @@ -146,6 +154,13 @@ void secp256k1_batch_destroy(const secp256k1_context *ctx, secp256k1_batch *batc } } +int secp256k1_batch_usable(const secp256k1_context *ctx, const secp256k1_batch *batch) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(batch != NULL); + + return batch->result; +} + /** verifies the inputs (schnorrsig or tweak_check) by performing multi-scalar point * multiplication on the scalars (`batch->scalars`) and points (`batch->points`) * present in the batch. Uses `secp256k1_ecmult_strauss_batch_internal` to perform diff --git a/src/modules/extrakeys/Makefile.am.include b/src/modules/extrakeys/Makefile.am.include index 0d901ec1f4..037a417491 100644 --- a/src/modules/extrakeys/Makefile.am.include +++ b/src/modules/extrakeys/Makefile.am.include @@ -1,4 +1,10 @@ include_HEADERS += include/secp256k1_extrakeys.h +if ENABLE_MODULE_BATCH +include_HEADERS += include/secp256k1_tweak_check_batch.h +endif noinst_HEADERS += src/modules/extrakeys/tests_impl.h noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h noinst_HEADERS += src/modules/extrakeys/main_impl.h +if ENABLE_MODULE_BATCH +noinst_HEADERS += src/modules/extrakeys/batch_add_impl.h +endif \ No newline at end of file diff --git a/src/modules/extrakeys/batch_add_impl.h b/src/modules/extrakeys/batch_add_impl.h new file mode 100644 index 0000000000..f8faf31d7c --- /dev/null +++ b/src/modules/extrakeys/batch_add_impl.h @@ -0,0 +1,151 @@ +#ifndef SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_IMPL_H +#define SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_IMPL_H + +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_tweak_check_batch.h" +#include "../batch/main_impl.h" + +/* The number of scalar-point pairs allocated on the scratch space + * by `secp256k1_batch_add_xonlypub_tweak_check` */ +#define BATCH_TWEAK_CHECK_SCRATCH_OBJS 1 + +/** Computes a 16-byte deterministic randomizer by + * SHA256(batch_add_tag || tweaked pubkey || parity || tweak || internal pubkey) */ +static void secp256k1_batch_xonlypub_tweak_randomizer_gen(unsigned char *randomizer32, secp256k1_sha256 *sha256, const unsigned char *tweaked_pubkey32, const unsigned char *tweaked_pk_parity, const unsigned char *internal_pk33, const unsigned char *tweak32) { + secp256k1_sha256 sha256_cpy; + unsigned char batch_add_type = (unsigned char) tweak_check; + + secp256k1_sha256_write(sha256, &batch_add_type, sizeof(batch_add_type)); + /* add tweaked pubkey check data to sha object */ + secp256k1_sha256_write(sha256, tweaked_pubkey32, 32); + secp256k1_sha256_write(sha256, tweaked_pk_parity, 1); + secp256k1_sha256_write(sha256, tweak32, 32); + secp256k1_sha256_write(sha256, internal_pk33, 33); + + /* generate randomizer */ + sha256_cpy = *sha256; + secp256k1_sha256_finalize(&sha256_cpy, randomizer32); + /* 16 byte randomizer is sufficient */ + memset(randomizer32, 0, 16); +} + +static int secp256k1_batch_xonlypub_tweak_randomizer_set(const secp256k1_context* ctx, secp256k1_batch *batch, secp256k1_scalar *r, const unsigned char *tweaked_pubkey32, int tweaked_pk_parity, const secp256k1_xonly_pubkey *internal_pubkey,const unsigned char *tweak32) { + unsigned char randomizer[32]; + unsigned char internal_buf[33]; + size_t internal_buflen = sizeof(internal_buf); + unsigned char parity = (unsigned char) tweaked_pk_parity; + int overflow; + /* t = 2^127 */ + secp256k1_scalar t = SECP256K1_SCALAR_CONST(0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000); + + /* We use compressed serialization here. If we would use + * xonly_pubkey serialization and a user would wrongly memcpy + * normal secp256k1_pubkeys into xonly_pubkeys then the randomizer + * would be the same for two different pubkeys. */ + if (!secp256k1_ec_pubkey_serialize(ctx, internal_buf, &internal_buflen, (const secp256k1_pubkey *) internal_pubkey, SECP256K1_EC_COMPRESSED)) { + return 0; + } + + secp256k1_batch_xonlypub_tweak_randomizer_gen(randomizer, &batch->sha256, tweaked_pubkey32, &parity, internal_buf, tweak32); + secp256k1_scalar_set_b32(r, randomizer, &overflow); + /* Shift scalar to range [-2^127, 2^127-1] */ + secp256k1_scalar_negate(&t, &t); + secp256k1_scalar_add(r, r, &t); + VERIFY_CHECK(overflow == 0); + + return 1; +} + +/** Adds the given x-only tweaked public key check to the batch. + * + * Updates the batch object by: + * 1. adding the point P-Q to the scratch space + * -> the point is of type `secp256k1_gej` + * 2. adding the scalar ai to the scratch space + * -> ai is the scalar coefficient of P-Q (in multi multiplication) + * 3. incrementing sc_g (scalar of G) by ai.tweak + * + * Conventions used above: + * -> Q (tweaked pubkey) = EC point where parity(y) = tweaked_pk_parity + * and x = tweaked_pubkey32 + * -> P (internal pubkey) = internal pubkey + * -> ai (randomizer) = sha256_tagged(batch_add_tag || tweaked_pubkey32 || + * tweaked_pk_parity || tweak32 || pubkey) + * -> tweak (challenge) = tweak32 + * + * This function is based on `secp256k1_xonly_pubkey_tweak_add_check`. + */ +int secp256k1_batch_add_xonlypub_tweak_check(const secp256k1_context* ctx, secp256k1_batch *batch, const unsigned char *tweaked_pubkey32, int tweaked_pk_parity, const secp256k1_xonly_pubkey *internal_pubkey,const unsigned char *tweak32) { + secp256k1_scalar tweak; + secp256k1_scalar ai; + secp256k1_ge pk; + secp256k1_ge q; + secp256k1_gej tmpj; + secp256k1_fe qx; + int overflow; + size_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(batch != NULL); + ARG_CHECK(internal_pubkey != NULL); + ARG_CHECK(tweaked_pubkey32 != NULL); + ARG_CHECK(tweak32 != NULL); + + if(batch->result == 0) { + return 0; + } + + if (!secp256k1_fe_set_b32_limit(&qx, tweaked_pubkey32)) { + return 0; + } + + secp256k1_scalar_set_b32(&tweak, tweak32, &overflow); + if (overflow) { + return 0; + } + + if (!secp256k1_xonly_pubkey_load(ctx, &pk, internal_pubkey)) { + return 0; + } + + /* if insufficient space in batch, verify the inputs (stored in curr batch) and + * save the result. This extends the batch capacity since `secp256k1_batch_verify` + * clears the batch after verification. */ + if (batch->capacity - batch->len < BATCH_TWEAK_CHECK_SCRATCH_OBJS) { + secp256k1_batch_verify(ctx, batch); + } + + i = batch->len; + /* append point P-Q to the scratch space */ + if (!secp256k1_ge_set_xo_var(&q, &qx, tweaked_pk_parity)) { + return 0; + } + if (!secp256k1_ge_is_in_correct_subgroup(&q)) { + return 0; + } + secp256k1_ge_neg(&q, &q); + secp256k1_gej_set_ge(&tmpj, &q); + secp256k1_gej_add_ge_var(&tmpj, &tmpj, &pk, NULL); + batch->points[i] = tmpj; + + /* Compute ai (randomizer) */ + if (batch->len == 0) { + /* set randomizer as 1 for the first term in batch */ + ai = secp256k1_scalar_one; + } else if(!secp256k1_batch_xonlypub_tweak_randomizer_set(ctx, batch, &ai, tweaked_pubkey32, tweaked_pk_parity, internal_pubkey, tweak32)) { + return 0; + } + + /* append scalar ai to scratch space */ + batch->scalars[i] = ai; + + /* increment scalar of G by ai.tweak */ + secp256k1_scalar_mul(&tweak, &tweak, &ai); + secp256k1_scalar_add(&batch->sc_g, &batch->sc_g, &tweak); + + batch->len += 1; + + return 1; +} + +#endif /* SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_IMPL_H */ diff --git a/src/modules/schnorrsig/Makefile.am.include b/src/modules/schnorrsig/Makefile.am.include index 654fa2e5ae..03fc15aa5b 100644 --- a/src/modules/schnorrsig/Makefile.am.include +++ b/src/modules/schnorrsig/Makefile.am.include @@ -1,5 +1,11 @@ include_HEADERS += include/secp256k1_schnorrsig.h +if ENABLE_MODULE_BATCH +include_HEADERS += include/secp256k1_schnorrsig_batch.h +endif noinst_HEADERS += src/modules/schnorrsig/main_impl.h noinst_HEADERS += src/modules/schnorrsig/tests_impl.h noinst_HEADERS += src/modules/schnorrsig/tests_exhaustive_impl.h noinst_HEADERS += src/modules/schnorrsig/bench_impl.h +if ENABLE_MODULE_BATCH +noinst_HEADERS += src/modules/schnorrsig/batch_add_impl.h +endif diff --git a/src/modules/schnorrsig/batch_add_impl.h b/src/modules/schnorrsig/batch_add_impl.h new file mode 100644 index 0000000000..7c3da4a29d --- /dev/null +++ b/src/modules/schnorrsig/batch_add_impl.h @@ -0,0 +1,158 @@ +#ifndef SECP256K1_MODULE_SCHNORRSIG_BATCH_ADD_IMPL_H +#define SECP256K1_MODULE_SCHNORRSIG_BATCH_ADD_IMPL_H + +#include "../../../include/secp256k1_schnorrsig.h" +#include "../../../include/secp256k1_schnorrsig_batch.h" +#include "../batch/main_impl.h" + +/* The number of scalar-point pairs allocated on the scratch space + * by `secp256k1_batch_add_schnorrsig` */ +#define BATCH_SCHNORRSIG_SCRATCH_OBJS 2 + +/** Computes a 16-byte deterministic randomizer by + * SHA256(batch_add_tag || sig || msg || compressed pubkey) */ +static void secp256k1_batch_schnorrsig_randomizer_gen(unsigned char *randomizer32, secp256k1_sha256 *sha256, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *compressed_pk33) { + secp256k1_sha256 sha256_cpy; + unsigned char batch_add_type = (unsigned char) schnorrsig; + + secp256k1_sha256_write(sha256, &batch_add_type, sizeof(batch_add_type)); + /* add schnorrsig data to sha256 object */ + secp256k1_sha256_write(sha256, sig64, 64); + secp256k1_sha256_write(sha256, msg, msglen); + secp256k1_sha256_write(sha256, compressed_pk33, 33); + + /* generate randomizer */ + sha256_cpy = *sha256; + secp256k1_sha256_finalize(&sha256_cpy, randomizer32); + /* 16 byte randomizer is sufficient */ + memset(randomizer32, 0, 16); +} + +static int secp256k1_batch_schnorrsig_randomizer_set(const secp256k1_context *ctx, secp256k1_batch *batch, secp256k1_scalar *r, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey) { + unsigned char randomizer[32]; + unsigned char buf[33]; + size_t buflen = sizeof(buf); + int overflow; + /* t = 2^127 */ + secp256k1_scalar t = SECP256K1_SCALAR_CONST(0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000); + + /* We use compressed serialization here. If we would use + * xonly_pubkey serialization and a user would wrongly memcpy + * normal secp256k1_pubkeys into xonly_pubkeys then the randomizer + * would be the same for two different pubkeys. */ + if (!secp256k1_ec_pubkey_serialize(ctx, buf, &buflen, (const secp256k1_pubkey *) pubkey, SECP256K1_EC_COMPRESSED)) { + return 0; + } + + secp256k1_batch_schnorrsig_randomizer_gen(randomizer, &batch->sha256, sig64, msg, msglen, buf); + secp256k1_scalar_set_b32(r, randomizer, &overflow); + /* Shift scalar to range [-2^127, 2^127-1] */ + secp256k1_scalar_negate(&t, &t); + secp256k1_scalar_add(r, r, &t); + VERIFY_CHECK(overflow == 0); + + return 1; +} + +/** Adds the given schnorr signature to the batch. + * + * Updates the batch object by: + * 1. adding the points R and P to the scratch space + * -> both the points are of type `secp256k1_gej` + * 2. adding the scalars ai and ai.e to the scratch space + * -> ai is the scalar coefficient of R (in multi multiplication) + * -> ai.e is the scalar coefficient of P (in multi multiplication) + * 3. incrementing sc_g (scalar of G) by -ai.s + * + * Conventions used above: + * -> R (nonce commitment) = EC point whose y = even and x = sig64[0:32] + * -> P (public key) = pubkey + * -> ai (randomizer) = sha256_tagged(batch_add_tag || sig64 || msg || pubkey) + * -> e (challenge) = sha256_tagged(sig64[0:32] || pk.x || msg) + * -> s = sig64[32:64] + * + * This function is based on `secp256k1_schnorrsig_verify`. + */ +int secp256k1_batch_add_schnorrsig(const secp256k1_context* ctx, secp256k1_batch *batch, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey) { + secp256k1_scalar s; + secp256k1_scalar e; + secp256k1_scalar ai; + secp256k1_ge pk; + secp256k1_fe rx; + secp256k1_ge r; + unsigned char buf[32]; + int overflow; + size_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(batch != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(msg != NULL || msglen == 0); + ARG_CHECK(pubkey != NULL); + + if (batch->result == 0) { + return 0; + } + + if (!secp256k1_fe_set_b32_limit(&rx, &sig64[0])) { + return 0; + } + + secp256k1_scalar_set_b32(&s, &sig64[32], &overflow); + if (overflow) { + return 0; + } + + if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) { + return 0; + } + + /* if insufficient space in batch, verify the inputs (stored in curr batch) and + * save the result. This extends the batch capacity since `secp256k1_batch_verify` + * clears the batch after verification. */ + if (batch->capacity - batch->len < BATCH_SCHNORRSIG_SCRATCH_OBJS) { + secp256k1_batch_verify(ctx, batch); + } + + i = batch->len; + /* append point R to the scratch space */ + if (!secp256k1_ge_set_xo_var(&r, &rx, 0)) { + return 0; + } + if (!secp256k1_ge_is_in_correct_subgroup(&r)) { + return 0; + } + secp256k1_gej_set_ge(&batch->points[i], &r); + + /* append point P to the scratch space */ + secp256k1_gej_set_ge(&batch->points[i+1], &pk); + + /* compute e (challenge) */ + secp256k1_fe_get_b32(buf, &pk.x); + secp256k1_schnorrsig_challenge(&e, &sig64[0], msg, msglen, buf); + + /* compute ai (randomizer) */ + if (batch->len == 0) { + /* don't generate a randomizer for the first term in the batch to improve + * the computation speed. hence, set the randomizer to 1. */ + ai = secp256k1_scalar_one; + } else if (!secp256k1_batch_schnorrsig_randomizer_set(ctx, batch, &ai, sig64, msg, msglen, pubkey)) { + return 0; + } + + /* append scalars ai and ai.e to scratch space (order shouldn't change) */ + batch->scalars[i] = ai; + secp256k1_scalar_mul(&e, &e, &ai); + batch->scalars[i+1] = e; + + /* increment scalar of G by -ai.s */ + secp256k1_scalar_mul(&s, &s, &ai); + secp256k1_scalar_negate(&s, &s); + secp256k1_scalar_add(&batch->sc_g, &batch->sc_g, &s); + + batch->len += 2; + + return 1; +} + +#endif /* SECP256K1_MODULE_SCHNORRSIG_BATCH_ADD_IMPL_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index 9f5b119562..9bc26d050f 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -820,4 +820,10 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_BATCH # include "modules/batch/main_impl.h" +# ifdef ENABLE_MODULE_EXTRAKEYS +# include "modules/extrakeys/batch_add_impl.h" +# endif +# ifdef ENABLE_MODULE_SCHNORRSIG +# include "modules/schnorrsig/batch_add_impl.h" +# endif #endif From e5bbca63bf3120347f61dbf14431c8f9c5e5c325 Mon Sep 17 00:00:00 2001 From: siv2r Date: Mon, 30 Jun 2025 22:35:28 +0530 Subject: [PATCH 5/9] batch: Add example This commit adds an example C program using the batch API. GNU Autotools and CMake will compile this example only if both batch and schnorrsig modules are enabled. --- .gitignore | 1 + Makefile.am | 11 +++ examples/CMakeLists.txt | 4 + examples/batch.c | 181 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 examples/batch.c diff --git a/.gitignore b/.gitignore index ce33a84adf..389d33c016 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ ecdsa_example schnorr_example ellswift_example musig_example +batch_example *.exe *.so *.a diff --git a/Makefile.am b/Makefile.am index 90b06e403c..794bd7a7ee 100644 --- a/Makefile.am +++ b/Makefile.am @@ -206,6 +206,17 @@ if BUILD_WINDOWS musig_example_LDFLAGS += -lbcrypt endif TESTS += musig_example +if ENABLE_MODULE_BATCH +noinst_PROGRAMS += batch_example +batch_example_SOURCES = examples/batch.c +batch_example_CPPFLAGS = -I$(top_srcdir)/include +batch_example_LDADD = libsecp256k1.la +batch_example_LDFLAGS = -static +if BUILD_WINDOWS +batch_example_LDFLAGS += -lbcrypt +endif +TESTS += batch_example +endif endif endif diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c9da9de6be..b6c40fbe29 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if(SECP256K1_ENABLE_MODULE_MUSIG) add_example(musig) endif() + +if(SECP256K1_ENABLE_MODULE_BATCH AND SECP256K1_ENABLE_MODULE_SCHNORRSIG) + add_example(batch) +endif() diff --git a/examples/batch.c b/examples/batch.c new file mode 100644 index 0000000000..3e3e59c620 --- /dev/null +++ b/examples/batch.c @@ -0,0 +1,181 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "examples_util.h" + +/* key pair data */ +unsigned char sk[32]; +secp256k1_keypair keypair; +secp256k1_xonly_pubkey pk; + +/* schnorrsig verification data */ +#define N_SIGS 10 +unsigned char msg[N_SIGS][32]; +unsigned char sig[N_SIGS][64]; + +/* xonly pubkey tweak checks data */ +#define N_CHECKS 10 +unsigned char tweaked_pubkey[N_CHECKS][32]; +int tweaked_pk_parity[N_CHECKS]; +unsigned char tweak[N_CHECKS][32]; + +/* 2*N_SIGS since one schnorrsig creates two scalar-point pairs in batch + * whereas one tweak check creates one scalar-point pair in batch */ +#define N_TERMS (N_CHECKS + 2*N_SIGS) + +/* generate key pair required for sign and verify */ +int create_keypair(secp256k1_context *ctx) { + while(1) { + if (!fill_random(sk, sizeof(sk))) { + printf("Failed to generate randomness\n"); + return 1; + } + if (secp256k1_keypair_create(ctx, &keypair, sk)) { + break; + } + } + if (!secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)) { + return 0; + } + + return 1; +} + +/* create valid schnorrsigs for N_SIGS random messages */ +int generate_schnorrsigs(secp256k1_context *ctx) { + size_t i; + + for (i = 0; i < N_SIGS; i++) { + if(!fill_random(msg[i], sizeof(msg[i]))) { + printf("Failed to generate randomness\n"); + return 1; + } + assert(secp256k1_schnorrsig_sign32(ctx, sig[i], msg[i], &keypair, NULL)); + assert(secp256k1_schnorrsig_verify(ctx, sig[i], msg[i], sizeof(msg[i]), &pk)); + } + + return 1; +} + +/* create valid N_CHECKS number of xonly pukey tweak checks */ +int generate_xonlypub_tweak_checks(secp256k1_context *ctx) { + secp256k1_pubkey output_pk; + secp256k1_xonly_pubkey output_xonly_pk; + size_t i; + + for (i = 0; i < N_CHECKS; i++) { + if (!fill_random(tweak[i], sizeof(tweak[i]))) { + printf("Failed to generate randomness\n"); + return 1; + } + assert(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &pk, tweak[i])); + assert(secp256k1_xonly_pubkey_from_pubkey(ctx, &output_xonly_pk, &tweaked_pk_parity[i], &output_pk)); + assert(secp256k1_xonly_pubkey_serialize(ctx, tweaked_pubkey[i], &output_xonly_pk)); + assert(secp256k1_xonly_pubkey_tweak_add_check(ctx, tweaked_pubkey[i], tweaked_pk_parity[i], &pk, tweak[i])); + } + + return 1; +} + +int main(void) { + int ret; + size_t i; + /* batch object uses secp256k1_context only for the error callback function + * here, we create secp256k1_context that can sign and verify, only to generate + * input data (schnorrsigs, tweak checks) required for the batch */ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + secp256k1_batch *batch; + unsigned char auxiliary_rand[16]; + + /* Generate 16 bytes of randomness to use during batch creation. */ + if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) { + printf("Failed to generate randomness\n"); + return 1; + } + + batch = secp256k1_batch_create(ctx, N_TERMS, auxiliary_rand); + + assert(ctx != NULL); + assert(batch != NULL); + + /* key pair generation */ + printf("Creating a key pair........................."); + if(!create_keypair(ctx)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + + /* create schnorrsigs for N_SIGS random messages */ + printf("Signing messages............................"); + if(!generate_schnorrsigs(ctx)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + + printf("Adding signatures to the batch object......."); + for (i = 0; i < N_SIGS; i++) { + /* It is recommended to check the validity of the batch before adding a + * new input (schnorrsig/tweak check) to it. The `secp256k1_batch_add_` APIs + * won't add any new input to invalid batch since the final `secp256k1_batch_verify` + * API call will fail even if the new input is valid. */ + if(secp256k1_batch_usable(ctx, batch)) { + ret = secp256k1_batch_add_schnorrsig(ctx, batch, sig[i], msg[i], sizeof(msg[i]), &pk); + } else { + printf("INVALID BATCH\n"); + return 1; + } + + if(!ret) { + printf("FAILED\n"); + return 1; + } + } + printf("ok\n"); + + printf("Generating xonlypub tweak checks............"); + if(!generate_xonlypub_tweak_checks(ctx)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + + printf("Adding tweak checks to the batch object....."); + for (i = 0; i < N_CHECKS; i++) { + /* It is recommended to check the validity of the batch before adding a + * new input (schnorrsig/tweak check) to it. The `secp256k1_batch_add_` APIs + * won't add any new input to invalid batch since the final `secp256k1_batch_verify` + * API call will fail even if the new input is valid. */ + if(secp256k1_batch_usable(ctx, batch)) { + ret = secp256k1_batch_add_xonlypub_tweak_check(ctx, batch, tweaked_pubkey[i], tweaked_pk_parity[i], &pk, tweak[i]); + } else { + printf("INVALID BATCH\n"); + return 1; + } + + if(!ret) { + printf("FAILED\n"); + return 1; + } + } + printf("ok\n"); + + printf("Verifying the batch object.................."); + if(!secp256k1_batch_verify(ctx, batch)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + + secp256k1_batch_destroy(ctx, batch); + secp256k1_context_destroy(ctx); + + return 0; +} From 6378b6bcdc58e423fa39cb5930bad886342d793c Mon Sep 17 00:00:00 2001 From: siv2r Date: Wed, 2 Jul 2025 16:34:24 +0530 Subject: [PATCH 6/9] batch,ecmult: Add tests for core batch APIs and strauss_batch refactor This commit adds the following tests: 1. GitHub workflow 2. Batch API tests (ordered) 3. Tagged SHA256 test 4. BIP340 test vectors: https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv 5. Large random test for `strauss_batch` refactor --- .github/workflows/ci.yml | 47 +++++--- ci/ci.sh | 3 +- src/modules/batch/Makefile.am.include | 3 +- src/modules/batch/tests_impl.h | 126 ++++++++++++++++++++ src/modules/schnorrsig/tests_impl.h | 87 +++++++++++++- src/tests.c | 161 ++++++++++++++++++++------ 6 files changed, 376 insertions(+), 51 deletions(-) create mode 100644 src/modules/batch/tests_impl.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3108d6bb1..7088ee267a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ env: SCHNORRSIG: 'no' MUSIG: 'no' ELLSWIFT: 'no' + BATCH: 'no' ### test options SECP256K1_TEST_ITERS: 64 BENCH: 'yes' @@ -74,18 +75,18 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } + - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } - - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } - env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 } - env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 } cc: @@ -132,6 +133,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CC: ${{ matrix.cc }} steps: @@ -164,6 +167,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' steps: @@ -204,6 +209,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' steps: @@ -237,6 +244,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' strategy: @@ -280,6 +289,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' steps: @@ -321,6 +332,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' SECP256K1_TEST_ITERS: 2 @@ -360,6 +373,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' CFLAGS: '-fsanitize=undefined,address -g' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' @@ -413,6 +428,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CC: 'clang' SECP256K1_TEST_ITERS: 32 ASM: 'no' @@ -449,6 +466,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' CTIMETESTS: 'no' strategy: @@ -492,15 +511,15 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', BATCH: 'yes', EXPERIMENTAL: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' steps: @@ -669,6 +688,8 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + BATCH: 'yes' + EXPERIMENTAL: 'yes' steps: - name: Checkout diff --git a/ci/ci.sh b/ci/ci.sh index 3285ecc951..8f171c60db 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -13,7 +13,7 @@ print_environment() { # does not rely on bash. for var in WERROR_CFLAGS MAKEFLAGS BUILD \ ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ - EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT \ + EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT BATCH \ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \ EXAMPLES \ HOST WRAPPER_CMD \ @@ -80,6 +80,7 @@ esac --enable-module-extrakeys="$EXTRAKEYS" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-musig="$MUSIG" \ + --enable-module-batch="$BATCH" --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ diff --git a/src/modules/batch/Makefile.am.include b/src/modules/batch/Makefile.am.include index ddad7594fb..acb939b805 100644 --- a/src/modules/batch/Makefile.am.include +++ b/src/modules/batch/Makefile.am.include @@ -1,2 +1,3 @@ include_HEADERS += include/secp256k1_batch.h -noinst_HEADERS += src/modules/batch/main_impl.h \ No newline at end of file +noinst_HEADERS += src/modules/batch/main_impl.h +noinst_HEADERS += src/modules/batch/tests_impl.h \ No newline at end of file diff --git a/src/modules/batch/tests_impl.h b/src/modules/batch/tests_impl.h new file mode 100644 index 0000000000..b8cc9182a9 --- /dev/null +++ b/src/modules/batch/tests_impl.h @@ -0,0 +1,126 @@ +#ifndef SECP256K1_MODULE_BATCH_TESTS_H +#define SECP256K1_MODULE_BATCH_TESTS_H + +#include "../../../include/secp256k1_batch.h" +#ifdef ENABLE_MODULE_SCHNORRSIG +#include "../../../include/secp256k1_schnorrsig.h" +#include "../../../include/secp256k1_schnorrsig_batch.h" +#endif +#ifdef ENABLE_MODULE_EXTRAKEYS +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_tweak_check_batch.h" +#endif + +/* Tests for the equality of two sha256 structs. This function only produces a + * correct result if an integer multiple of 64 many bytes have been written + * into the hash functions. */ +void test_batch_sha256_eq(const secp256k1_sha256 *sha1, const secp256k1_sha256 *sha2) { + /* Is buffer fully consumed? */ + CHECK((sha1->bytes & 0x3F) == 0); + + CHECK(sha1->bytes == sha2->bytes); + CHECK(secp256k1_memcmp_var(sha1->s, sha2->s, sizeof(sha1->s)) == 0); +} + +/* Checks that hash initialized by secp256k1_batch_sha256_tagged has the + * expected state. */ +void test_batch_sha256_tagged(void) { + unsigned char tag[] = {'B', 'I', 'P', '0', '3', '4', '0', '/', 'b', 'a', 't', 'c', 'h'}; + secp256k1_sha256 sha; + secp256k1_sha256 sha_optimized; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char *) tag, sizeof(tag)); + secp256k1_batch_sha256_tagged(&sha_optimized); + test_batch_sha256_eq(&sha, &sha_optimized); +} + +#define N_SIGS 10 +#define N_TWK_CHECKS 10 +#define N_TERMS (N_TWK_CHECKS + 2*N_SIGS) +void test_batch_api(void) { + +#ifdef ENABLE_MODULE_EXTRAKEYS + unsigned char sk[32]; + secp256k1_keypair keypair; + secp256k1_xonly_pubkey pk; + /* xonly pubkey tweak checks data */ + unsigned char tweaked_pk[N_TWK_CHECKS][32]; + int tweaked_pk_parity[N_TWK_CHECKS]; + unsigned char tweak[N_TWK_CHECKS][32]; + secp256k1_pubkey tmp_pk; + secp256k1_xonly_pubkey tmp_xonly_pk; + size_t i; +#endif + +#ifdef ENABLE_MODULE_SCHNORRSIG + /* schnorr verification data */ + unsigned char msg[N_SIGS][32]; + unsigned char sig[N_SIGS][64]; +#endif + /* batch object setup */ + secp256k1_batch *batch; + unsigned char aux_rand16[32]; + + /* 16 byte auxiliary randomness */ + testrand256(aux_rand16); + memset(&aux_rand16[16], 0, 16); + + /** main test body **/ + /* batch_create tests */ + batch = secp256k1_batch_create(CTX, N_TERMS, aux_rand16); + CHECK(batch != NULL); + /* ARG_CHECK(max_terms != 0) in `batch_create` should fail*/ + CHECK_ILLEGAL(CTX, secp256k1_batch_create(CTX, 0, NULL)); + +#ifdef ENABLE_MODULE_EXTRAKEYS + /* generate keypair data */ + testrand256(sk); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair) == 1); + + /* generate N_TWK_CHECKS tweak check data (tweaked_pk, tweaked_pk_parity, tweak) */ + for (i = 0; i < N_TWK_CHECKS; i++) { + testrand256(tweak[i]); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &tmp_pk, &pk, tweak[i])); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &tmp_xonly_pk, &tweaked_pk_parity[i], &tmp_pk)); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, tweaked_pk[i], &tmp_xonly_pk)); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, tweaked_pk[i], tweaked_pk_parity[i], &pk, tweak[i])); + } + /* add tweak checks to the batch object */ + for (i = 0; i < N_TWK_CHECKS; i++) { + CHECK(secp256k1_batch_usable(CTX, batch)); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk[i], tweaked_pk_parity[i], &pk, tweak[i])); + } +#endif + +#ifdef ENABLE_MODULE_SCHNORRSIG + /* generate N_SIGS schnorr signatures */ + for (i = 0; i < N_SIGS; i++) { + testrand256(msg[i]); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[i], msg[i], &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[i], msg[i], sizeof(msg[i]), &pk)); + } + /* add schnorrsigs to the batch object */ + for (i = 0; i < N_SIGS; i++) { + CHECK(secp256k1_batch_usable(CTX, batch) == 1); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch, sig[i], msg[i], sizeof(msg[i]), &pk) == 1); + } +#endif + + /* batch_verify tests */ + CHECK(secp256k1_batch_verify(CTX, batch) == 1); + CHECK_ILLEGAL(CTX, secp256k1_batch_verify(CTX, NULL)); + + secp256k1_batch_destroy(CTX, batch); +} +#undef N_SIGS +#undef N_TWK_CHECKS +#undef N_TERMS + + +void run_batch_tests(void) { + test_batch_api(); + test_batch_sha256_tagged(); +} + +#endif /* SECP256K1_MODULE_BATCH_TESTS_H */ diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 2d716a01f8..8bce479ad7 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -8,6 +8,10 @@ #define SECP256K1_MODULE_SCHNORRSIG_TESTS_H #include "../../../include/secp256k1_schnorrsig.h" +#ifdef ENABLE_MODULE_BATCH +# include "../../../include/secp256k1_batch.h" +# include "../../../include/secp256k1_schnorrsig_batch.h" +#endif /* Checks that a bit flip in the n_flip-th argument (that has n_bytes many * bytes) changes the hash function @@ -193,7 +197,7 @@ static void test_schnorrsig_bip_vectors_check_signing(const unsigned char *sk, c } /* Helper function for schnorrsig_bip_vectors - * Checks that both verify and verify_batch (TODO) return the same value as expected. */ + * Checks that schnorrsig_verify return the same value as expected. */ static void test_schnorrsig_bip_vectors_check_verify(const unsigned char *pk_serialized, const unsigned char *msg, size_t msglen, const unsigned char *sig, int expected) { secp256k1_xonly_pubkey pk; @@ -201,6 +205,23 @@ static void test_schnorrsig_bip_vectors_check_verify(const unsigned char *pk_ser CHECK(expected == secp256k1_schnorrsig_verify(CTX, sig, msg, msglen, &pk)); } +#ifdef ENABLE_MODULE_BATCH +/* Helper function for schnorrsig_bip_vectors + * Checks that batch_verify return the same value as expected. */ +static void test_schnorrsig_bip_vectors_check_batch_verify(const unsigned char *pk_serialized, const unsigned char *msg, size_t msglen, const unsigned char *sig, int add_expected, int verify_expected) { + secp256k1_xonly_pubkey pk; + secp256k1_batch *batch; + + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk, pk_serialized)); + batch = secp256k1_batch_create(CTX, 2, NULL); + CHECK(batch != NULL); + CHECK(secp256k1_batch_usable(CTX, batch) == 1); + CHECK(add_expected == secp256k1_batch_add_schnorrsig(CTX, batch, sig, msg, msglen, &pk)); + CHECK(verify_expected == secp256k1_batch_verify(CTX, batch)); + secp256k1_batch_destroy(CTX, batch); +} +#endif + /* Test vectors according to BIP-340 ("Schnorr Signatures for secp256k1"). See * https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv. */ static void test_schnorrsig_bip_vectors(void) { @@ -242,6 +263,9 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + #ifdef ENABLE_MODULE_BATCH + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 1); + #endif } { /* Test vector 1 */ @@ -281,6 +305,9 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + #ifdef ENABLE_MODULE_BATCH + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 1); + #endif } { /* Test vector 2 */ @@ -320,6 +347,9 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + #ifdef ENABLE_MODULE_BATCH + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 1); + #endif } { /* Test vector 3 */ @@ -359,6 +389,9 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + #ifdef ENABLE_MODULE_BATCH + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 1); + #endif } { /* Test vector 4 */ @@ -385,6 +418,9 @@ static void test_schnorrsig_bip_vectors(void) { 0x06, 0x0B, 0x07, 0xD2, 0x83, 0x08, 0xD7, 0xF4 }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + #ifdef ENABLE_MODULE_BATCH + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 1); + #endif } { /* Test vector 5 */ @@ -423,6 +459,12 @@ static void test_schnorrsig_bip_vectors(void) { 0xBE, 0xAF, 0xA3, 0x4B, 0x1A, 0xC5, 0x53, 0xE2 }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add_schnorrsig adds converts sig[0:32] to point R such + * that R.y is always even. This test vector has R.y = odd, so + * batch_add_schnorrsig returns 1 and batch_verify returns 0. */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 0); + #endif } { /* Test vector 7 */ @@ -449,6 +491,12 @@ static void test_schnorrsig_bip_vectors(void) { 0xAA, 0xEA, 0x51, 0x34, 0xFC, 0xCD, 0xB2, 0xBD }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add_schnorrsig does not verify the schnorr eqn. + * This test vector negated message, so batch_add_schnorrsig + * returns 1 and batch_verify returns 0. */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 0); + #endif } { /* Test vector 8 */ @@ -475,6 +523,12 @@ static void test_schnorrsig_bip_vectors(void) { 0x18, 0x34, 0xFF, 0x0D, 0x0C, 0x2E, 0x6D, 0xA6 }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add_schnorrsig does not verify the schnorr eqn. + * This test vector negated s (sig[32:64]), so batch_add_schnorrsig + * returns 1 and batch_verify returns 0. */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 0); + #endif } { /* Test vector 9 */ @@ -501,6 +555,12 @@ static void test_schnorrsig_bip_vectors(void) { 0xB6, 0x5C, 0x64, 0x25, 0xBD, 0x18, 0x60, 0x51 }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add_schnorrsig fails since R.x = 0. + * batch_verify passes because the batch is empty + * (prev batch_add failed so nothing was added to the batch)*/ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 0, 1); + #endif } { /* Test vector 10 */ @@ -527,6 +587,12 @@ static void test_schnorrsig_bip_vectors(void) { 0x37, 0x80, 0xD5, 0xA1, 0x83, 0x7C, 0xF1, 0x97 }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add_schnorrsig passes since R.x = 1. + * batch_verify fails since R (with R.x = 1 & R.y = even) does not + * lie on libsecp256k1 */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 1, 0); + #endif } { /* Test vector 11 */ @@ -553,6 +619,11 @@ static void test_schnorrsig_bip_vectors(void) { 0xA7, 0x9D, 0x5F, 0x7F, 0xC4, 0x07, 0xD3, 0x9B }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add fails since R.x is an invalid x-coordinate (not on curve) + * batch_verify passes since the batch is empty */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 0, 1); + #endif } { /* Test vector 12 */ @@ -579,6 +650,11 @@ static void test_schnorrsig_bip_vectors(void) { 0xA7, 0x9D, 0x5F, 0x7F, 0xC4, 0x07, 0xD3, 0x9B }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add fails since R.x = field modulo `p` + * batch_verify passes since the batch is empty */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 0, 1); + #endif } { /* Test vector 13 */ @@ -605,6 +681,11 @@ static void test_schnorrsig_bip_vectors(void) { 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41 }; test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 0); + #ifdef ENABLE_MODULE_BATCH + /* batch_add fails since s (sig[32:64]) = curve order `n` + * batch_verify passes since the batch is empty */ + test_schnorrsig_bip_vectors_check_batch_verify(pk, msg, sizeof(msg), sig, 0, 1); + #endif } { /* Test vector 14 */ @@ -651,6 +732,7 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, NULL, 0, sig); test_schnorrsig_bip_vectors_check_verify(pk, NULL, 0, sig, 1); + /* TODO batch verify */ } { /* Test vector 16 */ @@ -685,6 +767,7 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + /* TODO batch verify */ } { /* Test vector 17 */ @@ -723,6 +806,7 @@ static void test_schnorrsig_bip_vectors(void) { }; test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + /* TODO batch verify */ } { /* Test vector 18 */ @@ -758,6 +842,7 @@ static void test_schnorrsig_bip_vectors(void) { memset(msg, 0x99, sizeof(msg)); test_schnorrsig_bip_vectors_check_signing(sk, pk, aux_rand, msg, sizeof(msg), sig); test_schnorrsig_bip_vectors_check_verify(pk, msg, sizeof(msg), sig, 1); + /* TODO batch verify */ } } diff --git a/src/tests.c b/src/tests.c index 52614401c0..01eccc8d23 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4872,38 +4872,15 @@ static void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi } } -static int test_ecmult_multi_random(secp256k1_scratch *scratch) { - /* Large random test for ecmult_multi_* functions which exercises: - * - Few or many inputs (0 up to 128, roughly exponentially distributed). - * - Few or many 0*P or a*INF inputs (roughly uniformly distributed). - * - Including or excluding an nonzero a*G term (or such a term at all). - * - Final expected result equal to infinity or not (roughly 50%). - * - ecmult_multi_var, ecmult_strauss_single_batch, ecmult_pippenger_single_batch - */ - - /* These 4 variables define the eventual input to the ecmult_multi function. - * g_scalar is the G scalar fed to it (or NULL, possibly, if g_scalar=0), and - * scalars[0..filled-1] and gejs[0..filled-1] are the scalars and points - * which form its normal inputs. */ - int filled = 0; - secp256k1_scalar g_scalar = secp256k1_scalar_zero; - secp256k1_scalar scalars[128]; - secp256k1_gej gejs[128]; - /* The expected result, and the computed result. */ - secp256k1_gej expected, computed; +/** helper function used by `test_ecmult_multi_random` and `test_ecmult_strauss_batch_internal_random` + * to generate inputs (scalars, points, g_scalar) for multi-scalar point multiplication */ +static void ecmult_multi_random_generate_inp(secp256k1_gej *expected, secp256k1_scalar *g_scalar, secp256k1_scalar *scalars, secp256k1_gej *gejs, int *inp_len, int *nonzero_inp_len, int *is_g_nonzero, int *mults_performed) { /* Temporaries. */ secp256k1_scalar sc_tmp; secp256k1_ge ge_tmp; - /* Variables needed for the actual input to ecmult_multi. */ - secp256k1_ge ges[128]; - ecmult_multi_data data; int i; - /* Which multiplication function to use */ - int fn = testrand_int(3); - secp256k1_ecmult_multi_func ecmult_multi = fn == 0 ? secp256k1_ecmult_multi_var : - fn == 1 ? secp256k1_ecmult_strauss_batch_single : - secp256k1_ecmult_pippenger_batch_single; + int filled = 0; /* Simulate exponentially distributed num. */ int num_bits = 2 + testrand_int(6); /* Number of (scalar, point) inputs (excluding g). */ @@ -4918,25 +4895,25 @@ static int test_ecmult_multi_random(secp256k1_scratch *scratch) { num_nonzero == 1 && !nonzero_result ? 1 : (int)testrand_bits(1); /* Which g_scalar pointer to pass into ecmult_multi(). */ - const secp256k1_scalar* g_scalar_ptr = (g_nonzero || testrand_bits(1)) ? &g_scalar : NULL; + secp256k1_scalar* g_scalar_ptr = (g_nonzero || testrand_bits(1)) ? g_scalar : NULL; /* How many EC multiplications were performed in this function. */ int mults = 0; /* How many randomization steps to apply to the input list. */ int rands = (int)testrand_bits(3); if (rands > num_nonzero) rands = num_nonzero; - secp256k1_gej_set_infinity(&expected); + secp256k1_gej_set_infinity(expected); secp256k1_gej_set_infinity(&gejs[0]); secp256k1_scalar_set_int(&scalars[0], 0); if (g_nonzero) { /* If g_nonzero, set g_scalar to nonzero value r. */ - testutil_random_scalar_order_test(&g_scalar); + testutil_random_scalar_order_test(g_scalar); if (!nonzero_result) { /* If expected=0 is desired, add a (a*r, -(1/a)*g) term to compensate. */ CHECK(num_nonzero > filled); testutil_random_scalar_order_test(&sc_tmp); - secp256k1_scalar_mul(&scalars[filled], &sc_tmp, &g_scalar); + secp256k1_scalar_mul(&scalars[filled], &sc_tmp, g_scalar); secp256k1_scalar_inverse_var(&sc_tmp, &sc_tmp); secp256k1_scalar_negate(&sc_tmp, &sc_tmp); secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &gejs[filled], &sc_tmp); @@ -4956,7 +4933,7 @@ static int test_ecmult_multi_random(secp256k1_scratch *scratch) { if (nonzero_result) { /* Compute the expected result using normal ecmult. */ CHECK(filled <= 1); - secp256k1_ecmult(&expected, &gejs[0], &scalars[0], &g_scalar); + secp256k1_ecmult(expected, &gejs[0], &scalars[0], g_scalar); mults += filled + g_nonzero; } @@ -5026,6 +5003,54 @@ static int test_ecmult_multi_random(secp256k1_scratch *scratch) { } } + /* number of (scalars, points) inputs generated */ + *inp_len = filled; + /* number of non-zero (scalars, points) inputs */ + *nonzero_inp_len = num_nonzero; + /* ptr to g_scalar*/ + g_scalar = g_scalar_ptr; + /* is mulciplicand of g nonzero? */ + *is_g_nonzero = g_nonzero; + /* number of mults performed in this function */ + *mults_performed += mults; +} + +static int test_ecmult_multi_random(secp256k1_scratch *scratch) { + /* Large random test for ecmult_multi_* functions which exercises: + * - Few or many inputs (0 up to 128, roughly exponentially distributed). + * - Few or many 0*P or a*INF inputs (roughly uniformly distributed). + * - Including or excluding an nonzero a*G term (or such a term at all). + * - Final expected result equal to infinity or not (roughly 50%). + * - ecmult_multi_var, ecmult_strauss_single_batch, ecmult_pippenger_single_batch + */ + + /* These 4 variables define the eventual input to the ecmult_multi function. + * g_scalar is the G scalar fed to it (or NULL, possibly, if g_scalar=0), and + * scalars[0..filled-1] and gejs[0..filled-1] are the scalars and points + * which form its normal inputs. */ + int filled = 0; + secp256k1_scalar g_scalar = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + secp256k1_scalar *g_scalar_ptr = &g_scalar; + secp256k1_scalar scalars[128]; + secp256k1_gej gejs[128]; + /* The expected result, and the computed result. */ + secp256k1_gej expected, computed; + /* Variables needed for the actual input to ecmult_multi. */ + secp256k1_ge ges[128]; + ecmult_multi_data data; + /* How many EC multiplications were performed in this function. */ + int mults = 0; + int g_nonzero, num_nonzero; + + /* Which multiplication function to use */ + int fn = testrand_int(3); + secp256k1_ecmult_multi_func ecmult_multi = fn == 0 ? secp256k1_ecmult_multi_var : + fn == 1 ? secp256k1_ecmult_strauss_batch_single : + secp256k1_ecmult_pippenger_batch_single; + + /* generate inputs and their ecmult_multi output */ + ecmult_multi_random_generate_inp(&expected, g_scalar_ptr, scalars, gejs, &filled, &num_nonzero, &g_nonzero, &mults); + /* Compute affine versions of all inputs. */ secp256k1_ge_set_all_gej_var(ges, gejs, filled); /* Invoke ecmult_multi code. */ @@ -5038,6 +5063,59 @@ static int test_ecmult_multi_random(secp256k1_scratch *scratch) { return mults; } +static int test_ecmult_strauss_batch_internal_random(secp256k1_scratch *scratch) { + /* Large random test for `ecmult_strauss_batch_internal`. This test is + * very similar to `test_ecmult_multi_random`. */ + + /* These 4 variables define the eventual input to the ecmult_multi function. + * g_scalar is the G scalar fed to it (or NULL, possibly, if g_scalar=0), and + * scalars[0..filled-1] and gejs[0..filled-1] are the scalars and points + * which form its normal inputs. */ + int filled = 0; + secp256k1_scalar g_scalar = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + secp256k1_scalar *g_scalar_ptr = &g_scalar; + secp256k1_scalar scalars[128]; + secp256k1_gej gejs[128]; + /* The expected result, and the computed result. */ + secp256k1_gej expected, computed; + /* How many EC multiplications were performed in this function. */ + int mults = 0; + int g_nonzero, num_nonzero; + secp256k1_scalar *scratch_scalars; + secp256k1_gej *scratch_points; + size_t checkpoint = secp256k1_scratch_checkpoint(&CTX->error_callback, scratch); + int i; + + /* generate inputs and their ecmult_multi output */ + ecmult_multi_random_generate_inp(&expected, g_scalar_ptr, scalars, gejs, &filled, &num_nonzero, &g_nonzero, &mults); + + /* allocate inputs on the scratch space */ + scratch_scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(&CTX->error_callback, scratch, filled*sizeof(secp256k1_scalar)); + scratch_points = (secp256k1_gej*)secp256k1_scratch_alloc(&CTX->error_callback, scratch, filled*sizeof(secp256k1_gej)); + + /* If scalar or point allocation fails, restore scratch space to previous state */ + if (scratch_scalars == NULL || scratch_points == NULL) { + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, checkpoint); + return 0; + } + + /* copy the scalar and points to the scratch space */ + for (i = 0; i < filled; i++) { + scratch_scalars[i] = scalars[i]; + scratch_points[i] = gejs[i]; + } + + CHECK(secp256k1_ecmult_strauss_batch_internal(&CTX->error_callback, scratch, &computed, scratch_scalars, scratch_points, g_scalar_ptr, filled)); + mults += num_nonzero + g_nonzero; + /* Compare with expected result. */ + secp256k1_gej_neg(&computed, &computed); + secp256k1_gej_add_var(&computed, &computed, &expected, NULL); + CHECK(secp256k1_gej_is_infinity(&computed)); + + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, checkpoint); + return mults; +} + static void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { secp256k1_scalar sc; secp256k1_ge pt; @@ -5224,7 +5302,9 @@ static void test_ecmult_multi_batching(void) { static void run_ecmult_multi_tests(void) { secp256k1_scratch *scratch; - int64_t todo = (int64_t)320 * COUNT; + int64_t todo_multi = (int64_t)320 * COUNT; + /* todo: what should be the intial val of `todo_strauss_internal` */ + int64_t todo_strauss_internal = (int64_t)320 * COUNT; test_secp256k1_pippenger_bucket_window_inv(); test_ecmult_multi_pippenger_max_points(); @@ -5235,8 +5315,11 @@ static void run_ecmult_multi_tests(void) { test_ecmult_multi_batch_single(secp256k1_ecmult_pippenger_batch_single); test_ecmult_multi(scratch, secp256k1_ecmult_strauss_batch_single); test_ecmult_multi_batch_single(secp256k1_ecmult_strauss_batch_single); - while (todo > 0) { - todo -= test_ecmult_multi_random(scratch); + while (todo_multi > 0) { + todo_multi -= test_ecmult_multi_random(scratch); + } + while (todo_strauss_internal > 0) { + todo_strauss_internal -= test_ecmult_strauss_batch_internal_random(scratch); } secp256k1_scratch_destroy(&CTX->error_callback, scratch); @@ -7451,6 +7534,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ellswift/tests_impl.h" #endif +#ifdef ENABLE_MODULE_BATCH +# include "modules/batch/tests_impl.h" +#endif + static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7819,6 +7906,10 @@ int main(int argc, char **argv) { run_ellswift_tests(); #endif +#ifdef ENABLE_MODULE_BATCH + run_batch_tests(); +#endif + /* util tests */ run_secp256k1_memczero_test(); run_secp256k1_is_zero_array_test(); From 642901f57aaf87b322903546c5ad783692b40f9f Mon Sep 17 00:00:00 2001 From: siv2r Date: Wed, 2 Jul 2025 18:09:58 +0530 Subject: [PATCH 7/9] batch: Add tests for batch_add_* APIs This commit adds the following tests: 1. Random bitflip test for randomizer generating function 2. Random bitflip in Schnorr Signature (batch_add_schnorrsig test) 3. NULL arg tests (for both batch_add APIs) --- src/modules/extrakeys/Makefile.am.include | 1 + src/modules/extrakeys/batch_add_tests_impl.h | 128 ++++++++ src/modules/schnorrsig/Makefile.am.include | 1 + src/modules/schnorrsig/batch_add_tests_impl.h | 282 ++++++++++++++++++ src/modules/schnorrsig/tests_impl.h | 5 +- src/tests.c | 12 + 6 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 src/modules/extrakeys/batch_add_tests_impl.h create mode 100644 src/modules/schnorrsig/batch_add_tests_impl.h diff --git a/src/modules/extrakeys/Makefile.am.include b/src/modules/extrakeys/Makefile.am.include index 037a417491..c73da0225f 100644 --- a/src/modules/extrakeys/Makefile.am.include +++ b/src/modules/extrakeys/Makefile.am.include @@ -7,4 +7,5 @@ noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h noinst_HEADERS += src/modules/extrakeys/main_impl.h if ENABLE_MODULE_BATCH noinst_HEADERS += src/modules/extrakeys/batch_add_impl.h +noinst_HEADERS += src/modules/extrakeys/batch_add_tests_impl.h endif \ No newline at end of file diff --git a/src/modules/extrakeys/batch_add_tests_impl.h b/src/modules/extrakeys/batch_add_tests_impl.h new file mode 100644 index 0000000000..c7db8849ea --- /dev/null +++ b/src/modules/extrakeys/batch_add_tests_impl.h @@ -0,0 +1,128 @@ +#ifndef SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_TESTS_IMPL_H +#define SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_TESTS_IMPL_H + +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_batch.h" +#include "../../../include/secp256k1_tweak_check_batch.h" + +/* Checks that a bit flip in the n_flip-th argument (that has n_bytes many + * bytes) changes the hash function */ +void batch_xonlypub_tweak_randomizer_gen_bitflip(secp256k1_sha256 *sha, unsigned char **args, size_t n_flip, size_t n_bytes) { + unsigned char randomizers[2][32]; + secp256k1_sha256 sha_cpy; + sha_cpy = *sha; + secp256k1_batch_xonlypub_tweak_randomizer_gen(randomizers[0], &sha_cpy, args[0], args[1], args[2], args[3]); + testrand_flip(args[n_flip], n_bytes); + sha_cpy = *sha; + secp256k1_batch_xonlypub_tweak_randomizer_gen(randomizers[1], &sha_cpy, args[0], args[1], args[2], args[3]); + CHECK(secp256k1_memcmp_var(randomizers[0], randomizers[1], 32) != 0); +} + +void run_batch_xonlypub_tweak_randomizer_gen_tests(void) { + secp256k1_sha256 sha; + size_t n_checks = 20; + unsigned char tweaked_pk[32]; + unsigned char tweaked_pk_parity; + unsigned char tweak[32]; + unsigned char internal_pk[33]; + unsigned char *args[4]; + size_t i; /* loops through n_checks */ + int j; /* loops through count */ + + secp256k1_batch_sha256_tagged(&sha); + + for (i = 0; i < n_checks; i++) { + uint8_t temp_rand; + + /* generate i-th tweak check data */ + testrand256(tweaked_pk); + tweaked_pk_parity = (unsigned char) testrand_int(2); + testrand256(tweak); + testrand256(&internal_pk[1]); + temp_rand = testrand_int(2) + 2; /* randomly choose 2 or 3 */ + internal_pk[0] = (unsigned char)temp_rand; + + /* check bitflip in any argument results in generates randomizers */ + args[0] = tweaked_pk; + args[1] = &tweaked_pk_parity; + args[2] = internal_pk; + args[3] = tweak; + + for (j = 0; j < COUNT; j++) { + batch_xonlypub_tweak_randomizer_gen_bitflip(&sha, args, 0, 32); + batch_xonlypub_tweak_randomizer_gen_bitflip(&sha, args, 1, 1); + batch_xonlypub_tweak_randomizer_gen_bitflip(&sha, args, 2, 33); + batch_xonlypub_tweak_randomizer_gen_bitflip(&sha, args, 3, 32); + } + + /* write i-th tweak check data to the sha object + * this is required for generating the next randomizer */ + secp256k1_sha256_write(&sha, tweaked_pk, 32); + secp256k1_sha256_write(&sha, &tweaked_pk_parity, 1); + secp256k1_sha256_write(&sha, tweak, 32); + secp256k1_sha256_write(&sha, internal_pk, 33); + } + +} + +void test_batch_add_xonlypub_tweak_api(void) { + unsigned char sk[32]; + secp256k1_keypair keypair; + secp256k1_xonly_pubkey pk; + /* xonly pubkey tweak checks data */ + unsigned char tweaked_pk[32]; + int tweaked_pk_parity; + unsigned char tweak[32]; + secp256k1_pubkey tmp_pk; + secp256k1_xonly_pubkey tmp_xonly_pk; + unsigned char overflows[32]; + + /** setup **/ + secp256k1_batch *batch = secp256k1_batch_create(CTX, 1, NULL); + + /** generate keypair data **/ + testrand256(sk); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair) == 1); + memset(overflows, 0xFF, sizeof(overflows)); + + /** generate tweak check data (tweaked_pk, tweaked_pk_parity, tweak) **/ + testrand256(tweak); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &tmp_pk, &pk, tweak)); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &tmp_xonly_pk, &tweaked_pk_parity, &tmp_pk)); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, tweaked_pk, &tmp_xonly_pk)); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, tweaked_pk, tweaked_pk_parity, &pk, tweak)); + + CHECK(batch != NULL); + + /** main test body **/ + CHECK(secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk, tweaked_pk_parity, &pk, tweak) == 1); + CHECK(secp256k1_batch_verify(CTX, batch) == 1); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, NULL, tweaked_pk_parity, &pk, tweak)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk, tweaked_pk_parity, NULL, tweak)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk, tweaked_pk_parity, &pk, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_xonlypub_tweak_check(CTX, NULL, tweaked_pk, tweaked_pk_parity, &pk, tweak)); + /** overflowing tweak not allowed **/ + CHECK(secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk, tweaked_pk_parity, &pk, overflows) == 0); + /** x-coordinate of tweaked pubkey should be less than prime order **/ + CHECK(secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, overflows, tweaked_pk_parity, &pk, tweak) == 0); + + /** batch_verify should fail for incorrect tweak **/ + CHECK(secp256k1_batch_usable(CTX, batch)); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk, !tweaked_pk_parity, &pk, tweak) == 1); + CHECK(secp256k1_batch_verify(CTX, batch) == 0); + + /** batch_add_ should ignore unusable batch object (i.e, batch->result = 0) **/ + CHECK(secp256k1_batch_usable(CTX, batch) == 0); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(CTX, batch, tweaked_pk, tweaked_pk_parity, &pk, tweak) == 0); + + secp256k1_batch_destroy(CTX, batch); +} + +void run_batch_add_xonlypub_tweak_tests(void) { + run_batch_xonlypub_tweak_randomizer_gen_tests(); + test_batch_add_xonlypub_tweak_api(); +} + + +#endif /* SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_TESTS_IMPL_H */ diff --git a/src/modules/schnorrsig/Makefile.am.include b/src/modules/schnorrsig/Makefile.am.include index 03fc15aa5b..2c211784fb 100644 --- a/src/modules/schnorrsig/Makefile.am.include +++ b/src/modules/schnorrsig/Makefile.am.include @@ -8,4 +8,5 @@ noinst_HEADERS += src/modules/schnorrsig/tests_exhaustive_impl.h noinst_HEADERS += src/modules/schnorrsig/bench_impl.h if ENABLE_MODULE_BATCH noinst_HEADERS += src/modules/schnorrsig/batch_add_impl.h +noinst_HEADERS += src/modules/schnorrsig/batch_add_tests_impl.h endif diff --git a/src/modules/schnorrsig/batch_add_tests_impl.h b/src/modules/schnorrsig/batch_add_tests_impl.h new file mode 100644 index 0000000000..67da77a8bb --- /dev/null +++ b/src/modules/schnorrsig/batch_add_tests_impl.h @@ -0,0 +1,282 @@ +#ifndef SECP256K1_MODULE_SCHNORRSIG_BATCH_ADD_TESTS_IMPL_H +#define SECP256K1_MODULE_SCHNORRSIG_BATCH_ADD_TESTS_IMPL_H + +#include "../../../include/secp256k1_schnorrsig.h" +#include "../../../include/secp256k1_batch.h" +#include "../../../include/secp256k1_schnorrsig_batch.h" + +/* Checks that a bit flip in the n_flip-th argument (that has n_bytes many + * bytes) changes the hash function */ +void batch_schnorrsig_randomizer_gen_bitflip(secp256k1_sha256 *sha, unsigned char **args, size_t n_flip, size_t n_bytes, size_t msglen) { + unsigned char randomizers[2][32]; + secp256k1_sha256 sha_cpy; + sha_cpy = *sha; + secp256k1_batch_schnorrsig_randomizer_gen(randomizers[0], &sha_cpy, args[0], args[1], msglen, args[2]); + testrand_flip(args[n_flip], n_bytes); + sha_cpy = *sha; + secp256k1_batch_schnorrsig_randomizer_gen(randomizers[1], &sha_cpy, args[0], args[1], msglen, args[2]); + CHECK(secp256k1_memcmp_var(randomizers[0], randomizers[1], 32) != 0); +} + +void run_batch_schnorrsig_randomizer_gen_tests(void) { + secp256k1_sha256 sha; + size_t n_sigs = 20; + unsigned char msg[32]; + size_t msglen = sizeof(msg); + unsigned char sig[64]; + unsigned char compressed_pk[33]; + unsigned char *args[3]; + size_t i; /* loops through n_sigs */ + int j; /* loops through count */ + + secp256k1_batch_sha256_tagged(&sha); + + for (i = 0; i < n_sigs; i++) { + uint8_t temp_rand; + unsigned char randomizer[32]; + /* batch_schnorrsig_randomizer_gen func modifies the sha object passed + * so, pass the copied obj instead of original */ + secp256k1_sha256 sha_cpy; + + /* generate i-th schnorrsig verify data */ + testrand256(msg); + testrand256(&sig[0]); + testrand256(&sig[32]); + testrand256(&compressed_pk[1]); + temp_rand = testrand_int(2) + 2; /* randomly choose 2 or 3 */ + compressed_pk[0] = (unsigned char)temp_rand; + + /* check that bitflip in an argument results in different nonces */ + args[0] = sig; + args[1] = msg; + args[2] = compressed_pk; + + for (j = 0; j < COUNT; j++) { + batch_schnorrsig_randomizer_gen_bitflip(&sha, args, 0, 64, msglen); + batch_schnorrsig_randomizer_gen_bitflip(&sha, args, 1, 32, msglen); + batch_schnorrsig_randomizer_gen_bitflip(&sha, args, 2, 33, msglen); + } + + /* different msglen should generate different randomizers */ + sha_cpy = sha; + secp256k1_batch_schnorrsig_randomizer_gen(randomizer, &sha_cpy, sig, msg, msglen, compressed_pk); + + for (j = 0; j < COUNT; j++) { + unsigned char randomizer2[32]; + uint32_t offset = testrand_int(msglen - 1); + size_t msglen_tmp = (msglen + offset) % msglen; + + sha_cpy = sha; + secp256k1_batch_schnorrsig_randomizer_gen(randomizer2, &sha_cpy, sig, msg, msglen_tmp, compressed_pk); + CHECK(secp256k1_memcmp_var(randomizer, randomizer2, 32) != 0); + } + + /* write i-th schnorrsig verify data to the sha object + * this is required for generating the next randomizer */ + secp256k1_sha256_write(&sha, sig, 64); + secp256k1_sha256_write(&sha, msg, msglen); + secp256k1_sha256_write(&sha, compressed_pk, 33); + } + +} + +/* Helper for function test_schnorrsig_sign_batch_verify + * Checks that batch_verify fails after flipping random byte. */ +void test_schnorrsig_sign_verify_check_batch(secp256k1_batch *batch, unsigned char *sig64, unsigned char *msg, size_t msglen, secp256k1_xonly_pubkey *pk) { + int ret; + + CHECK(secp256k1_batch_usable(CTX, batch)); + /* filling a random byte (in msg or sig) can cause the following: + * 1. unparsable msg or sig - here, batch_add_schnorrsig fails and batch_verify passes + * 2. invalid schnorr eqn - here, batch_verify fails and batch_add_schnorrsig passes + */ + ret = secp256k1_batch_add_schnorrsig(CTX, batch, sig64, msg, msglen, pk); + if (ret == 0) { + CHECK(secp256k1_batch_verify(CTX, batch) == 1); + } else if (ret == 1) { + CHECK(secp256k1_batch_verify(CTX, batch) == 0); + } +} + +#define N_SIGS 3 +#define ONE_SIG 1 +/* Creates N_SIGS valid signatures and verifies them with batch_verify. + * Then flips some bits and checks that verification now fails. This is a + * variation of `test_schnorrsig_sign_verify` (in schnorrsig/tests_impl.h) */ +void test_schnorrsig_sign_batch_verify(void) { + unsigned char sk[32]; + unsigned char msg[N_SIGS][32]; + unsigned char sig[N_SIGS][64]; + size_t i; + secp256k1_keypair keypair; + secp256k1_xonly_pubkey pk; + secp256k1_scalar s; + secp256k1_batch *batch[N_SIGS + 1]; + secp256k1_batch *batch_fail1; + secp256k1_batch *batch_fail2; + + /* batch[0] will be used where batch_add and batch_verify + * are expected to succeed */ + batch[0] = secp256k1_batch_create(CTX, 2*N_SIGS, NULL); + for (i = 0; i < N_SIGS; i++) { + batch[i+1] = secp256k1_batch_create(CTX, 2*ONE_SIG, NULL); + } + batch_fail1 = secp256k1_batch_create(CTX, 2*ONE_SIG, NULL); + batch_fail2 = secp256k1_batch_create(CTX, 2*ONE_SIG, NULL); + + testrand256(sk); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair)); + + for (i = 0; i < N_SIGS; i++) { + testrand256(msg[i]); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[i], msg[i], &keypair, NULL)); + CHECK(secp256k1_batch_usable(CTX, batch[0])); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch[0], sig[i], msg[i], sizeof(msg[i]), &pk)); + } + CHECK(secp256k1_batch_verify(CTX, batch[0])); + + { + /* Flip a few bits in the signature and in the message and check that + * verify and verify_batch fail */ + size_t sig_idx = testrand_int(N_SIGS); + size_t byte_idx = testrand_bits(5); + unsigned char xorbyte = testrand_int(254)+1; + + sig[sig_idx][byte_idx] ^= xorbyte; + test_schnorrsig_sign_verify_check_batch(batch[1], sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk); + sig[sig_idx][byte_idx] ^= xorbyte; + + byte_idx = testrand_bits(5); + sig[sig_idx][32+byte_idx] ^= xorbyte; + test_schnorrsig_sign_verify_check_batch(batch[2], sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk); + sig[sig_idx][32+byte_idx] ^= xorbyte; + + byte_idx = testrand_bits(5); + msg[sig_idx][byte_idx] ^= xorbyte; + test_schnorrsig_sign_verify_check_batch(batch[3], sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk); + msg[sig_idx][byte_idx] ^= xorbyte; + + /* Check that above bitflips have been reversed correctly */ + CHECK(secp256k1_schnorrsig_verify(CTX, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); + } + + /* Test overflowing s */ + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[0], msg[0], &keypair, NULL)); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch[0], sig[0], msg[0], sizeof(msg[0]), &pk) == 1); + memset(&sig[0][32], 0xFF, 32); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch[0], sig[0], msg[0], sizeof(msg[0]), &pk) == 0); + + /* Test negative s */ + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[0], msg[0], &keypair, NULL)); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch[0], sig[0], msg[0], sizeof(msg[0]), &pk) == 1); + secp256k1_scalar_set_b32(&s, &sig[0][32], NULL); + secp256k1_scalar_negate(&s, &s); + secp256k1_scalar_get_b32(&sig[0][32], &s); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch_fail1, sig[0], msg[0], sizeof(msg[0]), &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch_fail1) == 0); + + /* The empty message can be signed & verified */ + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig[0], NULL, 0, &keypair, NULL) == 1); + CHECK(secp256k1_batch_usable(CTX, batch[0]) == 1); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch[0], sig[0], NULL, 0, &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch[0]) == 1); + + { + /* Test varying message lengths */ + unsigned char msg_large[32 * 8]; + uint32_t msglen = testrand_int(sizeof(msg_large)); + for (i = 0; i < sizeof(msg_large); i += 32) { + testrand256(&msg_large[i]); + } + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig[0], msg_large, msglen, &keypair, NULL) == 1); + CHECK(secp256k1_batch_usable(CTX, batch[0]) == 1); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch[0], sig[0], msg_large, msglen, &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch[0]) == 1); + /* batch_add fails for a random wrong message length */ + msglen = (msglen + (sizeof(msg_large) - 1)) % sizeof(msg_large); + CHECK(secp256k1_batch_usable(CTX, batch_fail2) == 1); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch_fail2, sig[0], msg_large, msglen, &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch_fail2) == 0); + } + + /* Destroy the batch objects */ + for (i = 0; i < N_SIGS+1; i++) { + secp256k1_batch_destroy(CTX, batch[i]); + } + secp256k1_batch_destroy(CTX, batch_fail1); + secp256k1_batch_destroy(CTX, batch_fail2); +} +#undef N_SIGS +/* ONE_SIG is undefined after `test_batch_add_schnorrsig_api` */ + +void test_batch_add_schnorrsig_api(void) { + unsigned char sk[32]; + secp256k1_keypair keypair; + secp256k1_xonly_pubkey pk; + secp256k1_xonly_pubkey zero_pk; + unsigned char msg[32]; + unsigned char sig[64]; + unsigned char nullmsg_sig[64]; + + /** setup **/ + secp256k1_batch *batch1 = secp256k1_batch_create(CTX, 2*ONE_SIG, NULL); + /* batch2 is used when batch_add_schnorrsig is expected to fail */ + secp256k1_batch *batch2 = secp256k1_batch_create(CTX, 2*ONE_SIG, NULL); + + + /** generate keypair data **/ + testrand256(sk); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair) == 1); + memset(&zero_pk, 0, sizeof(zero_pk)); + + /** generate a signature **/ + testrand256(msg); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk)); + + CHECK(batch1 != NULL); + CHECK(batch2 != NULL); + + /** main test body **/ + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch1, sig, msg, sizeof(msg), &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch1) == 1); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_schnorrsig(CTX, batch2, NULL, msg, sizeof(msg), &pk)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_schnorrsig(CTX, batch2, sig, NULL, sizeof(msg), &pk)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_schnorrsig(CTX, batch2, sig, msg, sizeof(msg), NULL)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_schnorrsig(CTX, batch2, sig, msg, sizeof(msg), &zero_pk)); + CHECK_ILLEGAL(CTX, secp256k1_batch_add_schnorrsig(CTX, NULL, sig, msg, sizeof(msg), &pk)); + + /** NULL msg with valid signature **/ + CHECK(secp256k1_schnorrsig_sign_custom(CTX, nullmsg_sig, NULL, 0, &keypair, NULL) == 1); + CHECK(secp256k1_batch_usable(CTX, batch1) == 1); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch1, nullmsg_sig, NULL, 0, &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch1) == 1); + + /** NULL msg with invalid signature **/ + CHECK(secp256k1_batch_usable(CTX, batch2) == 1); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch2, sig, NULL, 0, &pk) == 1); + CHECK(secp256k1_batch_verify(CTX, batch2) == 0); + + /** batch_add_ should ignore unusable batch object (i.e, batch->result = 0) **/ + CHECK(secp256k1_batch_usable(CTX, batch2) == 0); + CHECK(secp256k1_batch_add_schnorrsig(CTX, batch2, sig, msg, sizeof(msg), &pk) == 0); + + secp256k1_batch_destroy(CTX, batch1); + secp256k1_batch_destroy(CTX, batch2); +} +#undef ONE_SIG + +void run_batch_add_schnorrsig_tests(void) { + int i; + + run_batch_schnorrsig_randomizer_gen_tests(); + test_batch_add_schnorrsig_api(); + for (i = 0; i < COUNT; i++) { + test_schnorrsig_sign_batch_verify(); + } +} + + +#endif /* SECP256K1_MODULE_SCHNORRSIG_BATCH_ADD_TESTS_IMPL_H */ diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 8bce479ad7..fddde2c0f2 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -935,8 +935,9 @@ static void test_schnorrsig_sign(void) { #define N_SIGS 3 /* Creates N_SIGS valid signatures and verifies them with verify and - * verify_batch (TODO). Then flips some bits and checks that verification now - * fails. */ + * batch_verify. Then flips some bits and checks that verification now + * fails. The batch_verify variation of this test is implemented as + * test_schnorrsig_sign_batch_verify (in schnorrsig/batch_add_tests_impl.h) */ static void test_schnorrsig_sign_verify(void) { unsigned char sk[32]; unsigned char msg[N_SIGS][32]; diff --git a/src/tests.c b/src/tests.c index 01eccc8d23..356a905cf4 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7520,10 +7520,16 @@ static void run_ecdsa_wycheproof(void) { #ifdef ENABLE_MODULE_EXTRAKEYS # include "modules/extrakeys/tests_impl.h" +#ifdef ENABLE_MODULE_BATCH +# include "modules/extrakeys/batch_add_tests_impl.h" +#endif #endif #ifdef ENABLE_MODULE_SCHNORRSIG # include "modules/schnorrsig/tests_impl.h" +# ifdef ENABLE_MODULE_BATCH +# include "modules/schnorrsig/batch_add_tests_impl.h" +# endif #endif #ifdef ENABLE_MODULE_MUSIG @@ -7892,10 +7898,16 @@ int main(int argc, char **argv) { #ifdef ENABLE_MODULE_EXTRAKEYS run_extrakeys_tests(); +# ifdef ENABLE_MODULE_BATCH + run_batch_add_xonlypub_tweak_tests(); +# endif #endif #ifdef ENABLE_MODULE_SCHNORRSIG run_schnorrsig_tests(); +# ifdef ENABLE_MODULE_BATCH + run_batch_add_schnorrsig_tests(); +# endif #endif #ifdef ENABLE_MODULE_MUSIG From e5df505bad5f67263c343db1a1e82e8b3db5a42e Mon Sep 17 00:00:00 2001 From: siv2r Date: Thu, 3 Jul 2025 14:57:30 +0530 Subject: [PATCH 8/9] batch, extrakeys: Add benchmarks This commit adds benchmarks for: 1. Batch verifying Schnorr signatures 2. Batch verifying tweaked pubkey checks 3. Normal tweaked pubkey check in extrakeys module For batch verify benchmark, the number of sigs (or checks) in the batch varies from 1 to SECP256K1_BENCH_ITERS with a 20% increment. --- src/bench.c | 39 +++++++- src/bench.h | 4 +- src/modules/extrakeys/bench_impl.h | 139 ++++++++++++++++++++++++++++ src/modules/schnorrsig/bench_impl.h | 47 ++++++++++ 4 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 src/modules/extrakeys/bench_impl.h diff --git a/src/bench.c b/src/bench.c index 8ba7623a0e..7b4125f8c3 100644 --- a/src/bench.c +++ b/src/bench.c @@ -54,10 +54,20 @@ static void help(int default_iters) { printf(" ecdh : ECDH key exchange algorithm\n"); #endif +#ifdef ENABLE_MODULE_EXTRAKEYS + printf(" tweak_add_check : Checks if tweaked x-only pubkey is valid\n"); +# ifdef ENABLE_MODULE_BATCH + printf(" batch_tweak_checks : Batch verification of tweaked x-only pubkeys check\n"); +# endif +#endif + #ifdef ENABLE_MODULE_SCHNORRSIG printf(" schnorrsig : all Schnorr signature algorithms (sign, verify)\n"); printf(" schnorrsig_sign : Schnorr sigining algorithm\n"); printf(" schnorrsig_verify : Schnorr verification algorithm\n"); +#ifdef ENABLE_MODULE_BATCH + printf(" batch_schnorrsigs : Batch verification of Schnorr signatures\n"); +#endif #endif #ifdef ENABLE_MODULE_ELLSWIFT @@ -162,6 +172,10 @@ static void bench_keygen_run(void *arg, int iters) { # include "modules/recovery/bench_impl.h" #endif +#ifdef ENABLE_MODULE_EXTRAKEYS +# include "modules/extrakeys/bench_impl.h" +#endif + #ifdef ENABLE_MODULE_SCHNORRSIG # include "modules/schnorrsig/bench_impl.h" #endif @@ -182,7 +196,7 @@ int main(int argc, char** argv) { /* Check for invalid user arguments */ char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", - "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", + "ecdsa_recover", "extrakeys", "tweak_add_check", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "batch", "batch_tweak_checks", "batch_schnorrsigs", "ec", "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; size_t valid_args_size = sizeof(valid_args)/sizeof(valid_args[0]); @@ -219,13 +233,29 @@ int main(int argc, char** argv) { #endif #ifndef ENABLE_MODULE_SCHNORRSIG - if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) { + if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify") || have_flag(argc, argv, "batch_schnorrsigs")) { fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n"); fprintf(stderr, "Use ./configure --enable-module-schnorrsig.\n\n"); return EXIT_FAILURE; } #endif +#ifndef ENABLE_MODULE_EXTRAKEYS + if (have_flag(argc, argv, "extrakeys") || have_flag(argc, argv, "tweak_add_check") || have_flag(argc, argv, "batch_tweak_checks")) { + fprintf(stderr, "./bench: extrakeys module not enabled.\n"); + fprintf(stderr, "Use ./configure --enable-module-extrakeys.\n\n"); + return EXIT_FAILURE; + } +#endif + +#ifndef ENABLE_MODULE_BATCH + if (have_flag(argc, argv, "batch") || have_flag(argc, argv, "batch_schnorrsigs") || have_flag(argc, argv, "batch_tweak_checks")) { + fprintf(stderr, "./bench: Batch module not enabled.\n"); + fprintf(stderr, "Use ./configure --enable-module-batch --enable-experimental.\n\n"); + return EXIT_FAILURE; + } +#endif + #ifndef ENABLE_MODULE_ELLSWIFT if (have_flag(argc, argv, "ellswift") || have_flag(argc, argv, "ellswift_encode") || have_flag(argc, argv, "ellswift_decode") || have_flag(argc, argv, "encode") || have_flag(argc, argv, "decode") || have_flag(argc, argv, "ellswift_keygen") || @@ -270,6 +300,11 @@ int main(int argc, char** argv) { run_recovery_bench(iters, argc, argv); #endif +#ifdef ENABLE_MODULE_EXTRAKEYS + /* Extrakeys benchmarks */ + run_extrakeys_bench(iters, argc, argv); +#endif + #ifdef ENABLE_MODULE_SCHNORRSIG /* Schnorr signature benchmarks */ run_schnorrsig_bench(iters, argc, argv); diff --git a/src/bench.h b/src/bench.h index 232fb35fc0..d187383fa9 100644 --- a/src/bench.h +++ b/src/bench.h @@ -120,7 +120,7 @@ static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setu sum += total; } /* ',' is used as a column delimiter */ - printf("%-30s, ", name); + printf("%-35s, ", name); print_number(min * FP_MULT / iter); printf(" , "); print_number(((sum * FP_MULT) / count) / iter); @@ -181,7 +181,7 @@ static void print_output_table_header_row(void) { char* min_str = " Min(us) "; /* center alignment */ char* avg_str = " Avg(us) "; char* max_str = " Max(us) "; - printf("%-30s,%-15s,%-15s,%-15s\n", bench_str, min_str, avg_str, max_str); + printf("%-35s,%-15s,%-15s,%-15s\n", bench_str, min_str, avg_str, max_str); printf("\n"); } diff --git a/src/modules/extrakeys/bench_impl.h b/src/modules/extrakeys/bench_impl.h new file mode 100644 index 0000000000..5ec018e995 --- /dev/null +++ b/src/modules/extrakeys/bench_impl.h @@ -0,0 +1,139 @@ + +#ifndef SECP256K1_MODULE_EXTRAKEYS_BENCH_H +#define SECP256K1_MODULE_EXTRAKEYS_BENCH_H + +#include "../../../include/secp256k1_extrakeys.h" +#ifdef ENABLE_MODULE_BATCH +# include "../../../include/secp256k1_batch.h" +# include "../../../include/secp256k1_tweak_check_batch.h" +#endif + +typedef struct { + secp256k1_context *ctx; +#ifdef ENABLE_MODULE_BATCH + secp256k1_batch *batch; + /* number of tweak checks to batch verify. + * it varies from 1 to iters with 20% increments */ + int n; +#endif + + const secp256k1_keypair **keypairs; + const unsigned char **pks; + const unsigned char **tweaked_pks; + const int **tweaked_pk_parities; + const unsigned char **tweaks; +} bench_tweak_check_data; + +static void bench_xonly_pubkey_tweak_add_check(void* arg, int iters) { + bench_tweak_check_data *data = (bench_tweak_check_data *)arg; + int i; + + for (i = 0; i < iters; i++) { + secp256k1_xonly_pubkey pk; + CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &pk, data->pks[i]) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(data->ctx, data->tweaked_pks[i], *data->tweaked_pk_parities[i], &pk, data->tweaks[i]) == 1); + } +} + +#ifdef ENABLE_MODULE_BATCH +static void bench_xonly_pubkey_tweak_add_check_n(void* arg, int iters) { + bench_tweak_check_data *data = (bench_tweak_check_data *)arg; + int i, j; + + for (j = 0; j < iters/data->n; j++) { + for (i = 0; i < data->n; i++) { + secp256k1_xonly_pubkey pk; + CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &pk, data->pks[j+i]) == 1); + CHECK(secp256k1_batch_usable(data->ctx, data->batch) == 1); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(data->ctx, data->batch, data->tweaked_pks[j+i], *data->tweaked_pk_parities[j+i], &pk, data->tweaks[j+i]) == 1); + } + CHECK(secp256k1_batch_verify(data->ctx, data->batch) == 1); + } +} +#endif + +static void run_extrakeys_bench(int iters, int argc, char** argv) { + int i; + bench_tweak_check_data data; + int d = argc == 1; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + data.keypairs = (const secp256k1_keypair **)malloc(iters * sizeof(secp256k1_keypair *)); + data.pks = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); + data.tweaked_pks = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); + data.tweaked_pk_parities = (const int **)malloc(iters * sizeof(int *)); + data.tweaks = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); +#ifdef ENABLE_MODULE_BATCH + data.batch = secp256k1_batch_create(data.ctx, iters, NULL); + CHECK(data.batch != NULL); +#endif + + for (i = 0; i < iters; i++) { + unsigned char sk[32]; + unsigned char *tweaked_pk_char = (unsigned char *)malloc(32); + int *tweaked_pk_parity = (int *)malloc(sizeof(int)); /*todo: use sizeof(*twk_parity) instead?*/ + unsigned char *tweak = (unsigned char *)malloc(32); + secp256k1_keypair *keypair = (secp256k1_keypair *)malloc(sizeof(*keypair)); + unsigned char *pk_char = (unsigned char *)malloc(32); + secp256k1_xonly_pubkey pk; + secp256k1_pubkey output_pk; + secp256k1_xonly_pubkey output_pk_xonly; + tweak[0] = sk[0] = i; + tweak[1] = sk[1] = i >> 8; + tweak[2] = sk[2] = i >> 16; + tweak[3] = sk[3] = i >> 24; + memset(&tweak[4], 't', 28); + memset(&sk[4], 's', 28); + + data.keypairs[i] = keypair; + data.pks[i] = pk_char; + data.tweaked_pks[i] = tweaked_pk_char; + data.tweaked_pk_parities[i] = tweaked_pk_parity; + data.tweaks[i] = tweak; + + CHECK(secp256k1_keypair_create(data.ctx, keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(data.ctx, &pk, NULL, keypair)); + CHECK(secp256k1_xonly_pubkey_tweak_add(data.ctx, &output_pk, &pk, tweak)); + CHECK(secp256k1_xonly_pubkey_from_pubkey(data.ctx, &output_pk_xonly, tweaked_pk_parity, &output_pk)); + CHECK(secp256k1_xonly_pubkey_serialize(data.ctx, tweaked_pk_char, &output_pk_xonly) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(data.ctx, pk_char, &pk) == 1); + } + + if (d || have_flag(argc, argv, "extrakeys") || have_flag(argc, argv, "tweak_add_check")) run_benchmark("tweak_add_check", bench_xonly_pubkey_tweak_add_check, NULL, NULL, (void *) &data, 10, iters); +#ifdef ENABLE_MODULE_BATCH + if (d || have_flag(argc, argv, "extrakeys") || have_flag(argc, argv, "batch") || have_flag(argc, argv, "batch_tweak_checks")) { + for (i = 1; i <= iters; i = (int)(i*1.2 + 1)) { + char name[64]; + int divisible_iters; + sprintf(name, "batchverify_tweak_checks_%d", (int) i); + + data.n = i; + divisible_iters = iters - (iters % data.n); + run_benchmark(name, bench_xonly_pubkey_tweak_add_check_n, NULL, NULL, (void *) &data, 3, divisible_iters); + fflush(stdout); + } + } +#endif + + for (i = 0; i < iters; i++) { + free((void *)data.keypairs[i]); + free((void *)data.pks[i]); + free((void *)data.tweaked_pks[i]); + free((void *)data.tweaked_pk_parities[i]); + free((void *)data.tweaks[i]); + } + + /* Casting to (void *) avoids a stupid warning in MSVC. */ + free((void *)data.keypairs); + free((void *)data.pks); + free((void *)data.tweaked_pks); + free((void *)data.tweaked_pk_parities); + free((void *)data.tweaks); + +#ifdef ENABLE_MODULE_BATCH + secp256k1_batch_destroy(data.ctx, data.batch); +#endif + secp256k1_context_destroy(data.ctx); +} + +#endif /* SECP256K1_MODULE_EXTRAKEYS_BENCH_H */ diff --git a/src/modules/schnorrsig/bench_impl.h b/src/modules/schnorrsig/bench_impl.h index 93a878ede3..c8bec977c0 100644 --- a/src/modules/schnorrsig/bench_impl.h +++ b/src/modules/schnorrsig/bench_impl.h @@ -8,12 +8,21 @@ #define SECP256K1_MODULE_SCHNORRSIG_BENCH_H #include "../../../include/secp256k1_schnorrsig.h" +#ifdef ENABLE_MODULE_BATCH +# include "../../../include/secp256k1_batch.h" +# include "../../../include/secp256k1_schnorrsig_batch.h" +#endif #define MSGLEN 32 typedef struct { secp256k1_context *ctx; +#ifdef ENABLE_MODULE_BATCH + secp256k1_batch *batch; + /* number of signatures to batch verify. + * it varies from 1 to iters with 20% increments */ int n; +#endif const secp256k1_keypair **keypairs; const unsigned char **pk; @@ -45,6 +54,23 @@ static void bench_schnorrsig_verify(void* arg, int iters) { } } +#ifdef ENABLE_MODULE_BATCH +static void bench_schnorrsig_verify_n(void* arg, int iters) { + bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg; + int i, j; + + for (j = 0; j < iters/data->n; j++) { + for (i = 0; i < data->n; i++) { + secp256k1_xonly_pubkey pk; + CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &pk, data->pk[j+i]) == 1); + CHECK(secp256k1_batch_usable(data->ctx, data->batch) == 1); + CHECK(secp256k1_batch_add_schnorrsig(data->ctx, data->batch, data->sigs[j+i], data->msgs[j+i], MSGLEN, &pk) == 1); + } + CHECK(secp256k1_batch_verify(data->ctx, data->batch) == 1); + } +} +#endif + static void run_schnorrsig_bench(int iters, int argc, char** argv) { int i; bench_schnorrsig_data data; @@ -55,6 +81,10 @@ static void run_schnorrsig_bench(int iters, int argc, char** argv) { data.pk = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); data.msgs = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); data.sigs = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); +#ifdef ENABLE_MODULE_BATCH + data.batch = secp256k1_batch_create(data.ctx, 2*iters, NULL); + CHECK(data.batch != NULL); +#endif CHECK(MSGLEN >= 4); for (i = 0; i < iters; i++) { @@ -84,6 +114,20 @@ static void run_schnorrsig_bench(int iters, int argc, char** argv) { if (d || have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "sign") || have_flag(argc, argv, "schnorrsig_sign")) run_benchmark("schnorrsig_sign", bench_schnorrsig_sign, NULL, NULL, (void *) &data, 10, iters); if (d || have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "verify") || have_flag(argc, argv, "schnorrsig_verify")) run_benchmark("schnorrsig_verify", bench_schnorrsig_verify, NULL, NULL, (void *) &data, 10, iters); +#ifdef ENABLE_MODULE_BATCH + if (d || have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "batch") || have_flag(argc, argv, "batch_schnorrsigs")) { + for (i = 1; i <= iters; i = (int)(i*1.2 + 1)) { + char name[64]; + int divisible_iters; + sprintf(name, "batchverify_schnorrsigs_%d", (int) i); + + data.n = i; + divisible_iters = iters - (iters % data.n); + run_benchmark(name, bench_schnorrsig_verify_n, NULL, NULL, (void *) &data, 3, divisible_iters); + fflush(stdout); + } + } +#endif for (i = 0; i < iters; i++) { free((void *)data.keypairs[i]); @@ -98,6 +142,9 @@ static void run_schnorrsig_bench(int iters, int argc, char** argv) { free((void *)data.msgs); free((void *)data.sigs); +#ifdef ENABLE_MODULE_BATCH + secp256k1_batch_destroy(data.ctx, data.batch); +#endif secp256k1_context_destroy(data.ctx); } From 410abb205a93b5c84a00a4e9e478c852b6dc6d69 Mon Sep 17 00:00:00 2001 From: siv2r Date: Thu, 3 Jul 2025 21:35:10 +0530 Subject: [PATCH 9/9] batch: Generate speedup graphs This commit generates two semi-log graphs that visualize the batch verification speed up over single verification (y-axis) wrt the number of signatures (or tweak checks) in the batch (x-axis). The input data points are taken from the batch verify benchmark. GNU plot was used to generate these graphs (plot.gp file). The instructions to reproduce these graphs (on your local machine) are given in doc/speedup-batch.md file. The value of `STRAUSS_MAX_TERMS_PER_BATCH` was calculated (approx) from the generated graphs. Relevant discussion: https://github.com/siv2r/secp256k1/pull/2#issuecomment-1211585236 --- doc/speedup-batch.md | 17 +++ doc/speedup-batch/.gitignore | 1 + doc/speedup-batch/Makefile | 23 +++ doc/speedup-batch/bench.sh | 13 ++ doc/speedup-batch/bench_output.txt | 137 ++++++++++++++++++ doc/speedup-batch/plot.gp | 41 ++++++ .../schnorrsig-speedup-batch.png | Bin 0 -> 20465 bytes .../tweakcheck-speedup-batch.png | Bin 0 -> 22382 bytes src/modules/batch/main_impl.h | 8 + 9 files changed, 240 insertions(+) create mode 100644 doc/speedup-batch.md create mode 100644 doc/speedup-batch/.gitignore create mode 100644 doc/speedup-batch/Makefile create mode 100755 doc/speedup-batch/bench.sh create mode 100644 doc/speedup-batch/bench_output.txt create mode 100644 doc/speedup-batch/plot.gp create mode 100644 doc/speedup-batch/schnorrsig-speedup-batch.png create mode 100644 doc/speedup-batch/tweakcheck-speedup-batch.png diff --git a/doc/speedup-batch.md b/doc/speedup-batch.md new file mode 100644 index 0000000000..c1f47cc01f --- /dev/null +++ b/doc/speedup-batch.md @@ -0,0 +1,17 @@ +# Schnorrsig Batch Verification Speedup + +![Speedup over single verification](speedup-batch/schnorrsig-speedup-batch.png) + +# Tweak Pubkey Check Batch Verification Speedup + +![Speedup over single verification](speedup-batch/tweakcheck-speedup-batch.png) + +Build steps +----------- +To generate the above graphs on your local machine: + + $ cd doc/speedup-batch + $ make + $ make speedup-batch.png + + \ No newline at end of file diff --git a/doc/speedup-batch/.gitignore b/doc/speedup-batch/.gitignore new file mode 100644 index 0000000000..773a6df9ba --- /dev/null +++ b/doc/speedup-batch/.gitignore @@ -0,0 +1 @@ +*.dat diff --git a/doc/speedup-batch/Makefile b/doc/speedup-batch/Makefile new file mode 100644 index 0000000000..6ad82be58a --- /dev/null +++ b/doc/speedup-batch/Makefile @@ -0,0 +1,23 @@ +schnorrsig_data = schnorrsig_batch.dat schnorrsig_single.dat +tweak_data = tweak_batch.dat tweak_single.dat + +bench_output.txt: bench.sh + SECP256K1_BENCH_ITERS=500000 ./bench.sh bench_output.txt + +schnorrsig_batch.dat: bench_output.txt + cat bench_output.txt | grep -v "batchverify_schnorrsigs_1 " | awk '{ gsub(/ /,""); print }' | gawk -F, 'match($$0, /batchverify_schnorrsigs_([0-9]+)/, arr) {print arr[1] " " $$3}' > schnorrsig_batch.dat + +schnorrsig_single.dat: bench_output.txt + cat bench_output.txt | awk '{ gsub(/ /,""); print }' | awk -F, 'match($$0, /schnorrsig_verify/) {print $$3}' > schnorrsig_single.dat + +tweak_batch.dat: bench_output.txt + cat bench_output.txt | grep -v "batchverify_tweak_checks_1 " | awk '{ gsub(/ /,""); print }' | gawk -F, 'match($$0, /batchverify_tweak_checks_([0-9]+)/, arr) {print arr[1] " " $$3}' > tweak_batch.dat + +tweak_single.dat: bench_output.txt + cat bench_output.txt | awk '{ gsub(/ /,""); print }' | awk -F, 'match($$0, /tweak_add_check/) {print $$3}' > tweak_single.dat + +speedup-batch.png: $(schnorrsig_data) $(tweak_data) plot.gp + gnuplot plot.gp + +clean: + rm *.log *.txt *.dat *.png diff --git a/doc/speedup-batch/bench.sh b/doc/speedup-batch/bench.sh new file mode 100755 index 0000000000..3f40ef289b --- /dev/null +++ b/doc/speedup-batch/bench.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +output_file=$1 +cur_dir=$(pwd) + +cd ../../ +echo "HEAD: $(git rev-parse --short HEAD)" > "$cur_dir/$output_file.log" +make clean +./autogen.sh +./configure --enable-experimental --enable-module-batch --enable-module-schnorrsig >> "$cur_dir/$output_file.log" +make +./bench schnorrsig > "$cur_dir/$output_file" +./bench extrakeys >> "$cur_dir/$output_file" \ No newline at end of file diff --git a/doc/speedup-batch/bench_output.txt b/doc/speedup-batch/bench_output.txt new file mode 100644 index 0000000000..299ef734ff --- /dev/null +++ b/doc/speedup-batch/bench_output.txt @@ -0,0 +1,137 @@ +Benchmark , Min(us) , Avg(us) , Max(us) + +schnorrsig_sign , 7.57 , 7.60 , 7.64 +schnorrsig_verify , 15.2 , 15.3 , 15.5 +batchverify_schnorrsigs_1 , 17.8 , 17.9 , 18.1 +batchverify_schnorrsigs_2 , 15.6 , 15.8 , 15.9 +batchverify_schnorrsigs_3 , 14.7 , 14.8 , 14.8 +batchverify_schnorrsigs_4 , 14.4 , 14.4 , 14.4 +batchverify_schnorrsigs_5 , 14.1 , 14.1 , 14.2 +batchverify_schnorrsigs_7 , 13.9 , 13.9 , 13.9 +batchverify_schnorrsigs_9 , 13.6 , 13.7 , 13.8 +batchverify_schnorrsigs_11 , 13.7 , 13.8 , 13.8 +batchverify_schnorrsigs_14 , 13.3 , 13.5 , 13.6 +batchverify_schnorrsigs_17 , 13.5 , 13.5 , 13.5 +batchverify_schnorrsigs_21 , 13.4 , 13.4 , 13.5 +batchverify_schnorrsigs_26 , 13.3 , 13.3 , 13.4 +batchverify_schnorrsigs_32 , 13.3 , 13.4 , 13.4 +batchverify_schnorrsigs_39 , 13.3 , 13.3 , 13.4 +batchverify_schnorrsigs_47 , 13.2 , 13.2 , 13.2 +batchverify_schnorrsigs_57 , 13.2 , 13.4 , 13.5 +batchverify_schnorrsigs_69 , 13.2 , 13.3 , 13.4 +batchverify_schnorrsigs_83 , 13.3 , 13.3 , 13.4 +batchverify_schnorrsigs_100 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_121 , 13.2 , 13.3 , 13.5 +batchverify_schnorrsigs_146 , 13.4 , 13.5 , 13.5 +batchverify_schnorrsigs_176 , 13.3 , 13.4 , 13.5 +batchverify_schnorrsigs_212 , 13.2 , 13.3 , 13.5 +batchverify_schnorrsigs_255 , 13.2 , 13.3 , 13.4 +batchverify_schnorrsigs_307 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_369 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_443 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_532 , 13.4 , 13.5 , 13.6 +batchverify_schnorrsigs_639 , 13.5 , 13.6 , 13.6 +batchverify_schnorrsigs_767 , 13.3 , 13.3 , 13.4 +batchverify_schnorrsigs_921 , 13.3 , 13.4 , 13.4 +batchverify_schnorrsigs_1106 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_1328 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_1594 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_1913 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_2296 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_2756 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_3308 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_3970 , 13.3 , 13.6 , 13.8 +batchverify_schnorrsigs_4765 , 13.3 , 13.3 , 13.4 +batchverify_schnorrsigs_5719 , 13.3 , 13.4 , 13.7 +batchverify_schnorrsigs_6863 , 13.4 , 13.5 , 13.7 +batchverify_schnorrsigs_8236 , 13.3 , 13.3 , 13.4 +batchverify_schnorrsigs_9884 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_11861 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_14234 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_17081 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_20498 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_24598 , 13.2 , 13.2 , 13.3 +batchverify_schnorrsigs_29518 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_35422 , 13.2 , 13.2 , 13.3 +batchverify_schnorrsigs_42507 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_51009 , 13.2 , 13.2 , 13.3 +batchverify_schnorrsigs_61211 , 13.2 , 13.2 , 13.2 +batchverify_schnorrsigs_73454 , 13.2 , 13.2 , 13.2 +batchverify_schnorrsigs_88145 , 13.2 , 13.2 , 13.3 +batchverify_schnorrsigs_105775 , 13.2 , 13.2 , 13.2 +batchverify_schnorrsigs_126931 , 13.2 , 13.2 , 13.3 +batchverify_schnorrsigs_152318 , 13.3 , 13.3 , 13.3 +batchverify_schnorrsigs_182782 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_219339 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_263207 , 13.2 , 13.2 , 13.3 +batchverify_schnorrsigs_315849 , 13.2 , 13.3 , 13.3 +batchverify_schnorrsigs_379019 , 13.1 , 13.2 , 13.3 +batchverify_schnorrsigs_454823 , 13.2 , 13.2 , 13.3 +Benchmark , Min(us) , Avg(us) , Max(us) + +tweak_add_check , 11.4 , 11.5 , 11.6 +batchverify_tweak_checks_1 , 12.2 , 12.2 , 12.2 +batchverify_tweak_checks_2 , 10.2 , 10.2 , 10.2 +batchverify_tweak_checks_3 , 9.40 , 9.45 , 9.48 +batchverify_tweak_checks_4 , 9.01 , 9.03 , 9.05 +batchverify_tweak_checks_5 , 8.76 , 8.81 , 8.84 +batchverify_tweak_checks_7 , 8.52 , 8.55 , 8.58 +batchverify_tweak_checks_9 , 8.36 , 8.39 , 8.40 +batchverify_tweak_checks_11 , 8.24 , 8.27 , 8.29 +batchverify_tweak_checks_14 , 8.15 , 8.18 , 8.21 +batchverify_tweak_checks_17 , 8.11 , 8.12 , 8.14 +batchverify_tweak_checks_21 , 8.02 , 8.05 , 8.08 +batchverify_tweak_checks_26 , 7.99 , 8.00 , 8.00 +batchverify_tweak_checks_32 , 7.97 , 8.01 , 8.07 +batchverify_tweak_checks_39 , 7.91 , 7.92 , 7.94 +batchverify_tweak_checks_47 , 7.86 , 7.91 , 7.95 +batchverify_tweak_checks_57 , 7.93 , 7.94 , 7.95 +batchverify_tweak_checks_69 , 7.89 , 7.92 , 7.94 +batchverify_tweak_checks_83 , 7.85 , 7.88 , 7.90 +batchverify_tweak_checks_100 , 7.85 , 7.89 , 7.91 +batchverify_tweak_checks_121 , 7.93 , 7.95 , 7.98 +batchverify_tweak_checks_146 , 7.87 , 7.92 , 7.95 +batchverify_tweak_checks_176 , 7.91 , 7.92 , 7.93 +batchverify_tweak_checks_212 , 7.85 , 7.89 , 7.90 +batchverify_tweak_checks_255 , 7.89 , 7.91 , 7.93 +batchverify_tweak_checks_307 , 7.84 , 7.88 , 7.90 +batchverify_tweak_checks_369 , 7.86 , 7.89 , 7.91 +batchverify_tweak_checks_443 , 7.92 , 7.93 , 7.93 +batchverify_tweak_checks_532 , 7.91 , 7.91 , 7.92 +batchverify_tweak_checks_639 , 7.87 , 7.89 , 7.91 +batchverify_tweak_checks_767 , 7.90 , 7.90 , 7.90 +batchverify_tweak_checks_921 , 7.89 , 7.90 , 7.90 +batchverify_tweak_checks_1106 , 7.86 , 7.88 , 7.89 +batchverify_tweak_checks_1328 , 7.85 , 7.88 , 7.91 +batchverify_tweak_checks_1594 , 7.84 , 7.88 , 7.92 +batchverify_tweak_checks_1913 , 7.90 , 7.91 , 7.92 +batchverify_tweak_checks_2296 , 7.82 , 7.89 , 8.00 +batchverify_tweak_checks_2756 , 7.88 , 7.91 , 7.93 +batchverify_tweak_checks_3308 , 7.93 , 7.94 , 7.97 +batchverify_tweak_checks_3970 , 7.91 , 7.92 , 7.92 +batchverify_tweak_checks_4765 , 7.86 , 7.91 , 7.96 +batchverify_tweak_checks_5719 , 7.94 , 7.99 , 8.02 +batchverify_tweak_checks_6863 , 7.86 , 7.90 , 7.94 +batchverify_tweak_checks_8236 , 7.93 , 7.93 , 7.93 +batchverify_tweak_checks_9884 , 7.91 , 7.94 , 8.01 +batchverify_tweak_checks_11861 , 7.88 , 7.93 , 8.00 +batchverify_tweak_checks_14234 , 7.86 , 7.87 , 7.88 +batchverify_tweak_checks_17081 , 7.90 , 7.91 , 7.92 +batchverify_tweak_checks_20498 , 7.92 , 7.93 , 7.94 +batchverify_tweak_checks_24598 , 7.90 , 7.92 , 7.94 +batchverify_tweak_checks_29518 , 7.88 , 7.93 , 7.96 +batchverify_tweak_checks_35422 , 7.91 , 7.92 , 7.93 +batchverify_tweak_checks_42507 , 7.90 , 7.91 , 7.93 +batchverify_tweak_checks_51009 , 7.90 , 7.91 , 7.92 +batchverify_tweak_checks_61211 , 7.90 , 7.92 , 7.95 +batchverify_tweak_checks_73454 , 7.91 , 7.95 , 7.98 +batchverify_tweak_checks_88145 , 7.91 , 7.95 , 8.02 +batchverify_tweak_checks_105775 , 7.99 , 8.04 , 8.08 +batchverify_tweak_checks_126931 , 7.88 , 7.96 , 8.03 +batchverify_tweak_checks_152318 , 7.92 , 7.96 , 7.99 +batchverify_tweak_checks_182782 , 7.94 , 7.95 , 7.96 +batchverify_tweak_checks_219339 , 7.96 , 8.04 , 8.10 +batchverify_tweak_checks_263207 , 7.94 , 7.97 , 8.00 +batchverify_tweak_checks_315849 , 7.91 , 7.93 , 7.95 +batchverify_tweak_checks_379019 , 7.87 , 7.89 , 7.92 +batchverify_tweak_checks_454823 , 7.81 , 7.88 , 7.92 diff --git a/doc/speedup-batch/plot.gp b/doc/speedup-batch/plot.gp new file mode 100644 index 0000000000..7960ca0fd0 --- /dev/null +++ b/doc/speedup-batch/plot.gp @@ -0,0 +1,41 @@ +set style line 80 lt rgb "#808080" +set style line 81 lt 0 +set style line 81 lt rgb "#808080" +set grid back linestyle 81 +set border 3 back linestyle 80 +set xtics nomirror +set ytics nomirror +set style line 1 lt rgb "#A00000" lw 2 pt 1 +set style line 2 lt rgb "#00A000" lw 2 pt 6 +set style line 3 lt rgb "#5060D0" lw 2 pt 2 +set style line 4 lt rgb "#F25900" lw 2 pt 9 +set key bottom right +set autoscale +unset log +unset label +set xtic auto +set ytic auto +set title "Batch signature verification in libsecp256k1" +set xlabel "Number of signatures (logarithmic)" +set ylabel "Verification time per signature in us" +set grid +set logscale x +set mxtics 10 + +# Generate graph of Schnorr signature benchmark +schnorrsig_single_val=system("cat schnorrsig_single.dat") +set xrange [1.1:] +set xtics add ("2" 2) +set yrange [0.9:] +set ytics -1,0.1,3 +set ylabel "Speedup over single verification" +set term png size 800,600 +set output 'schnorrsig-speedup-batch.png' +plot "schnorrsig_batch.dat" using 1:(schnorrsig_single_val/$2) with points title "" ls 1 + +# Generate graph of tweaked x-only pubkey check benchmark +set title "Batch tweaked x-only pubkey check in libsecp256k1" +set xlabel "Number of tweak checks (logarithmic)" +tweak_single_val=system("cat tweak_single.dat") +set output 'tweakcheck-speedup-batch.png' +plot "tweak_batch.dat" using 1:(tweak_single_val/$2) with points title "" ls 1 diff --git a/doc/speedup-batch/schnorrsig-speedup-batch.png b/doc/speedup-batch/schnorrsig-speedup-batch.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c0195b60eeb70731bb7343637500a56fd7dc9c GIT binary patch literal 20465 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%0DfzFUt!9oPT=+#fi*mKR2>7sMGD7&z23b_w42 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 STRAUSS_MAX_TERMS_PER_BATCH) { + max_terms = STRAUSS_MAX_TERMS_PER_BATCH; + } VERIFY_CHECK(ctx != NULL); ARG_CHECK(max_terms != 0);