From 21a271d03f9324685cc8a3bb545b0d4436344206 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 6 Jan 2025 16:13:51 -0500 Subject: [PATCH 01/10] gui, psbt: Use SIGHASH_DEFAULT when signing PSBTs SIGHASH_DEFAULT should be used to indicate SIGHASH_DEFAULT for taproot inputs, and SIGHASH_ALL for all other input types. This avoids adding an unnecessary byte to the end of all Taproot signatures added to PSBTs signed in the GUI. --- src/qt/psbtoperationsdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index 5a4b4442f3d..454d9287aea 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -83,7 +83,7 @@ void PSBTOperationsDialog::signTransaction() WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock()); - const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)}; + const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)}; if (err) { showStatus(tr("Failed to sign transaction: %1") From 249a3fee9b831e99ad83e429d299a0dafb9316b3 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Sun, 2 Feb 2025 14:41:11 +0100 Subject: [PATCH 02/10] Squashed 'src/secp256k1/' changes from 0cdc758a56..e440c4fe45 e440c4fe45 build: Add build option for batch module 7c6b9df9ec SQUASH ME: Fix silent merge conflicts 4a0ea6104d batch: Generate graphs for batch verification speed up a804f05738 batch, extrakeys: Add benchmark for batch verify and `tweak_add_check` 2fa3654b1e batch: Add tests for `batch_add_*` APIs 2167f490c5 batch,ecmult: Add tests for core batch APIs and `strauss_batch` refactor 453a42c22f batch: Add API usage example 564b573451 batch: Add `batch_add_*` APIs c6577399a0 batch, ecmult: Add `batch_verify` API and refactor `strauss_batch` bb04d14af5 batch: Add `create` and `destroy` APIs e28dd3a120 batch: Initialize an experimental batch module 8deef00b33 Merge bitcoin-core/secp256k1#1634: Fix some misspellings 39705450eb Fix some misspellings ec329c2501 Merge bitcoin-core/secp256k1#1633: release cleanup: bump version after 0.6.0 c97059f594 release cleanup: bump version after 0.6.0 git-subtree-dir: src/secp256k1 git-subtree-split: e440c4fe45b82dba2fb39889c1b5244574ae4bd5 --- .gitignore | 1 + CHANGELOG.md | 3 + CMakeLists.txt | 21 +- Makefile.am | 15 + README.md | 1 + ci/cirrus.sh | 82 +++++ configure.ac | 23 +- doc/speedup-batch.md | 15 + 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/bench_output.txt.log | 127 +++++++ doc/speedup-batch/plot.gp | 41 +++ .../schnorrsig-speedup-batch.png | Bin 0 -> 8192 bytes .../tweakcheck-speedup-batch.png | Bin 0 -> 8639 bytes examples/batch.c | 181 ++++++++++ include/secp256k1_batch.h | 110 ++++++ include/secp256k1_schnorrsig_batch.h | 42 +++ include/secp256k1_tweak_check_batch.h | 50 +++ src/CMakeLists.txt | 5 + src/bench.c | 40 ++- src/bench.h | 4 +- src/ecmult_impl.h | 75 +++-- src/modules/batch/Makefile.am.include | 3 + src/modules/batch/main_impl.h | 207 ++++++++++++ src/modules/batch/tests_impl.h | 214 ++++++++++++ src/modules/extrakeys/Makefile.am.include | 7 + src/modules/extrakeys/batch_add_impl.h | 151 +++++++++ src/modules/extrakeys/batch_add_tests_impl.h | 165 +++++++++ src/modules/extrakeys/bench_impl.h | 139 ++++++++ src/modules/schnorrsig/Makefile.am.include | 7 + src/modules/schnorrsig/batch_add_impl.h | 158 +++++++++ src/modules/schnorrsig/batch_add_tests_impl.h | 313 ++++++++++++++++++ src/modules/schnorrsig/bench_impl.h | 49 ++- src/modules/schnorrsig/tests_impl.h | 89 ++++- src/scalar_impl.h | 4 +- src/secp256k1.c | 10 + src/tests.c | 177 +++++++--- src/util.h | 2 +- 40 files changed, 2624 insertions(+), 81 deletions(-) create mode 100755 ci/cirrus.sh 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/bench_output.txt.log 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 create mode 100644 examples/batch.c create mode 100644 include/secp256k1_batch.h create mode 100644 include/secp256k1_schnorrsig_batch.h create mode 100644 include/secp256k1_tweak_check_batch.h create mode 100644 src/modules/batch/Makefile.am.include create mode 100644 src/modules/batch/main_impl.h create mode 100644 src/modules/batch/tests_impl.h create mode 100644 src/modules/extrakeys/batch_add_impl.h create mode 100644 src/modules/extrakeys/batch_add_tests_impl.h create mode 100644 src/modules/extrakeys/bench_impl.h create mode 100644 src/modules/schnorrsig/batch_add_impl.h create mode 100644 src/modules/schnorrsig/batch_add_tests_impl.h diff --git a/.gitignore b/.gitignore index bffba8cb2c9..c26de35157b 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/CHANGELOG.md b/CHANGELOG.md index ee447c0c1c7..05222e5ed01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + ## [0.6.0] - 2024-11-04 #### Added @@ -162,6 +164,7 @@ This version was in fact never released. The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6). Therefore, this version number does not uniquely identify a set of source files. +[unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.6.0...HEAD [0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0 [0.5.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.1...v0.5.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 041bfa3dca0..6946a262c22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ project(libsecp256k1 # The package (a.k.a. release) version is based on semantic versioning 2.0.0 of # the API. All changes in experimental modules are treated as # backwards-compatible and therefore at most increase the minor version. - VERSION 0.6.0 + VERSION 0.6.1 DESCRIPTION "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1." HOMEPAGE_URL "https://github.com/bitcoin-core/secp256k1" LANGUAGES C @@ -32,7 +32,7 @@ endif() # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. set(${PROJECT_NAME}_LIB_VERSION_CURRENT 5) -set(${PROJECT_NAME}_LIB_VERSION_REVISION 0) +set(${PROJECT_NAME}_LIB_VERSION_REVISION 1) set(${PROJECT_NAME}_LIB_VERSION_AGE 0) #============================= @@ -55,11 +55,12 @@ option(SECP256K1_INSTALL "Enable installation." ${PROJECT_IS_TOP_LEVEL}) ## Modules # We declare all options before processing them, to make sure we can express -# dependendencies while processing. +# dependencies while processing. option(SECP256K1_ENABLE_MODULE_ECDH "Enable ECDH module." ON) option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF) option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) +option(SECP256K1_ENABLE_MODULE_BATCH "Enable batch module." OFF) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) @@ -69,6 +70,18 @@ if(SECP256K1_ENABLE_MODULE_ELLSWIFT) add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) endif() +option(SECP256K1_EXPERIMENTAL "Allow experimental configuration options." OFF) +if(SECP256K1_ENABLE_MODULE_BATCH) + if(NOT SECP256K1_EXPERIMENTAL) + message(FATAL_ERROR "Schnorrsig batch validation is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") + endif() + if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG) + message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the Schnorrsig batch validation module.") + endif() + set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON) + add_compile_definitions(ENABLE_MODULE_BATCH=1) +endif() + if(SECP256K1_ENABLE_MODULE_MUSIG) if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG) message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.") @@ -156,7 +169,6 @@ elseif(SECP256K1_ASM) endif() endif() -option(SECP256K1_EXPERIMENTAL "Allow experimental configuration options." OFF) if(NOT SECP256K1_EXPERIMENTAL) if(SECP256K1_ASM STREQUAL "arm32") message(FATAL_ERROR "ARM32 assembly is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") @@ -325,6 +337,7 @@ message(" ECDH ................................ ${SECP256K1_ENABLE_MODULE_ECDH} message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOVERY}") message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}") message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") +message(" batch ............................... ${SECP256K1_ENABLE_MODULE_BATCH}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") message("Parameters:") diff --git a/Makefile.am b/Makefile.am index a95b4809d48..4cb81a21b60 100644 --- a/Makefile.am +++ b/Makefile.am @@ -183,6 +183,17 @@ if BUILD_WINDOWS schnorr_example_LDFLAGS += -lbcrypt endif TESTS += schnorr_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 if ENABLE_MODULE_ELLSWIFT noinst_PROGRAMS += ellswift_example @@ -300,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 222e5fb7685..4f3361b127c 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/ci/cirrus.sh b/ci/cirrus.sh new file mode 100755 index 00000000000..2b8936a2f12 --- /dev/null +++ b/ci/cirrus.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +set -e +set -x + +export LC_ALL=C + +# Start persistent wineserver if necessary. +# This speeds up jobs with many invocations of wine (e.g., ./configure with MSVC) tremendously. +case "$WRAPPER_CMD" in + *wine*) + # This is apparently only reliable when we run a dummy command such as "hh.exe" afterwards. + wineserver -p && wine hh.exe + ;; +esac + +env >> test_env.log + +$CC -v || true +valgrind --version || true +$WRAPPER_CMD --version || true + +./autogen.sh + +./configure \ + --enable-experimental="$EXPERIMENTAL" \ + --with-test-override-wide-multiply="$WIDEMUL" --with-asm="$ASM" \ + --with-ecmult-window="$ECMULTWINDOW" \ + --with-ecmult-gen-precision="$ECMULTGENPRECISION" \ + --enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \ + --enable-module-schnorrsig="$SCHNORRSIG" \ + --enable-module-batch="$BATCH" \ + --enable-examples="$EXAMPLES" \ + --with-valgrind="$WITH_VALGRIND" \ + --host="$HOST" $EXTRAFLAGS + +# We have set "-j" in MAKEFLAGS. +make + +# Print information about binaries so that we can see that the architecture is correct +file *tests* || true +file bench* || true +file .libs/* || true + +# This tells `make check` to wrap test invocations. +export LOG_COMPILER="$WRAPPER_CMD" + +make "$BUILD" + +if [ "$BENCH" = "yes" ] +then + # Using the local `libtool` because on macOS the system's libtool has nothing to do with GNU libtool + EXEC='./libtool --mode=execute' + if [ -n "$WRAPPER_CMD" ] + then + EXEC="$EXEC $WRAPPER_CMD" + fi + { + $EXEC ./bench_ecmult + $EXEC ./bench_internal + $EXEC ./bench + } >> bench.log 2>&1 +fi + +if [ "$CTIMETEST" = "yes" ] +then + ./libtool --mode=execute valgrind --error-exitcode=42 ./valgrind_ctime_test > valgrind_ctime_test.log 2>&1 +fi + +# Rebuild precomputed files (if not cross-compiling). +if [ -z "$HOST" ] +then + make clean-precomp + make precomp +fi + +# Shutdown wineserver again +wineserver -k || true + +# Check that no repo files have been modified by the build. +# (This fails for example if the precomp files need to be updated in the repo.) +git diff --exit-code diff --git a/configure.ac b/configure.ac index f880a3578dd..25354ff8e73 100644 --- a/configure.ac +++ b/configure.ac @@ -5,8 +5,8 @@ AC_PREREQ([2.60]) # backwards-compatible and therefore at most increase the minor version. define(_PKG_VERSION_MAJOR, 0) define(_PKG_VERSION_MINOR, 6) -define(_PKG_VERSION_PATCH, 0) -define(_PKG_VERSION_IS_RELEASE, true) +define(_PKG_VERSION_PATCH, 1) +define(_PKG_VERSION_IS_RELEASE, false) # The library version is based on libtool versioning of the ABI. The set of # rules for updating the version can be found here: @@ -14,7 +14,7 @@ define(_PKG_VERSION_IS_RELEASE, true) # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. define(_LIB_VERSION_CURRENT, 5) -define(_LIB_VERSION_REVISION, 0) +define(_LIB_VERSION_REVISION, 1) define(_LIB_VERSION_AGE, 0) AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_PATCH)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-dev]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) @@ -192,6 +192,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])]) @@ -254,8 +258,8 @@ fi print_msan_notice=no if test x"$enable_ctime_tests" = x"yes"; then SECP_MSAN_CHECK - # MSan on Clang >=16 reports unitialized memory in function parameters and return values, even if - # the uninitalized variable is never actually "used". This is called "eager" checking, and it's + # MSan on Clang >=16 reports uninitialized memory in function parameters and return values, even if + # the uninitialized variable is never actually "used". This is called "eager" checking, and it's # sounds like good idea for normal use of MSan. However, it yields many false positives in the # ctime_tests because many return values depend on secret (i.e., "uninitialized") values, and # we're only interested in detecting branches (which count as "uses") on secret data. @@ -430,6 +434,10 @@ if test x"$enable_module_ecdh" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1" fi +if test x"$enable_module_batch" = x"yes"; then + AC_DEFINE(ENABLE_MODULE_BATCH, 1, [Define this symbol to enable the batch verification module]) +fi + if test x"$enable_external_default_callbacks" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1" fi @@ -442,6 +450,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 ### @@ -463,6 +474,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/doc/speedup-batch.md b/doc/speedup-batch.md new file mode 100644 index 00000000000..c6956398925 --- /dev/null +++ b/doc/speedup-batch.md @@ -0,0 +1,15 @@ +# 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 diff --git a/doc/speedup-batch/.gitignore b/doc/speedup-batch/.gitignore new file mode 100644 index 00000000000..773a6df9baf --- /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 00000000000..a6a270d348b --- /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 "schnorrsig_batch_verify_1 " | awk '{ gsub(/ /,""); print }' | awk -F, 'match($$0, /schnorrsig_batch_verify_([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 "tweak_check_batch_verify_1 " | awk '{ gsub(/ /,""); print }' | awk -F, 'match($$0, /tweak_check_batch_verify_([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 00000000000..d38d45dceba --- /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 -j +./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 00000000000..9d257341c13 --- /dev/null +++ b/doc/speedup-batch/bench_output.txt @@ -0,0 +1,137 @@ +Benchmark , Min(us) , Avg(us) , Max(us) + +schnorrsig_sign , 50.4 , 50.5 , 50.7 +schnorrsig_verify , 89.1 , 89.2 , 89.3 +schnorrsig_batch_verify_1 , 104.0 , 104.0 , 104.0 +schnorrsig_batch_verify_2 , 89.0 , 89.1 , 89.1 +schnorrsig_batch_verify_3 , 84.1 , 84.1 , 84.1 +schnorrsig_batch_verify_4 , 81.5 , 81.5 , 81.5 +schnorrsig_batch_verify_5 , 79.9 , 79.9 , 79.9 +schnorrsig_batch_verify_7 , 78.0 , 78.1 , 78.3 +schnorrsig_batch_verify_9 , 77.0 , 77.0 , 77.1 +schnorrsig_batch_verify_11 , 76.2 , 76.3 , 76.3 +schnorrsig_batch_verify_14 , 75.6 , 75.6 , 75.6 +schnorrsig_batch_verify_17 , 75.2 , 75.2 , 75.2 +schnorrsig_batch_verify_21 , 74.8 , 74.8 , 74.8 +schnorrsig_batch_verify_26 , 74.5 , 74.6 , 74.9 +schnorrsig_batch_verify_32 , 74.3 , 74.5 , 74.7 +schnorrsig_batch_verify_39 , 74.1 , 74.1 , 74.1 +schnorrsig_batch_verify_47 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_57 , 74.5 , 74.5 , 74.5 +schnorrsig_batch_verify_69 , 74.3 , 74.3 , 74.5 +schnorrsig_batch_verify_83 , 74.1 , 74.1 , 74.2 +schnorrsig_batch_verify_100 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_121 , 74.1 , 74.1 , 74.2 +schnorrsig_batch_verify_146 , 73.9 , 73.9 , 74.0 +schnorrsig_batch_verify_176 , 74.0 , 74.2 , 74.5 +schnorrsig_batch_verify_212 , 73.9 , 74.1 , 74.1 +schnorrsig_batch_verify_255 , 74.0 , 74.0 , 74.1 +schnorrsig_batch_verify_307 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_369 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_443 , 73.9 , 74.1 , 74.3 +schnorrsig_batch_verify_532 , 74.0 , 74.0 , 74.1 +schnorrsig_batch_verify_639 , 73.9 , 74.0 , 74.0 +schnorrsig_batch_verify_767 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_921 , 74.0 , 74.0 , 74.1 +schnorrsig_batch_verify_1106 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_1328 , 73.9 , 74.1 , 74.2 +schnorrsig_batch_verify_1594 , 74.0 , 74.1 , 74.1 +schnorrsig_batch_verify_1913 , 74.0 , 74.0 , 74.0 +schnorrsig_batch_verify_2296 , 74.0 , 74.0 , 74.0 +schnorrsig_batch_verify_2756 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_3308 , 74.1 , 74.1 , 74.2 +schnorrsig_batch_verify_3970 , 74.1 , 74.2 , 74.4 +schnorrsig_batch_verify_4765 , 74.0 , 74.1 , 74.2 +schnorrsig_batch_verify_5719 , 74.0 , 74.1 , 74.1 +schnorrsig_batch_verify_6863 , 74.0 , 74.1 , 74.1 +schnorrsig_batch_verify_8236 , 74.0 , 74.1 , 74.1 +schnorrsig_batch_verify_9884 , 74.0 , 74.1 , 74.3 +schnorrsig_batch_verify_11861 , 74.0 , 74.0 , 74.1 +schnorrsig_batch_verify_14234 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_17081 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_20498 , 73.9 , 74.0 , 74.0 +schnorrsig_batch_verify_24598 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_29518 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_35422 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_42507 , 73.9 , 74.0 , 74.0 +schnorrsig_batch_verify_51009 , 73.9 , 74.1 , 74.3 +schnorrsig_batch_verify_61211 , 73.9 , 73.9 , 74.0 +schnorrsig_batch_verify_73454 , 73.9 , 74.0 , 74.3 +schnorrsig_batch_verify_88145 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_105775 , 74.0 , 74.1 , 74.1 +schnorrsig_batch_verify_126931 , 73.9 , 74.0 , 74.1 +schnorrsig_batch_verify_152318 , 73.9 , 73.9 , 74.0 +schnorrsig_batch_verify_182782 , 73.9 , 73.9 , 74.0 +schnorrsig_batch_verify_219339 , 73.9 , 73.9 , 74.0 +schnorrsig_batch_verify_263207 , 74.0 , 74.1 , 74.3 +schnorrsig_batch_verify_315849 , 73.9 , 74.0 , 74.0 +schnorrsig_batch_verify_379019 , 73.9 , 73.9 , 73.9 +schnorrsig_batch_verify_454823 , 74.0 , 74.0 , 74.0 +Benchmark , Min(us) , Avg(us) , Max(us) + +tweak_add_check , 64.7 , 64.7 , 65.0 +tweak_check_batch_verify_1 , 69.7 , 69.8 , 69.8 +tweak_check_batch_verify_2 , 57.2 , 57.2 , 57.3 +tweak_check_batch_verify_3 , 52.0 , 52.1 , 52.2 +tweak_check_batch_verify_4 , 49.4 , 49.5 , 49.5 +tweak_check_batch_verify_5 , 47.9 , 47.9 , 47.9 +tweak_check_batch_verify_7 , 46.1 , 46.1 , 46.2 +tweak_check_batch_verify_9 , 45.2 , 45.2 , 45.4 +tweak_check_batch_verify_11 , 44.5 , 44.6 , 44.6 +tweak_check_batch_verify_14 , 43.9 , 43.9 , 43.9 +tweak_check_batch_verify_17 , 43.5 , 43.5 , 43.5 +tweak_check_batch_verify_21 , 43.1 , 43.1 , 43.1 +tweak_check_batch_verify_26 , 42.8 , 42.8 , 42.8 +tweak_check_batch_verify_32 , 42.5 , 42.6 , 42.6 +tweak_check_batch_verify_39 , 42.3 , 42.4 , 42.4 +tweak_check_batch_verify_47 , 42.2 , 42.2 , 42.2 +tweak_check_batch_verify_57 , 42.1 , 42.2 , 42.3 +tweak_check_batch_verify_69 , 42.0 , 42.1 , 42.1 +tweak_check_batch_verify_83 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_100 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_121 , 42.1 , 42.1 , 42.1 +tweak_check_batch_verify_146 , 42.0 , 42.0 , 42.0 +tweak_check_batch_verify_176 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_212 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_255 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_307 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_369 , 41.9 , 42.0 , 42.1 +tweak_check_batch_verify_443 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_532 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_639 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_767 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_921 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_1106 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_1328 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_1594 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_1913 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_2296 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_2756 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_3308 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_3970 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_4765 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_5719 , 41.9 , 42.0 , 42.1 +tweak_check_batch_verify_6863 , 42.0 , 42.0 , 42.0 +tweak_check_batch_verify_8236 , 42.0 , 42.0 , 42.0 +tweak_check_batch_verify_9884 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_11861 , 41.9 , 42.0 , 42.1 +tweak_check_batch_verify_14234 , 41.9 , 42.0 , 42.0 +tweak_check_batch_verify_17081 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_20498 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_24598 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_29518 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_35422 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_42507 , 41.8 , 41.8 , 41.9 +tweak_check_batch_verify_51009 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_61211 , 41.8 , 41.8 , 41.8 +tweak_check_batch_verify_73454 , 41.8 , 42.0 , 42.2 +tweak_check_batch_verify_88145 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_105775 , 41.8 , 41.8 , 41.8 +tweak_check_batch_verify_126931 , 41.8 , 41.9 , 41.9 +tweak_check_batch_verify_152318 , 41.8 , 41.9 , 42.0 +tweak_check_batch_verify_182782 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_219339 , 41.9 , 42.0 , 42.0 +tweak_check_batch_verify_263207 , 41.9 , 42.0 , 42.1 +tweak_check_batch_verify_315849 , 41.9 , 41.9 , 41.9 +tweak_check_batch_verify_379019 , 41.9 , 41.9 , 42.0 +tweak_check_batch_verify_454823 , 41.9 , 41.9 , 41.9 diff --git a/doc/speedup-batch/bench_output.txt.log b/doc/speedup-batch/bench_output.txt.log new file mode 100644 index 00000000000..c289c4aab2e --- /dev/null +++ b/doc/speedup-batch/bench_output.txt.log @@ -0,0 +1,127 @@ +HEAD: 6ddb0d0c +checking build system type... x86_64-pc-linux-gnu +checking host system type... x86_64-pc-linux-gnu +checking for a BSD-compatible install... /usr/bin/install -c +checking whether build environment is sane... yes +checking for a thread-safe mkdir -p... /usr/bin/mkdir -p +checking for gawk... gawk +checking whether make sets $(MAKE)... yes +checking whether make supports nested variables... yes +checking whether make supports nested variables... (cached) yes +checking for gcc... gcc +checking whether the C compiler works... yes +checking for C compiler default output file name... a.out +checking for suffix of executables... +checking whether we are cross compiling... no +checking for suffix of object files... o +checking whether we are using the GNU C compiler... yes +checking whether gcc accepts -g... yes +checking for gcc option to accept ISO C89... none needed +checking whether gcc understands -c and -o together... yes +checking whether make supports the include directive... yes (GNU style) +checking dependency style of gcc... gcc3 +checking dependency style of gcc... gcc3 +checking for ar... ar +checking the archiver (ar) interface... ar +checking how to print strings... printf +checking for a sed that does not truncate output... /usr/bin/sed +checking for grep that handles long lines and -e... /usr/bin/grep +checking for egrep... /usr/bin/grep -E +checking for fgrep... /usr/bin/grep -F +checking for ld used by gcc... /usr/bin/ld +checking if the linker (/usr/bin/ld) is GNU ld... yes +checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B +checking the name lister (/usr/bin/nm -B) interface... BSD nm +checking whether ln -s works... yes +checking the maximum length of command line arguments... 1572864 +checking how to convert x86_64-pc-linux-gnu file names to x86_64-pc-linux-gnu format... func_convert_file_noop +checking how to convert x86_64-pc-linux-gnu file names to toolchain format... func_convert_file_noop +checking for /usr/bin/ld option to reload object files... -r +checking for objdump... objdump +checking how to recognize dependent libraries... pass_all +checking for dlltool... no +checking how to associate runtime and link libraries... printf %s\n +checking for archiver @FILE support... @ +checking for strip... strip +checking for ranlib... ranlib +checking command to parse /usr/bin/nm -B output from gcc object... ok +checking for sysroot... no +checking for a working dd... /usr/bin/dd +checking how to truncate binary pipes... /usr/bin/dd bs=4096 count=1 +checking for mt... mt +checking if mt is a manifest tool... no +checking how to run the C preprocessor... gcc -E +checking for ANSI C header files... yes +checking for sys/types.h... yes +checking for sys/stat.h... yes +checking for stdlib.h... yes +checking for string.h... yes +checking for memory.h... yes +checking for strings.h... yes +checking for inttypes.h... yes +checking for stdint.h... yes +checking for unistd.h... yes +checking for dlfcn.h... yes +checking for objdir... .libs +checking if gcc supports -fno-rtti -fno-exceptions... no +checking for gcc option to produce PIC... -fPIC -DPIC +checking if gcc PIC flag -fPIC -DPIC works... yes +checking if gcc static flag -static works... yes +checking if gcc supports -c -o file.o... yes +checking if gcc supports -c -o file.o... (cached) yes +checking whether the gcc linker (/usr/bin/ld -m elf_x86_64) supports shared libraries... yes +checking whether -lc should be explicitly linked in... no +checking dynamic linker characteristics... GNU/Linux ld.so +checking how to hardcode library paths into programs... immediate +checking whether stripping libraries is possible... yes +checking if libtool supports shared libraries... yes +checking whether to build shared libraries... yes +checking whether to build static libraries... yes +checking if gcc supports -Werror=unknown-warning-option... no +checking if gcc supports -std=c89 -pedantic -Wno-long-long -Wnested-externs -Wshadow -Wstrict-prototypes -Wundef... yes +checking if gcc supports -Wno-overlength-strings... yes +checking if gcc supports -Wall... yes +checking if gcc supports -Wno-unused-function... yes +checking if gcc supports -Wextra... yes +checking if gcc supports -Wcast-align... yes +checking if gcc supports -Wcast-align=strict... yes +checking if gcc supports -Wconditional-uninitialized... no +checking if gcc supports -fvisibility=hidden... yes +checking for valgrind support... yes +checking for x86_64 assembly availability... yes +configure: ****** +configure: WARNING: experimental build +configure: Experimental features do not have stable APIs or properties, and may not be safe for production use. +configure: Building batch verification module: yes +configure: ****** +checking that generated files are newer than configure... done +configure: creating ./config.status +config.status: creating Makefile +config.status: creating libsecp256k1.pc +config.status: creating src/libsecp256k1-config.h +config.status: src/libsecp256k1-config.h is unchanged +config.status: executing depfiles commands +config.status: executing libtool commands + +Build Options: + with external callbacks = no + with benchmarks = yes + with tests = yes + with coverage = no + with examples = no + module ecdh = no + module recovery = no + module extrakeys = yes + module schnorrsig = yes + module batch = yes + + asm = x86_64 + ecmult window size = 15 + ecmult gen prec. bits = 4 + + valgrind = yes + CC = gcc + CPPFLAGS = + SECP_CFLAGS = -O2 -std=c89 -pedantic -Wno-long-long -Wnested-externs -Wshadow -Wstrict-prototypes -Wundef -Wno-overlength-strings -Wall -Wno-unused-function -Wextra -Wcast-align -Wcast-align=strict -fvisibility=hidden + CFLAGS = -g -O2 + LDFLAGS = diff --git a/doc/speedup-batch/plot.gp b/doc/speedup-batch/plot.gp new file mode 100644 index 00000000000..7960ca0fd04 --- /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..536b831503ce3ef518dd8fb771952831a169d979 GIT binary patch literal 8192 zcmeHscT`hd*XIQV6%6nw7J9HDp!D*9v>zZ||Etzp1GLp<|^3 z005$|06;+0YCu>P>>WqN^ce)AGlQE>nCs>D=Vw0sHld9hPAb|pPyfR ze0*_nF&2w$Z*QNPngUfUiHJlHA_@lZOL|LRDHOtpKc%z|O(0PE0Zygfja;&H0-{pUh*@r0RX3kniA~R!=!~*_a9^k9R19kdBOAW z*s*YR!xH$KP(@qC{hHv_<15+f!@3@Ded!nEr<3<7#v`Y6^Nag8cAhNp%naw=EE(y` zEUoc&wdC-5za<=0=*|9joO|x3!t#B68!MAa&baGu$ohnNKJ}3+S6dlI$W+Y#Y$jbu=&4h=R|lm)Sn%RISgTuK7c+^SqFNh(ocR zk|)WV#X`L2qMg8&PV?5pi5~U?%81!6dpPbi;>(WSt5Y%F015=M0s3kws5edTd)O?JAWaAQ4X`5qyp>(P;!&lsyYAzVZTM{LqU zUQ$Lw=#!xDg@t|dS+JFKzHII8o9hM3WVpNe3#{$@Xzxj$&9I zZN&iEU4W`ALEuYKG6_`Gbla59G~`sBSo12e!o)r=jYhpJC%!T--p_^goK(|g)rs3r za=)vb>uD|jo-4q=Ddeu-JJH}4D0p_&JYaqb{p^m@65%BObdpmM;)!Q`0`5@oYnqc} zzUjB0E0IQH`korwE`v`idSUntYko zOe4#bWidvv^R)4!7G(2Qp(iPaCK^3a`7xOL5s|aL<)+*thv!8|r7&=R8?MtOeFR$G z;-XitKb)GFYEGPX*>;s;x>;oq^P^)(UA7jnxWs+KL3xN_BG_E{(Oa4dcACOO^M&d$ z|NK}ko-%b*e)#Bvrw#>ey#<$Bq^H@8B3wym{D-zS?qN^Vt-OWin}(h>zKK51Xw|}I zwNfM}S|>~-@_Zu_X<5#I7rTzTg7V*Oj-msIer<*Is$HddIb{%$p?;};GPH`-QcmZ} ziP}rGqm6DO(rYR;?28M948+epTVkqi?PO@ZcII+<^c4>=b^$m34o_5hUqgp9S0mpF z7$Kukjw<_DhCFnba7A$+iPSqp8^180G@o$?+*aDQgt_)~ESIxfen|*AMl@i`Eu@1( znGI*w4MLx@(QW$hbua7Gu`3Ju>}8t*Jaq5c5?uzviUp=&U-HB~YpRXVi56jk)8f|i zaW2W;{ZMzymqfgbYN-uR4aamcJ*KxP3C}Vex2BnECGQZ8%e5cY%7%SCe4pr@T>de* zp9rwOu4J#-wzXw?YtHiik%ZXc|2adPYhGM@-bT3lwazg?MB^bvMJj2cZmZ49gjj#T zHexLmcI+`NoRRe$4T9%EhqudM6$U>+lic~k<0Fq0XJ6j9#6oWBA^!Ov;cl;p+Aidp zaS_VONYmn!L8#3cs-)yJ*bf-08g+5vLielh#*xWr@9>xr6Jt)lva=UC-Kupb7t!o) zn{QlqG#$H>gZ8GeJ4S?hWh{6u)(wqhD5rFm-iU(CJ_yTp9vR#rsh0**;2GeKXz`)ODlGu z0$W5oh+a7s%~L#b>b?8Da{i0q-Eva{ukUD__q?3PD=8@dFz{tf4^%70m-NH48o3rc zltgijyi(Q7e+|Q(VPCesQLzxXGIR2rwQa3aj!?m+%r+n7A#V{K{{_M!-8zp)V&nc{i3Q*f5!k@#V(@Hr1Ea=GE`8bc8v z18psTz8XLZfb~HSb#T^g#RrNwovC?DTgQI%h%r{8e_r}5&tcRP0W&t%gCEXdQ(;gYC6=|bvIQ_gdp2kzoe;EEh(gg&vfSV|^@19`E0e4nwzdw@d3^ibJ?dtQ5^%IY%(?c+q zAwAGzSA#ZCe_3ta=Y{xh9FKtB7NB~}2=l*V$F$C*#Nubv%0y4(fwry>>98IIg|vnc z^w;B{-#|kMn1wo5gtE%TMpk@lSb=_nZc z9FDjiA+q8h>FOzfN%UAHfQ;J(gZ&}ASo;psZ|SFBdp;0@9C zlJ>FIwdmzom$*|zaPn{P?5X&7Vchj3JtR?e0lTaBkF@6pB2W>>qyF81t0 z3xCOBrIh6+{`Z+0#PlOp0#n2Q*GQk%v&k!flCsg}$KM{3eOIe_X`|u$N9!)%kf4qK zi>ndX(%U`cYrQ4U&Km5yB;|gEn_)p9aFKR!O(qAWChIQ+agTWk*VgxpbYQf89&RD9 zgcT1&Ieia#r;Go~^E|&TQy^*!Pw@hAoe=s=Za!+0#+?<9pcf&e_J1~t&|h}2WonO% z1I;MRf0z3`UyGUZ3HM2*tD@|U-H5nY+Q*pddMCH9DFt7gkOX7neh`nitJi)}_xaDH zI)+ceSx_bUgFwOh!xt6Fu9x-!Se*pTw~*GJ(Ey=!u3dQuKOw78*&=68%|z8V4Asbx z{lqUfM->QJ*nVrh?>&)b{&Gv+uizJNbOv!u!vM}t9vY*o+kXu{J3;j=HLtjoKzC$_j(>yt+pHZiZTCN2D4$7MU2!-fLGluliLs;Ix5bt$$D{JG{Iiy%YXY(j#QM}Pyto{wXv3JWB-w>@u>qc?s8bdY`mY4d1)txYtxjeT8tqIw{7FCez z^u&e+8()qCeBELkL5Jlst6abl>#ODE2eZIgCkff?!I-CNI_+`j0iTI-J*`hrFwqt< zu1TdZK<=0qXXY2ziSk&;ZarCU)L%6v55gz@4!9F{TyrXyGsSnE2TUlVG4oauLX)(!}SC zp9ZNW_ya@AIa3u;*fHgOVz7ySHtogm8_wgm0#7w)u_DW7-s4OXndI$&Z|N;;oCiW# zh$n408of{a+hU?(m>^2UsnA{1S5WPQu);)coO{kz;MS)TlR-enEWm#9C`-c>ka8M z^i+Z20U!h&d+@JfIe%wee3h%>*x9F*D*GD{TRxp_W&f7Jggl)F14`cn6>?eRFy8wG zV^;j7`2n@&5I7qHkva+a?`coyTXa=Ib7m4@stJH_n48vYz)(61Ops z?BYY$oY?1TgU^%3x?;wo?<;Z@8+2s>?XD0Tg`f-kMjD*U+D8=}JtlY~a&-~x>iZ#H zgNQKX&k5vdI!BC|hPpo6LkF4r_Oxb3zwwI`*^EWK5Nb0`QNNSwULDcZMMojjTnc(8 z>uriWQktVOmdB2$|EeZoO<;?#oahtuKq>y(=$!zNIkaAsbs$B;_f~afT=7G|Mg!eJ z;+VAF?qL0$=fA11#!?htxgZ^8gq-?lpVd4!TaXzVl;4aej;ZQGe3!!!Ax1!oOs&GI#<2m?itroMK+lc?=v z=KE3qeMAnvx_D`>n(vB}EZtI|1wyluVfiMV!joH8v*TxL{^TO7Tz()6QR4n6eD<&9 zhyzf$-1NoB4_mtcZre?l{h9D5v~?ly;M|!_?vuDfR1cM!HKnl9sUS>aD7a2F3gRJejToaguwu4H^f;R!R`DB4O?=BVL|-R^h~)26bt zh8yQ>_oMMIp}u5m^DkvOMm(%kcWmwVIzi?y>U51NCxA#D*B)Br{dQa?=9dK(Tt=ZD zpB3}nq>P{!ZMGok5i%q6S+Fnk}LoQyy+~Dp1vW zJ-I2=8Yr_J6h7CT%waq#!GTjdRGwv*=U6>lI?3`BC_9QXogY?P4N`XkJ#B=%&0RKR z;{5iUg?O3Zeqm}!N||{1Fm`%e&Ze7Th=CZM06gOK94)Pvow!b-;oi5R!l{{&-%Uf> zx?SsQf(M|xe@pkjd1v%r+EV`)cJu#V^**2mx&E)*KK(y*;P{$HzUl@Yf^+kyMBVIV z0paEIrt&3QA%Z(3!P?1E#YyEL@Ha6bwICLvb}fZOiKYZB`ehO*+xnyJX9RVsgMRWi zs1&*7ViGQvKVHC^j##uXWcjxeES zON0#Wc(DqeHr`~19OJj9cnKL^=Z?yM(5+HiTV_qG8s_X}=_TEs2q8m>^gy|k_WE{@ zvj!qrkSgLBQAHy_V0I05eRKCevHb^>t*~o*XCoiFyO@=)0gLFbR_+Rc6@@+0f#C(tXK zhGss5e4ClFte77hw<()vyWJ@(JU4@zEnoI3)*j*#m6+`G4nP>$+l`r%(DG+w*984& zvv4U>(>9Lfp>2Ml<;BTqV=;zbyzXqzhpXbY+fo#!;9TdFP{RgV-G^)=IBp;JeFs?~ zF8I7xMSX($Wy27)>SMh6nCoL7y$rRwomE2@(D@fON4k3`E3enb5H~QbJzjmCV`^hD ztV6glHTg5!x>sgTZVPjqFfyipL@oK*ebq?Eh^(IX6oFa&j;TJs)2u(Zl_x{?=n5sV zZ^!h0-s!ea8x!1CigaeiKcea1&z>#abzH zlKG8kT4a5Q`$IM9Y1UJV(=0C}1d>O#jw;egV%lyM9zp>)+r<2f&TI_@Xt=(<1_ptL zYrk|N32yoYIImO}Hkn`V>5Dg#6f}z1QSQ4@pKjgqDRg7b^?DzpYfzpfS-XF_$}&YH z#9zY5P2;uHY?Q+tnUoiuxqY~=I?TQS<~#Q8_l~%zj6t1_zjKOcD!A5BE->Z9akYTz86!Kmu6kN8^mjPS5g&hQhYHqEUcEDayF;q^miIP)G8;vKS4)) zQPKJkx1~Vp%=S-E&tfsvsxD}s6~w2Mh9xSrf=~t}MdZ~uMLe*Jn7dG~Omd-j`&fd8 z4ftb?LLAiR1OBcbsIGE|Fuyfsyx?*P;)Y>-cf({6bps+&IMVqQZ#pZGBk;V~h766p ztBw-Gu|Dx-dKg>;%k8{REA1sTTL{C~s}mHuA0llH>Ih*L6>FsBfqoBcP79hflQryS z$FtPWl~Uv=b8Ki9A>5)OGxlyQ?QHG>EsMx4<9h8&e>aGY+6Hv_jfyvJYi@c7m`g>v zc#1g5T`6*ewMyXnr(uO_64ztXX8IBR5e_3$VvW(}X7e&Fo`qSkyWI=4<1&XE0^P8b z_4j3byf)q3bh&NgLu)zCqXd$gvncgJ3-LSNv3UHp$*cDz50mMCXVm(neAVNqkV4?o Q%Px# literal 0 HcmV?d00001 diff --git a/doc/speedup-batch/tweakcheck-speedup-batch.png b/doc/speedup-batch/tweakcheck-speedup-batch.png new file mode 100644 index 0000000000000000000000000000000000000000..f12e6e273f1915e985c46ff175b0f5c162f0a26d GIT binary patch literal 8639 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 OsHLv=SGn5Fuzv#^D0X%L literal 0 HcmV?d00001 diff --git a/examples/batch.c b/examples/batch.c new file mode 100644 index 00000000000..d0fc5786d51 --- /dev/null +++ b/examples/batch.c @@ -0,0 +1,181 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "random.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; +} diff --git a/include/secp256k1_batch.h b/include/secp256k1_batch.h new file mode 100644 index 00000000000..6068ec64e4f --- /dev/null +++ b/include/secp256k1_batch.h @@ -0,0 +1,110 @@ +#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 Improvemtn Proposal 341 + * "Taproot: SegWit version 1 spending rules" + * (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); + +/** 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 + * 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 + +#endif /* SECP256K1_BATCH_H */ diff --git a/include/secp256k1_schnorrsig_batch.h b/include/secp256k1_schnorrsig_batch.h new file mode 100644 index 00000000000..ffd8399ee77 --- /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 00000000000..4ae9027fa4a --- /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 f31b8c8f551..701f8471cd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,11 @@ if(SECP256K1_INSTALL) if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig.h") endif() + if(SECP256K1_ENABLE_MODULE_BATCH) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_batch.h") + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig_batch.h") + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_tweak_check_batch.h") + endif() if(SECP256K1_ENABLE_MODULE_MUSIG) list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_musig.h") endif() diff --git a/src/bench.c b/src/bench.c index 1127df67ae0..0a9f287e3da 100644 --- a/src/bench.c +++ b/src/bench.c @@ -42,17 +42,27 @@ static void help(int default_iters) { printf(" ec_keygen : EC public key generation\n"); #ifdef ENABLE_MODULE_RECOVERY - printf(" ecdsa_recover : ECDSA public key recovery algorithm\n"); + printf(" ecdsa_recover : ECDSA public key recovery algorithm\n"); #endif #ifdef ENABLE_MODULE_ECDH - printf(" ecdh : ECDH key exchange algorithm\n"); + printf(" ecdh : ECDH key exchange algorithm\n"); #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"); + 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(" schnorrsig_batch_verify : Batch verification of Schnorr signatures\n"); +# endif +#endif + +#ifdef ENABLE_MODULE_EXTRAKEYS + printf(" tweak_add_check : Checks if tweaked x-only pubkey is valid\n"); +# ifdef ENABLE_MODULE_BATCH + printf(" tweak_check_batch_verify : Batch verification of tweaked x-only pubkeys check\n"); +# endif #endif #ifdef ENABLE_MODULE_ELLSWIFT @@ -157,6 +167,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 @@ -179,7 +193,8 @@ int main(int argc, char** argv) { char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", - "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; + "ellswift_decode", "ellswift_keygen", "ellswift_ecdh", + "batch_verify", "schnorrsig_batch_verify", "extrakeys", "tweak_add_check", "tweak_check_batch_verify"}; size_t valid_args_size = sizeof(valid_args)/sizeof(valid_args[0]); int invalid_args = have_invalid_args(argc, argv, valid_args, valid_args_size); @@ -221,6 +236,14 @@ int main(int argc, char** argv) { } #endif +#ifndef ENABLE_MODULE_BATCH + if (have_flag(argc, argv, "batch_verify") || have_flag(argc, argv, "schnorrsig_batch_verify") || have_flag(argc, argv, "tweak_check_batch_verify")) { + fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n"); + fprintf(stderr, "Use ./configure --enable-module-schnorrsig.\n\n"); + return 1; + } +#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") || @@ -265,6 +288,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 1564b1a1760..abb5f883443 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/ecmult_impl.h b/src/ecmult_impl.h index 0b53b3fcb98..248a4cd8b9f 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; } +/** 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); } -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); -} - /** 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/Makefile.am.include b/src/modules/batch/Makefile.am.include new file mode 100644 index 00000000000..f996e0efca7 --- /dev/null +++ b/src/modules/batch/Makefile.am.include @@ -0,0 +1,3 @@ +include_HEADERS += include/secp256k1_batch.h +noinst_HEADERS += src/modules/batch/main_impl.h +noinst_HEADERS += src/modules/batch/tests_impl.h diff --git a/src/modules/batch/main_impl.h b/src/modules/batch/main_impl.h new file mode 100644 index 00000000000..a649805ae78 --- /dev/null +++ b/src/modules/batch/main_impl.h @@ -0,0 +1,207 @@ +#ifndef SECP256K1_MODULE_BATCH_MAIN_H +#define SECP256K1_MODULE_BATCH_MAIN_H + +#include "../../../include/secp256k1_batch.h" + +/* Maximum number of scalar-point pairs on the batch + * for which `secp256k1_batch_verify` remains efficient */ +#define STRAUSS_MAX_TERMS_PER_BATCH 106 + +/* 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: + * 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 typdef 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; +} + +/** 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) { + 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}; + /* max number of scalar-point pairs on scratch up to which Strauss multi multiplication is efficient */ + if (max_terms > STRAUSS_MAX_TERMS_PER_BATCH) { + max_terms = STRAUSS_MAX_TERMS_PER_BATCH; + } + + 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); + } +} + +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 + * 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); + (void)strauss_ret; + 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); + + batch->result = batch->result && mid_res; + secp256k1_batch_scratch_clear(batch); + } + + return batch->result; +} + +#endif /* SECP256K1_MODULE_BATCH_MAIN_H */ diff --git a/src/modules/batch/tests_impl.h b/src/modules/batch/tests_impl.h new file mode 100644 index 00000000000..f9a81ec942e --- /dev/null +++ b/src/modules/batch/tests_impl.h @@ -0,0 +1,214 @@ +#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[13] = "BIP0340/batch"; + 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) { + secp256k1_batch *batch_none; + secp256k1_batch *batch_sign; + secp256k1_batch *batch_vrfy; + secp256k1_batch *batch_both; + secp256k1_batch *batch_sttc; + unsigned char aux_rand16[32]; + int ecount; + +#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 + /* context and batch setup */ + secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + secp256k1_context *sttc = malloc(sizeof(*secp256k1_context_no_precomp)); + memcpy(sttc, secp256k1_context_no_precomp, sizeof(secp256k1_context)); + + secp256k1_context_set_error_callback(none, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(sign, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(vrfy, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(both, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(sttc, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(none, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(vrfy, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(both, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sttc, counting_callback_fn, &ecount); + + /* 16 byte auxiliary randomness */ + testrand256(aux_rand16); + memset(&aux_rand16[16], 0, 16); + +#ifdef ENABLE_MODULE_EXTRAKEYS + /* generate keypair data */ + testrand256(sk); + CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(sign, &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(vrfy, &tmp_pk, &pk, tweak[i])); + CHECK(secp256k1_xonly_pubkey_from_pubkey(vrfy, &tmp_xonly_pk, &tweaked_pk_parity[i], &tmp_pk)); + CHECK(secp256k1_xonly_pubkey_serialize(vrfy, tweaked_pk[i], &tmp_xonly_pk)); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(vrfy, tweaked_pk[i], tweaked_pk_parity[i], &pk, tweak[i])); + } +#endif + +#ifdef ENABLE_MODULE_SCHNORRSIG + /* generate N_SIGS schnorr verify data (msg, sig) */ + for (i = 0; i < N_SIGS; i++) { + testrand256(msg[i]); + CHECK(secp256k1_schnorrsig_sign32(sign, sig[i], msg[i], &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(vrfy, sig[i], msg[i], sizeof(msg[i]), &pk)); + } +#endif + + /** main test body **/ + /* batch_create tests */ + ecount = 0; + batch_none = secp256k1_batch_create(none, 1, NULL); + CHECK(batch_none != NULL); + CHECK(ecount == 0); + /* 2*N_SIGS since one schnorrsig creates two scalar-point pair in batch */ + batch_sign = secp256k1_batch_create(sign, 2*N_SIGS, NULL); + CHECK(batch_sign != NULL); + CHECK(ecount == 0); + batch_vrfy = secp256k1_batch_create(vrfy, N_TWK_CHECKS - 1, aux_rand16); + CHECK(batch_vrfy != NULL); + CHECK(ecount == 0); + batch_both = secp256k1_batch_create(both, N_TERMS/4, aux_rand16); + CHECK(batch_both != NULL); + CHECK(ecount == 0); + /* ARG_CHECK(max_terms != 0) in `batch_create` should fail*/ + batch_sttc = secp256k1_batch_create(sttc, 0, NULL); + CHECK(batch_sttc == NULL); + CHECK(ecount == 1); + +#ifdef ENABLE_MODULE_SCHNORRSIG + ecount = 0; + for (i = 0; i < N_SIGS; i++) { + CHECK(secp256k1_batch_usable(sign, batch_sign) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_schnorrsig(sign, batch_sign, sig[i], msg[i], sizeof(msg[i]), &pk) == 1); + CHECK(ecount == 0); + } +#endif + +#ifdef ENABLE_MODULE_EXTRAKEYS + ecount = 0; + for (i = 0; i < N_TWK_CHECKS; i++) { + CHECK(secp256k1_batch_usable(vrfy, batch_vrfy)); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(vrfy, batch_vrfy, tweaked_pk[i], tweaked_pk_parity[i], &pk, tweak[i])); + CHECK(ecount == 0); + } +#endif + +#if defined(ENABLE_MODULE_SCHNORRSIG) && defined(ENABLE_MODULE_EXTRAKEYS) + /* secp256k1_batch_add_tests for batch_both */ + ecount = 0; + for (i = 0; i < N_SIGS; i++) { + CHECK(secp256k1_batch_usable(both, batch_both) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_schnorrsig(both, batch_both, sig[i], msg[i], sizeof(msg[i]), &pk) == 1); + CHECK(ecount == 0); + } + for (i = 0; i < N_TWK_CHECKS; i++) { + CHECK(secp256k1_batch_usable(both, batch_both)); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(both, batch_both, tweaked_pk[i], tweaked_pk_parity[i], &pk, tweak[i])); + CHECK(ecount == 0); + } +#endif + + /* batch_verify tests */ + ecount = 0; + CHECK(secp256k1_batch_verify(none, batch_none) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(sign, batch_sign) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(vrfy, batch_vrfy) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(both, batch_both) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(sttc, NULL) == 0); + CHECK(ecount == 1); + + ecount = 0; + secp256k1_batch_destroy(none, batch_none); + CHECK(ecount == 0); + secp256k1_batch_destroy(sign, batch_sign); + CHECK(ecount == 0); + secp256k1_batch_destroy(vrfy, batch_vrfy); + CHECK(ecount == 0); + secp256k1_batch_destroy(both, batch_both); + CHECK(ecount == 0); + secp256k1_batch_destroy(sttc, NULL); + CHECK(ecount == 0); + + secp256k1_context_destroy(none); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(vrfy); + secp256k1_context_destroy(both); + secp256k1_context_destroy(sttc); +} +#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/extrakeys/Makefile.am.include b/src/modules/extrakeys/Makefile.am.include index 0d901ec1f44..be6efb2d083 100644 --- a/src/modules/extrakeys/Makefile.am.include +++ b/src/modules/extrakeys/Makefile.am.include @@ -1,4 +1,11 @@ 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 +noinst_HEADERS += src/modules/extrakeys/batch_add_tests_impl.h +endif diff --git a/src/modules/extrakeys/batch_add_impl.h b/src/modules/extrakeys/batch_add_impl.h new file mode 100644 index 00000000000..46798fbe46c --- /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/extrakeys/batch_add_tests_impl.h b/src/modules/extrakeys/batch_add_tests_impl.h new file mode 100644 index 00000000000..d18aafcc1bd --- /dev/null +++ b/src/modules/extrakeys/batch_add_tests_impl.h @@ -0,0 +1,165 @@ +#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_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_batch *batch1 = secp256k1_batch_create(none, 1, NULL); + /* batch2 is used when batch_add_xonlypub_tweak is expected to fail */ + secp256k1_batch *batch2 = secp256k1_batch_create(none, 1, NULL); + int ecount; + + secp256k1_context_set_error_callback(none, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(sign, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(vrfy, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(none, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(vrfy, counting_callback_fn, &ecount); + + /** generate keypair data **/ + testrand256(sk); + CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(sign, &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(vrfy, &tmp_pk, &pk, tweak)); + CHECK(secp256k1_xonly_pubkey_from_pubkey(vrfy, &tmp_xonly_pk, &tweaked_pk_parity, &tmp_pk)); + CHECK(secp256k1_xonly_pubkey_serialize(vrfy, tweaked_pk, &tmp_xonly_pk)); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(vrfy, tweaked_pk, tweaked_pk_parity, &pk, tweak)); + + CHECK(batch1 != NULL); + CHECK(batch2 != NULL); + + /** main test body **/ + ecount = 0; + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch1, tweaked_pk, tweaked_pk_parity, &pk, tweak) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(none, batch1) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, NULL, tweaked_pk_parity, &pk, tweak) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, tweaked_pk, tweaked_pk_parity, NULL, tweak) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, tweaked_pk, tweaked_pk_parity, &pk, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, NULL, tweaked_pk, tweaked_pk_parity, &pk, tweak) == 0); + CHECK(ecount == 4); + /** overflowing tweak not allowed **/ + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, tweaked_pk, tweaked_pk_parity, &pk, overflows) == 0); + CHECK(ecount == 4); + /** x-coordinate of tweaked pubkey should be less than prime order **/ + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, overflows, tweaked_pk_parity, &pk, tweak) == 0); + CHECK(ecount == 4); + + /** batch_verify should fail for incorrect tweak **/ + ecount = 0; + CHECK(secp256k1_batch_usable(none, batch2)); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, tweaked_pk, !tweaked_pk_parity, &pk, tweak) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(none, batch2) == 0); + CHECK(ecount == 0); + + /** batch_add_ should ignore unusable batch object (i.e, batch->result = 0) **/ + ecount = 0; + CHECK(secp256k1_batch_usable(none, batch2) == 0); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_xonlypub_tweak_check(none, batch2, tweaked_pk, tweaked_pk_parity, &pk, tweak) == 0); + CHECK(ecount == 0); + + ecount = 0; + secp256k1_batch_destroy(none, batch1); + CHECK(ecount == 0); + secp256k1_batch_destroy(none, batch2); + CHECK(ecount == 0); + + secp256k1_context_destroy(none); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(vrfy); +} + +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/extrakeys/bench_impl.h b/src/modules/extrakeys/bench_impl.h new file mode 100644 index 00000000000..411fd7339c7 --- /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; + +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 +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 + +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_verify") || have_flag(argc, argv, "tweak_check_batch_verify")) { + for (i = 1; i <= iters; i = (int)(i*1.2 + 1)) { + char name[64]; + int divisible_iters; + sprintf(name, "tweak_check_batch_verify_%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/Makefile.am.include b/src/modules/schnorrsig/Makefile.am.include index 654fa2e5ae5..2c211784fbc 100644 --- a/src/modules/schnorrsig/Makefile.am.include +++ b/src/modules/schnorrsig/Makefile.am.include @@ -1,5 +1,12 @@ 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 +noinst_HEADERS += src/modules/schnorrsig/batch_add_tests_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 00000000000..7c3da4a29d1 --- /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/modules/schnorrsig/batch_add_tests_impl.h b/src/modules/schnorrsig/batch_add_tests_impl.h new file mode 100644 index 00000000000..89c31222256 --- /dev/null +++ b/src/modules/schnorrsig/batch_add_tests_impl.h @@ -0,0 +1,313 @@ +#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 succed */ + 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 (TODO) 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_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_batch *batch1 = secp256k1_batch_create(none, 2*ONE_SIG, NULL); + /* batch2 is used when batch_add_schnorrsig is expected to fail */ + secp256k1_batch *batch2 = secp256k1_batch_create(none, 2*ONE_SIG, NULL); + int ecount; + + secp256k1_context_set_error_callback(none, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(sign, counting_callback_fn, &ecount); + secp256k1_context_set_error_callback(vrfy, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(none, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(vrfy, counting_callback_fn, &ecount); + + /** generate keypair data **/ + testrand256(sk); + CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(sign, &pk, NULL, &keypair) == 1); + memset(&zero_pk, 0, sizeof(zero_pk)); + + /** generate a signature **/ + testrand256(msg); + CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(vrfy, sig, msg, sizeof(msg), &pk)); + + CHECK(batch1 != NULL); + CHECK(batch2 != NULL); + + /** main test body **/ + ecount = 0; + CHECK(secp256k1_batch_add_schnorrsig(none, batch1, sig, msg, sizeof(msg), &pk) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(none, batch1) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_schnorrsig(none, batch2, NULL, msg, sizeof(msg), &pk) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_batch_add_schnorrsig(none, batch2, sig, NULL, sizeof(msg), &pk) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_batch_add_schnorrsig(none, batch2, sig, msg, sizeof(msg), NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_batch_add_schnorrsig(none, batch2, sig, msg, sizeof(msg), &zero_pk) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_batch_add_schnorrsig(none, NULL, sig, msg, sizeof(msg), &pk) == 0); + CHECK(ecount == 5); + + /** NULL msg with valid signature **/ + ecount = 0; + CHECK(secp256k1_schnorrsig_sign_custom(sign, nullmsg_sig, NULL, 0, &keypair, NULL) == 1); + CHECK(secp256k1_batch_usable(none, batch1) == 1); + CHECK(secp256k1_batch_add_schnorrsig(none, batch1, nullmsg_sig, NULL, 0, &pk) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(none, batch1) == 1); + + /** NULL msg with invalid signature **/ + CHECK(secp256k1_batch_usable(none, batch2) == 1); + CHECK(secp256k1_batch_add_schnorrsig(none, batch2, sig, NULL, 0, &pk) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_batch_verify(none, batch2) == 0); + + /** batch_add_ should ignore unusable batch object (i.e, batch->result = 0) **/ + ecount = 0; + CHECK(secp256k1_batch_usable(none, batch2) == 0); + CHECK(ecount == 0); + CHECK(secp256k1_batch_add_schnorrsig(none, batch2, sig, msg, sizeof(msg), &pk) == 0); + CHECK(ecount == 0); + + ecount = 0; + secp256k1_batch_destroy(CTX, batch1); + CHECK(ecount == 0); + secp256k1_batch_destroy(CTX, batch2); + CHECK(ecount == 0); + + secp256k1_context_destroy(none); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(vrfy); +} +#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/bench_impl.h b/src/modules/schnorrsig/bench_impl.h index 93a878ede3e..da2681cca65 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,7 +54,24 @@ static void bench_schnorrsig_verify(void* arg, int iters) { } } -static void run_schnorrsig_bench(int iters, int argc, char** argv) { +#ifdef ENABLE_MODULE_BATCH +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 + +void run_schnorrsig_bench(int iters, int argc, char** argv) { int i; bench_schnorrsig_data data; int d = argc == 1; @@ -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_verify") || have_flag(argc, argv, "schnorrsig_batch_verify")) { + for (i = 1; i <= iters; i = (int)(i*1.2 + 1)) { + char name[64]; + int divisible_iters; + sprintf(name, "schnorrsig_batch_verify_%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); } diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 2d716a01f89..0ffcc67bed4 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 returns 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. */ +void test_schnorrsig_bip_vectors_check_batch_verify(const unsigned char *pk_serialized, const unsigned char *msg32, 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, msg32, 32, &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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, sig, 0, 1); + #endif } { /* Test vector 14 */ @@ -851,8 +932,10 @@ 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. */ -static void test_schnorrsig_sign_verify(void) { + * 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) */ +void test_schnorrsig_sign_verify(void) { unsigned char sk[32]; unsigned char msg[N_SIGS][32]; unsigned char sig[N_SIGS][64]; diff --git a/src/scalar_impl.h b/src/scalar_impl.h index dbb5b0a01ca..0232a8c223e 100644 --- a/src/scalar_impl.h +++ b/src/scalar_impl.h @@ -90,11 +90,11 @@ static void secp256k1_scalar_split_lambda_verify(const secp256k1_scalar *r1, con #endif /* - * Both lambda and beta are primitive cube roots of unity. That is lamba^3 == 1 mod n and + * Both lambda and beta are primitive cube roots of unity. That is lambda^3 == 1 mod n and * beta^3 == 1 mod p, where n is the curve order and p is the field order. * * Furthermore, because (X^3 - 1) = (X - 1)(X^2 + X + 1), the primitive cube roots of unity are - * roots of X^2 + X + 1. Therefore lambda^2 + lamba == -1 mod n and beta^2 + beta == -1 mod p. + * roots of X^2 + X + 1. Therefore lambda^2 + lambda == -1 mod n and beta^2 + beta == -1 mod p. * (The other primitive cube roots of unity are lambda^2 and beta^2 respectively.) * * Let l = -1/2 + i*sqrt(3)/2, the complex root of X^2 + X + 1. We can define a ring diff --git a/src/secp256k1.c b/src/secp256k1.c index a248519dfd8..db02ebaa5e7 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -829,3 +829,13 @@ 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" +# 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 diff --git a/src/tests.c b/src/tests.c index 78533b11c28..f3a1b39dc08 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4857,38 +4857,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 */ +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). */ @@ -4903,25 +4880,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); @@ -4941,7 +4918,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; } @@ -5011,6 +4988,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; +} + +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 *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. */ @@ -5023,7 +5048,60 @@ static int test_ecmult_multi_random(secp256k1_scratch *scratch) { return mults; } -static void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { +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; +} + +void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { secp256k1_scalar sc; secp256k1_ge pt; secp256k1_gej r; @@ -5209,7 +5287,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(); @@ -5220,8 +5300,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); @@ -7441,10 +7524,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 @@ -7455,7 +7544,11 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ellswift/tests_impl.h" #endif -static void run_secp256k1_memczero_test(void) { +#ifdef ENABLE_MODULE_BATCH +# include "modules/batch/tests_impl.h" +#endif + +void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7809,10 +7902,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 @@ -7823,6 +7922,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(); diff --git a/src/util.h b/src/util.h index 88a4149421a..5f29f4076ce 100644 --- a/src/util.h +++ b/src/util.h @@ -232,7 +232,7 @@ static SECP256K1_INLINE void secp256k1_memclear(void *ptr, size_t len) { * As best as we can tell, this is sufficient to break any optimisations that * might try to eliminate "superfluous" memsets. * This method is used in memzero_explicit() the Linux kernel, too. Its advantage is that it - * is pretty efficient, because the compiler can still implement the memset() efficently, + * is pretty efficient, because the compiler can still implement the memset() efficiently, * just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by * Yang et al. (USENIX Security 2017) for more background. */ From af08cd227631263219df3b4f9ec2149c7402c006 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Tue, 11 Mar 2025 20:14:30 +0100 Subject: [PATCH 03/10] build: Enable secp256k1 experimental batch module --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89fdd855a45..2c799a3b1f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,8 @@ set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_ECDH OFF CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE BOOL "" FORCE) +set(SECP256K1_EXPERIMENTAL ON CACHE BOOL "" FORCE) +set(SECP256K1_ENABLE_MODULE_BATCH ON CACHE BOOL "" FORCE) set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) set(SECP256K1_BUILD_EXHAUSTIVE_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) From c20db73f9a5b6fca38e39c77131f28f483bb7686 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Tue, 11 Mar 2025 20:27:29 +0100 Subject: [PATCH 04/10] validation: Add BatchVerify (unused) --- src/CMakeLists.txt | 1 + src/batchverify.cpp | 56 +++++++++++++++++++++++++++++++++++++++ src/batchverify.h | 34 ++++++++++++++++++++++++ src/kernel/CMakeLists.txt | 1 + 4 files changed, 92 insertions(+) create mode 100644 src/batchverify.cpp create mode 100644 src/batchverify.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2c799a3b1f5..c8eba4fc733 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -110,6 +110,7 @@ endif() add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL addresstype.cpp base58.cpp + batchverify.cpp bech32.cpp chain.cpp chainparams.cpp diff --git a/src/batchverify.cpp b/src/batchverify.cpp new file mode 100644 index 00000000000..e3c17091cbe --- /dev/null +++ b/src/batchverify.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include + +#include +#include +#include + +// This is the maximum number of scalar-point pairs on the batch for which +// Strauss' algorithm, which is used in the secp256k1 implementation, is +// still efficient. TODO: This will be changed when Pippenger algorithm is +// being used in the secp implementation too. +const size_t MAX_BATCH_SIZE{106}; + +BatchSchnorrVerifier::BatchSchnorrVerifier() { + unsigned char rnd[16]; + GetRandBytes(rnd); + secp256k1_batch* batch{secp256k1_batch_create(secp256k1_context_static, MAX_BATCH_SIZE, rnd)}; + m_batch = batch; +} + +BatchSchnorrVerifier::~BatchSchnorrVerifier() { + (void)secp256k1_batch_destroy(secp256k1_context_static, m_batch); +} + +bool BatchSchnorrVerifier::Add(const Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) { + LOCK(m_batch_mutex); + if (secp256k1_batch_usable(secp256k1_context_static, m_batch) == 0) { + return false; + } + + secp256k1_xonly_pubkey pubkey_parsed; + (void)secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey_parsed, pubkey.data()); + + return secp256k1_batch_add_schnorrsig(secp256k1_context_static, m_batch, sig.data(), sighash.begin(), 32, &pubkey_parsed); +} + +bool BatchSchnorrVerifier::Verify() { + LOCK(m_batch_mutex); + return secp256k1_batch_verify(secp256k1_context_static, m_batch); +} + +void BatchSchnorrVerifier::Reset() { + LOCK(m_batch_mutex); + (void)secp256k1_batch_destroy(secp256k1_context_static, m_batch); + unsigned char rnd[16]; + GetRandBytes(rnd); + secp256k1_batch* batch{secp256k1_batch_create(secp256k1_context_static, MAX_BATCH_SIZE, rnd)}; + m_batch = batch; +} diff --git a/src/batchverify.h b/src/batchverify.h new file mode 100644 index 00000000000..1dd489d884e --- /dev/null +++ b/src/batchverify.h @@ -0,0 +1,34 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_BATCHVERIFY_H +#define BITCOIN_BATCHVERIFY_H + +#include +#include + +#include +#include + +struct SchnorrSignatureToVerify { + std::vector sig; + XOnlyPubKey pubkey; + uint256 sighash; +}; + +class BatchSchnorrVerifier { +private: + secp256k1_batch* m_batch GUARDED_BY(m_batch_mutex); + mutable Mutex m_batch_mutex; + +public: + BatchSchnorrVerifier(); + ~BatchSchnorrVerifier(); + + bool Add(const Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) EXCLUSIVE_LOCKS_REQUIRED(!m_batch_mutex); + bool Verify() EXCLUSIVE_LOCKS_REQUIRED(!m_batch_mutex); + void Reset() EXCLUSIVE_LOCKS_REQUIRED(!m_batch_mutex); +}; + +#endif // BITCOIN_BATCHVERIFY_H diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt index 2e07ba042a4..4030e30317c 100644 --- a/src/kernel/CMakeLists.txt +++ b/src/kernel/CMakeLists.txt @@ -17,6 +17,7 @@ add_library(bitcoinkernel disconnected_transactions.cpp mempool_removal_reason.cpp ../arith_uint256.cpp + ../batchverify.cpp ../chain.cpp ../coins.cpp ../compressor.cpp From 8ef690e36cb80a9f94480ba13f8e37669cf15a31 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Tue, 11 Mar 2025 21:06:09 +0100 Subject: [PATCH 05/10] script: Add batch validation error --- src/script/script_error.cpp | 2 ++ src/script/script_error.h | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index fadc04262c3..d8b664b8423 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -115,6 +115,8 @@ std::string ScriptErrorString(const ScriptError serror) return "Using OP_CODESEPARATOR in non-witness script"; case SCRIPT_ERR_SIG_FINDANDDELETE: return "Signature is found in scriptCode"; + case SCRIPT_ERR_BATCH_VALIDATION_FAILED: + return "Schnorr batch validation failed"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index 44e68fe0fae..569566e8409 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -78,6 +78,9 @@ typedef enum ScriptError_t SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG, SCRIPT_ERR_TAPSCRIPT_MINIMALIF, + /* Batch validation */ + SCRIPT_ERR_BATCH_VALIDATION_FAILED, + /* Constant scriptCode */ SCRIPT_ERR_OP_CODESEPARATOR, SCRIPT_ERR_SIG_FINDANDDELETE, From d333d5c6e3c4b454621998772ff74ff6656af691 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Tue, 11 Mar 2025 21:25:41 +0100 Subject: [PATCH 06/10] sigcache: Add CollectingSignatureChecker --- src/script/sigcache.cpp | 16 ++++++++++++++++ src/script/sigcache.h | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 33531e6bf58..78c1dfcf594 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -5,6 +5,7 @@ #include