diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9783251d65..06e798c681 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ env: EXTRAKEYS: 'no' SCHNORRSIG: 'no' MUSIG: 'no' + SCHNORRSIG_FULLAGG: 'no' ELLSWIFT: 'no' ### test options SECP256K1_TEST_ITERS: 64 @@ -84,14 +85,14 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } @@ -140,6 +141,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CC: ${{ matrix.cc }} @@ -172,6 +175,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -212,6 +217,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -243,6 +250,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -285,6 +294,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -346,6 +357,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -385,6 +398,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -438,6 +453,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CC: 'clang' @@ -474,6 +491,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -519,14 +538,14 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' @@ -573,13 +592,13 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', SCHNORRSIG_FULLAGG: 'yes', CPPFLAGS: '-DVERIFY' } - BUILD: 'distcheck' steps: @@ -703,6 +722,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' diff --git a/.gitignore b/.gitignore index ce33a84adf..645f391a22 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ ecdsa_example schnorr_example ellswift_example musig_example +fullagg_example *.exe *.so *.a diff --git a/CMakeLists.txt b/CMakeLists.txt index 11dc3f6e53..c0e7c330e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." O option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) +option(SECP256K1_ENABLE_MODULE_SCHNORRSIG_FULLAGG "Enable schnorrsig full-aggregation module." OFF) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) @@ -284,6 +285,7 @@ message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOV message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}") message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") +message(" schnorrsig fullagg .................. ${SECP256K1_ENABLE_MODULE_SCHNORRSIG_FULLAGG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") diff --git a/Makefile.am b/Makefile.am index d511853b05..0761b5b4bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -207,6 +207,17 @@ musig_example_LDFLAGS += -lbcrypt endif TESTS += musig_example endif +if ENABLE_MODULE_SCHNORRSIG_FULLAGG +noinst_PROGRAMS += fullagg_example +fullagg_example_SOURCES = examples/fullagg.c +fullagg_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC +fullagg_example_LDADD = libsecp256k1.la +fullagg_example_LDFLAGS = -static +if BUILD_WINDOWS +fullagg_example_LDFLAGS += -lbcrypt +endif +TESTS += fullagg_example +endif endif ### Precomputed tables @@ -308,6 +319,10 @@ if ENABLE_MODULE_MUSIG include src/modules/musig/Makefile.am.include endif +if ENABLE_MODULE_SCHNORRSIG_FULLAGG +include src/modules/schnorrsig_fullagg/Makefile.am.include +endif + if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif diff --git a/README.md b/README.md index 90edae1a2c..7ee286feb5 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 Schnorr signatures full aggregation according to [TODO](). Implementation details ---------------------- @@ -150,6 +151,7 @@ Usage examples can be found in the [examples](examples) directory. To compile th * [Deriving a shared secret (ECDH) example](examples/ecdh.c) * [ElligatorSwift key exchange example](examples/ellswift.c) * [MuSig2 Schnorr multi-signatures example](examples/musig.c) + * [Schnorr signatures full aggregation example](examples/fullagg.c) To compile the examples, make sure the corresponding modules are enabled. diff --git a/ci/ci.sh b/ci/ci.sh index 08e84efd4f..5ee46ca9c6 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -13,7 +13,7 @@ print_environment() { # does not rely on bash. for var in WERROR_CFLAGS MAKEFLAGS BUILD \ ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ - EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT \ + EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT SCHNORRSIG_FULLAGG\ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \ EXAMPLES \ HOST WRAPPER_CMD \ @@ -80,6 +80,7 @@ esac --enable-module-extrakeys="$EXTRAKEYS" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-musig="$MUSIG" \ + --enable-module-schnorrsig-fullagg="$SCHNORRSIG_FULLAGG" \ --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ diff --git a/configure.ac b/configure.ac index 2f156ddc25..0d315bcb30 100644 --- a/configure.ac +++ b/configure.ac @@ -187,6 +187,10 @@ AC_ARG_ENABLE(module_musig, AS_HELP_STRING([--enable-module-musig],[enable MuSig2 module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_musig], [yes], [yes])]) +AC_ARG_ENABLE(module_schnorrsig_fullagg, + AS_HELP_STRING([--enable-module-schnorrsig-fullagg],[enable schnorrsig full-aggregation module (experimental) [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_schnorrsig_fullagg], [no], [yes])]) + AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) @@ -397,6 +401,14 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" # Processing must be done in a reverse topological sorting of the dependency graph # (dependent module first). +if test x"$enable_module_schnorrsig_fullagg" = x"yes"; then + if test x"$enable_module_schnorrsig" = x"no"; then + AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the schnorrsig fullagg module.]) + fi + enable_module_schnorrsig=yes + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SCHNORRSIG_FULLAGG=1" +fi + if test x"$enable_module_ellswift" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi @@ -441,6 +453,10 @@ 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_schnorrsig_fullagg" = x"yes"; then + AC_MSG_ERROR([Schnorrsig Full-Aggregation module is experimental. Use --enable-experimental to allow.]) + fi fi ### @@ -460,6 +476,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_FULLAGG], [test x"$enable_module_schnorrsig_fullagg" = 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([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) @@ -473,34 +490,35 @@ AC_OUTPUT echo echo "Build Options:" -echo " with external callbacks = $enable_external_default_callbacks" -echo " with benchmarks = $enable_benchmark" -echo " with tests = $enable_tests" -echo " with exhaustive tests = $enable_exhaustive_tests" -echo " with ctime tests = $enable_ctime_tests" -echo " with coverage = $enable_coverage" -echo " with examples = $enable_examples" -echo " module ecdh = $enable_module_ecdh" -echo " module recovery = $enable_module_recovery" -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 " with external callbacks = $enable_external_default_callbacks" +echo " with benchmarks = $enable_benchmark" +echo " with tests = $enable_tests" +echo " with exhaustive tests = $enable_exhaustive_tests" +echo " with ctime tests = $enable_ctime_tests" +echo " with coverage = $enable_coverage" +echo " with examples = $enable_examples" +echo " module ecdh = $enable_module_ecdh" +echo " module recovery = $enable_module_recovery" +echo " module extrakeys = $enable_module_extrakeys" +echo " module schnorrsig = $enable_module_schnorrsig" +echo " module schnorrsig-fullagg = $enable_module_schnorrsig_fullagg" +echo " module musig = $enable_module_musig" +echo " module ellswift = $enable_module_ellswift" echo -echo " asm = $set_asm" -echo " ecmult window size = $set_ecmult_window" -echo " ecmult gen table size = $set_ecmult_gen_kb KiB" +echo " asm = $set_asm" +echo " ecmult window size = $set_ecmult_window" +echo " ecmult gen table size = $set_ecmult_gen_kb KiB" # Hide test-only options unless they're used. if test x"$set_widemul" != xauto; then -echo " wide multiplication = $set_widemul" +echo " wide multiplication = $set_widemul" fi echo -echo " valgrind = $enable_valgrind" -echo " CC = $CC" -echo " CPPFLAGS = $CPPFLAGS" -echo " SECP_CFLAGS = $SECP_CFLAGS" -echo " CFLAGS = $CFLAGS" -echo " LDFLAGS = $LDFLAGS" +echo " valgrind = $enable_valgrind" +echo " CC = $CC" +echo " CPPFLAGS = $CPPFLAGS" +echo " SECP_CFLAGS = $SECP_CFLAGS" +echo " CFLAGS = $CFLAGS" +echo " LDFLAGS = $LDFLAGS" if test x"$print_msan_notice" = x"yes"; then echo diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c9da9de6be..665ca483f2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if(SECP256K1_ENABLE_MODULE_MUSIG) add_example(musig) endif() + +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG_FULLAGG) + add_example(fullagg) +endif() diff --git a/examples/fullagg.c b/examples/fullagg.c new file mode 100644 index 0000000000..c752d70f80 --- /dev/null +++ b/examples/fullagg.c @@ -0,0 +1,309 @@ +/************************************************************************* + * Written in 2025 by Fabian Jahr * + * To the extent possible under law, the author(s) have dedicated all * + * copyright and related and neighboring rights to the software in this * + * file to the public domain worldwide. This software is distributed * + * without any warranty. For the CC0 Public Domain Dedication, see * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +/** This file demonstrates how to use the FullAgg module to create an + * aggregate signature where each signer signs a different message. + * + * FullAgg (DahLIAS) allows multiple signers to create a single aggregate + * signature that proves each signer signed their respective message. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "examples_util.h" + +#define N_SIGNERS 3 + +struct signer_secrets { + secp256k1_keypair keypair; + secp256k1_fullagg_secnonce secnonce; +}; + +struct signer { + secp256k1_pubkey pubkey; + secp256k1_fullagg_pubnonce pubnonce; + secp256k1_fullagg_partial_sig partial_sig; + unsigned char message[32]; +}; + +/* Create a key pair, store it in signer_secrets->keypair and signer->pubkey */ +static int create_keypair(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) { + unsigned char seckey[32]; + + if (!fill_random(seckey, sizeof(seckey))) { + printf("Failed to generate randomness\n"); + return 0; + } + /* Try to create a keypair with a valid context. This only fails if the + * secret key is zero or out of range (greater than secp256k1's order). Note + * that the probability of this occurring is negligible with a properly + * functioning random number generator. */ + if (!secp256k1_keypair_create(ctx, &signer_secrets->keypair, seckey)) { + return 0; + } + if (!secp256k1_keypair_pub(ctx, &signer->pubkey, &signer_secrets->keypair)) { + return 0; + } + + secure_erase(seckey, sizeof(seckey)); + return 1; +} + +static void setup_messages(struct signer *signers) { + memset(signers[0].message, 0, 32); + memset(signers[1].message, 0, 32); + memset(signers[2].message, 0, 32); + memcpy(signers[0].message, "jonas", 5); + memcpy(signers[1].message, "tim", 3); + memcpy(signers[2].message, "yannick", 7); +} + +/* Each signer generates their nonce pair (R1_i, R2_i) */ +static int nonce_generation_round(const secp256k1_context* ctx, + struct signer_secrets *signer_secrets, + struct signer *signers) { + int i; + for (i = 0; i < N_SIGNERS; i++) { + unsigned char seckey[32]; + unsigned char session_secrand[32]; + + /* Create random session ID. It is absolutely necessary that the session ID + * is unique for every call of secp256k1_fullagg_nonce_gen. Otherwise + * it's trivial for an attacker to extract the secret key! */ + if (!fill_random(session_secrand, sizeof(session_secrand))) { + return 0; + } + if (!secp256k1_keypair_sec(ctx, seckey, &signer_secrets[i].keypair)) { + return 0; + } + + /* Initialize session and create secret nonce for signing and public + * nonce to send to the coordinator. Each signer provides their own + * message here, which binds the nonce to their specific message. */ + if (!secp256k1_fullagg_nonce_gen(ctx, &signer_secrets[i].secnonce, &signers[i].pubnonce, + session_secrand, seckey, &signers[i].pubkey, + signers[i].message, NULL)) { + return 0; + } + + secure_erase(seckey, sizeof(seckey)); + } + return 1; +} + +/* Each signer creates their partial signature */ +static int partial_signing_round(const secp256k1_context* ctx, + struct signer_secrets *signer_secrets, + struct signer *signers, + const secp256k1_fullagg_session *session, + const secp256k1_pubkey **pubkeys, + const unsigned char **messages, + const secp256k1_fullagg_pubnonce **pubnonces) { + int i; + for (i = 0; i < N_SIGNERS; i++) { + /* Each signer computes their partial signature: + * - First computes their challenge c_i = H_sig(L, R, X_i, m_i) where L is the list of all (Xi, mi) pairs + * - Then computes s_i = r1_i + b*r2_i + c_i*x_i + * The partial_sign function will clear the secnonce by setting it to 0. That's because + * you must _never_ reuse the secnonce. If you do, you effectively reuse the nonce and + * leak the secret key. */ + if (!secp256k1_fullagg_partial_sign(ctx, &signers[i].partial_sig, + &signer_secrets[i].secnonce, + &signer_secrets[i].keypair, + session, pubkeys, messages, + pubnonces, i)) { + return 0; + } + } + return 1; +} + +/* Verify each partial signature individually */ +static int verify_partial_signatures(const secp256k1_context* ctx, + struct signer *signers, + const secp256k1_fullagg_session *session, + const secp256k1_pubkey **pubkeys, + const unsigned char **messages) { + int i; + /* The coordinator can optionally verify each partial signature individually + * before aggregating them. This helps identify which signer(s) may have + * produced invalid signatures if the aggregate signature fails to verify. */ + for (i = 0; i < N_SIGNERS; i++) { + if (!secp256k1_fullagg_partial_sig_verify(ctx, &signers[i].partial_sig, + &signers[i].pubnonce, + &signers[i].pubkey, + session, pubkeys, messages, i)) { + printf("Partial signature %d failed to verify\n", i); + return 0; + } + } + return 1; +} + +int main(void) { + secp256k1_context* ctx; + int i; + struct signer_secrets signer_secrets[N_SIGNERS]; + struct signer signers[N_SIGNERS]; + const secp256k1_pubkey *pubkeys[N_SIGNERS]; + const unsigned char *messages[N_SIGNERS]; + const secp256k1_fullagg_pubnonce *pubnonces[N_SIGNERS]; + const secp256k1_fullagg_partial_sig *partial_sigs[N_SIGNERS]; + secp256k1_fullagg_session session; + secp256k1_fullagg_aggnonce agg_pubnonce; + unsigned char sig[64]; + + ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + + printf("\n=== Running FullAgg Example ===\n"); + printf("Creating a single signature for %d signers with different messages\n\n", N_SIGNERS); + + printf("Signers: Creating key pairs... "); + fflush(stdout); + for (i = 0; i < N_SIGNERS; i++) { + if (!create_keypair(ctx, &signer_secrets[i], &signers[i])) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + pubkeys[i] = &signers[i].pubkey; + } + printf("ok\n"); + + printf("Signers: Setting up messages... "); + fflush(stdout); + setup_messages(signers); + for (i = 0; i < N_SIGNERS; i++) { + messages[i] = signers[i].message; + } + printf("ok\n"); + + printf("Signers: Generating nonces... "); + fflush(stdout); + /* In FullAgg, we use two nonces per signer to prevent rogue-key attacks + * without requiring a proof of possession. */ + if (!nonce_generation_round(ctx, signer_secrets, signers)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + for (i = 0; i < N_SIGNERS; i++) { + pubnonces[i] = &signers[i].pubnonce; + } + printf("ok\n"); + + printf("Coordinator: Aggregating nonces... "); + fflush(stdout); + /* The coordinator (can be any party) collects all public nonces and + * aggregates them: R1 = sum(R1_i), R2 = sum(R2_i) */ + if (!secp256k1_fullagg_nonce_agg(ctx, &agg_pubnonce, pubnonces, N_SIGNERS)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + + printf("All: Initializing session... "); + fflush(stdout); + /* The session computes: + * - The nonce coefficient b = H_non(R1, R2, all Xi, all mi, all R2_i) + * - The final nonce R = R1 + b*R2 + * This binds the final nonce to all signers' keys, messages, and individual nonces. */ + if (!secp256k1_fullagg_session_init(ctx, &session, &agg_pubnonce, + pubkeys, messages, pubnonces, N_SIGNERS)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + + printf("Signers: Creating partial signatures... "); + fflush(stdout); + if (!partial_signing_round(ctx, signer_secrets, signers, &session, + pubkeys, messages, pubnonces)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + for (i = 0; i < N_SIGNERS; i++) { + partial_sigs[i] = &signers[i].partial_sig; + } + printf("ok\n"); + + printf("Coordinator: Verifying partial signatures... "); + fflush(stdout); + if (!verify_partial_signatures(ctx, signers, &session, pubkeys, messages)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + + printf("Coordinator: Aggregating signatures... "); + fflush(stdout); + /* The final signature is (R, s) where s = sum(s_i) */ + if (!secp256k1_fullagg_partial_sig_agg(ctx, sig, &session, partial_sigs, N_SIGNERS)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + + printf("All: Verifying aggregate signature... "); + fflush(stdout); + /* Verify the aggregate signature against all public keys and their messages. + * The verification checks that s*G = R + sum(c_i*X_i) where each c_i is + * computed from the specific message m_i that signer i signed. */ + if (!secp256k1_fullagg_verify(ctx, sig, pubkeys, messages, N_SIGNERS)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + + /* Test that the signature is specific to these exact messages */ + printf("Testing message binding... "); + fflush(stdout); + + /* Modify one message to verify the signature is bound to specific messages */ + signers[1].message[0] ^= 0xFF; + + if (secp256k1_fullagg_verify(ctx, sig, pubkeys, messages, N_SIGNERS)) { + printf("FAILED (signature verified with modified message)\n"); + return EXIT_FAILURE; + } + + /* Restore the original message and verify it works again */ + signers[1].message[0] ^= 0xFF; + + if (!secp256k1_fullagg_verify(ctx, sig, pubkeys, messages, N_SIGNERS)) { + printf("FAILED (signature doesn't verify after restoring)\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + + printf("\nFinal Aggregate Signature: "); + for (i = 0; i < 64; i++) { + printf("%02x", sig[i]); + if (i == 31) printf(" "); + } + printf("\n"); + + /* It's best practice to try to clear secrets from memory after using them. + * This is done because some bugs can allow an attacker to leak memory, for + * example through "out of bounds" array access (see Heartbleed), or the OS + * swapping them to disk. Hence, we overwrite secret key material with zeros. + * + * Here we are preventing these writes from being optimized out, as any good compiler + * will remove any writes that aren't used. */ + for (i = 0; i < N_SIGNERS; i++) { + secure_erase(&signer_secrets[i], sizeof(signer_secrets[i])); + } + secp256k1_context_destroy(ctx); + return EXIT_SUCCESS; +} diff --git a/include/secp256k1_schnorrsig_fullagg.h b/include/secp256k1_schnorrsig_fullagg.h new file mode 100644 index 0000000000..80a5036a71 --- /dev/null +++ b/include/secp256k1_schnorrsig_fullagg.h @@ -0,0 +1,433 @@ +#ifndef SECP256K1_FULLAGG_H +#define SECP256K1_FULLAGG_H + +#include "secp256k1_extrakeys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** This module implements Full Aggregation (FullAgg/DahLIAS) for Schnorr + * signatures, allowing multiple signers to each sign different messages + * and aggregate their signatures into a single signature. + */ + +/** Opaque data structures + * + * The exact representation of data inside the opaque data structures is + * implementation defined and not guaranteed to be portable between different + * platforms or versions. With the exception of `secp256k1_fullagg_secnonce`, + * the data structures can be safely copied/moved. If you need to convert to a + * format suitable for storage, transmission, or comparison, use the + * corresponding serialization and parsing functions. + */ + +/** Opaque data structure that holds a signer's _secret_ nonce (r1_i and r2_i). + * + * Guaranteed to be 132 bytes in size. + * + * WARNING: This structure MUST NOT be copied or read or written to directly. + * A signer who is online throughout the whole process and can keep this + * structure in memory can use the provided API functions for a safe standard + * workflow. + * + * Copying this data structure can result in nonce reuse which will leak the + * secret signing key. + */ +typedef struct secp256k1_fullagg_secnonce { + unsigned char data[132]; +} secp256k1_fullagg_secnonce; + +/** Opaque data structure that holds a signer's public nonce (R1_i and R2_i). + * + * Guaranteed to be 132 bytes in size. Serialized and parsed with + * `fullagg_pubnonce_serialize` and `fullagg_pubnonce_parse`. + */ +typedef struct secp256k1_fullagg_pubnonce { + unsigned char data[132]; +} secp256k1_fullagg_pubnonce; + +/** Opaque data structure that holds an aggregate public nonce (aggregated R1 and R2). + * + * Guaranteed to be 132 bytes in size. Serialized and parsed with + * `fullagg_aggnonce_serialize` and `fullagg_aggnonce_parse`. + */ +typedef struct secp256k1_fullagg_aggnonce { + unsigned char data[132]; +} secp256k1_fullagg_aggnonce; + +/** Opaque data structure that holds a FullAgg session. + * + * This structure contains the computed values needed for signing: + * the final nonce R, the nonce coefficient b, and the number of signers. + * + * Guaranteed to be 77 bytes in size. + */ +typedef struct secp256k1_fullagg_session { + unsigned char data[77]; +} secp256k1_fullagg_session; + +/** Opaque data structure that holds a partial FullAgg signature (s_i). + * + * Guaranteed to be 36 bytes in size. Serialized and parsed with + * `fullagg_partial_sig_serialize` and `fullagg_partial_sig_parse`. + */ +typedef struct secp256k1_fullagg_partial_sig { + unsigned char data[36]; +} secp256k1_fullagg_partial_sig; + +/** Parse a signer's public nonce. + * + * Returns: 1 when the nonce could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: nonce: pointer to a nonce object + * In: in66: pointer to the 66-byte nonce to be parsed + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_pubnonce_parse( + const secp256k1_context *ctx, + secp256k1_fullagg_pubnonce *nonce, + const unsigned char *in66 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a signer's public nonce + * + * Returns: 1 always + * Args: ctx: pointer to a context object + * Out: out66: pointer to a 66-byte array to store the serialized nonce + * In: nonce: pointer to the nonce + */ +SECP256K1_API int secp256k1_fullagg_pubnonce_serialize( + const secp256k1_context *ctx, + unsigned char *out66, + const secp256k1_fullagg_pubnonce *nonce +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse an aggregate public nonce. + * + * Returns: 1 when the nonce could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: nonce: pointer to a nonce object + * In: in66: pointer to the 66-byte nonce to be parsed + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_aggnonce_parse( + const secp256k1_context *ctx, + secp256k1_fullagg_aggnonce *nonce, + const unsigned char *in66 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize an aggregate public nonce + * + * Returns: 1 always + * Args: ctx: pointer to a context object + * Out: out66: pointer to a 66-byte array to store the serialized nonce + * In: nonce: pointer to the nonce + */ +SECP256K1_API int secp256k1_fullagg_aggnonce_serialize( + const secp256k1_context *ctx, + unsigned char *out66, + const secp256k1_fullagg_aggnonce *nonce +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a FullAgg partial signature. + * + * Returns: 1 when the signature could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: sig: pointer to a signature object + * In: in32: pointer to the 32-byte signature to be parsed + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_partial_sig_parse( + const secp256k1_context *ctx, + secp256k1_fullagg_partial_sig *sig, + const unsigned char *in32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a FullAgg partial signature + * + * Returns: 1 always + * Args: ctx: pointer to a context object + * Out: out32: pointer to a 32-byte array to store the serialized signature + * In: sig: pointer to the signature + */ +SECP256K1_API int secp256k1_fullagg_partial_sig_serialize( + const secp256k1_context *ctx, + unsigned char *out32, + const secp256k1_fullagg_partial_sig *sig +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Starts a signing session by generating a nonce + * + * This function outputs a secret nonce that will be required for signing and a + * corresponding public nonce that is intended to be sent to the coordinator. + * + * FullAgg differs from regular Schnorr signing in that implementers _must_ take + * special care to not reuse a nonce. This can be ensured by following these rules: + * + * 1. Each call to this function must have a UNIQUE session_secrand32 that must + * NOT BE REUSED in subsequent calls to this function and must be KEPT + * SECRET (even from other signers). + * 2. If you already know the seckey, message or public key, they can be + * optionally provided to derive the nonce and increase misuse-resistance. + * The extra_input32 argument can be used to provide additional data that + * does not repeat in normal scenarios, such as the current time. + * 3. Avoid copying (or serializing) the secnonce. This reduces the possibility + * that it is used more than once for signing. + * + * If you don't have access to good randomness for session_secrand32, but you + * have access to a non-repeating counter, then see + * secp256k1_fullagg_nonce_gen_counter. + * + * Remember that nonce reuse will leak the secret key! + * Note that using the same seckey for multiple FullAgg sessions is fine. + * + * Returns: 0 if the arguments are invalid and 1 otherwise + * Args: ctx: pointer to a context object (not secp256k1_context_static) + * Out: secnonce: pointer to a structure to store the secret nonce (r1_i, r2_i) + * pubnonce: pointer to a structure to store the public nonce (R1_i, R2_i) + * In/Out: + * session_secrand32: a 32-byte session_secrand32 as explained above. Must be unique to + * this call to secp256k1_fullagg_nonce_gen and must be + * uniformly random. If the function call is successful, the + * session_secrand32 buffer is invalidated to prevent reuse. + * In: + * seckey: the 32-byte secret key that will later be used for signing, if + * already known (can be NULL) + * pubkey: public key of the signer creating the nonce. The secnonce + * output of this function cannot be used to sign for any + * other public key. While the public key should correspond + * to the provided seckey, a mismatch will not cause the + * function to return 0. + * msg32: the 32-byte message that this signer will later sign, if + * already known (can be NULL) + * extra_input32: an optional 32-byte array that is input to the nonce + * derivation function (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_nonce_gen( + const secp256k1_context *ctx, + secp256k1_fullagg_secnonce *secnonce, + secp256k1_fullagg_pubnonce *pubnonce, + unsigned char *session_secrand32, + const unsigned char *seckey, + const secp256k1_pubkey *pubkey, + const unsigned char *msg32, + const unsigned char *extra_input32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6); + +/** Alternative way to generate a nonce and start a signing session + * + * This function outputs a secret nonce that will be required for signing and a + * corresponding public nonce that is intended to be sent to the coordinator. + * + * This function differs from `secp256k1_fullagg_nonce_gen` by accepting a + * non-repeating counter value instead of a secret random value. This requires + * that a secret key is provided to `secp256k1_fullagg_nonce_gen_counter` + * (through the keypair argument). + * + * Returns: 0 if the arguments are invalid and 1 otherwise + * Args: ctx: pointer to a context object (not secp256k1_context_static) + * Out: secnonce: pointer to a structure to store the secret nonce + * pubnonce: pointer to a structure to store the public nonce + * In: + * nonrepeating_cnt: the value of a counter as explained above. Must be + * unique to this call to secp256k1_fullagg_nonce_gen. + * keypair: keypair of the signer creating the nonce. The secnonce + * output of this function cannot be used to sign for any + * other keypair. + * msg32: the 32-byte message that this signer will later sign, if + * already known (can be NULL) + * extra_input32: an optional 32-byte array that is input to the nonce + * derivation function (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_nonce_gen_counter( + const secp256k1_context *ctx, + secp256k1_fullagg_secnonce *secnonce, + secp256k1_fullagg_pubnonce *pubnonce, + uint64_t nonrepeating_cnt, + const secp256k1_keypair *keypair, + const unsigned char *msg32, + const unsigned char *extra_input32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); + +/** Aggregates the nonces of all signers into a single nonce + * + * This is done by the coordinator to compute the aggregate nonces R1 and R2 + * from all signers' individual nonces R1_i and R2_i. + * + * If the aggregator does not compute the aggregate nonce correctly, the final + * signature will be invalid. + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: aggnonce: pointer to an aggregate public nonce object for + * fullagg_session_init (contains R1 and R2) + * In: pubnonces: array of pointers to public nonces sent by the + * signers (each containing R1_i and R2_i) + * n_pubnonces: number of elements in the pubnonces array. Must be + * greater than 0. + */ +SECP256K1_API int secp256k1_fullagg_nonce_agg( + const secp256k1_context *ctx, + secp256k1_fullagg_aggnonce *aggnonce, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_pubnonces +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Initialize a FullAgg signing session + * + * Creates a session context that contains the computed values needed for + * signing and verification of partial signatures. This includes the final + * nonce R and the nonce coefficient b. The arrays of public keys, messages, + * and nonces must be provided again when signing or verifying. + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: session: pointer to a struct to store the session + * In: aggnonce: pointer to an aggregate public nonce object that is the + * output of fullagg_nonce_agg + * pubkeys: array of pointers to the public keys of all signers. + * The order must be consistent across all signers and + * the coordinator. + * messages: array of pointers to 32-byte messages, where messages[i] + * is the message to be signed by the signer with pubkeys[i] + * pubnonces: array of pointers to public nonces, where pubnonces[i] + * contains the R2_i value from the signer with pubkeys[i]. + * This is needed for the context. + * n_signers: number of signers (length of pubkeys, messages, and + * pubnonces arrays). Must be greater than 0. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_session_init( + const secp256k1_context *ctx, + secp256k1_fullagg_session *session, + const secp256k1_fullagg_aggnonce *aggnonce, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_signers +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Produces a partial signature + * + * This function overwrites the given secnonce with zeros and will abort if given a + * secnonce that is all zeros. This is a best effort attempt to protect against nonce + * reuse. However, this is of course easily defeated if the secnonce has been + * copied (or serialized). Remember that nonce reuse will leak the secret key! + * + * For signing to succeed, the secnonce provided to this function must have + * been generated for the provided keypair. + * + * The signer must ensure that their public key, message, and R2_i nonce + * appear exactly once in the arrays at the expected position. + * + * Returns: 0 if the arguments are invalid or the provided secnonce has already + * been used for signing, 1 otherwise + * Args: ctx: pointer to a context object + * Out: partial_sig: pointer to struct to store the partial signature + * In/Out: secnonce: pointer to the secnonce struct created in + * fullagg_nonce_gen that has never been used in a + * partial_sign call before + * In: keypair: pointer to keypair to sign the message with + * session: pointer to the session that was created with + * fullagg_session_init + * pubkeys: array of pointers to public keys (same as in session_init) + * messages: array of pointers to messages (same as in session_init) + * pubnonces: array of pointers to public nonces (same as in session_init) + * signer_index: the index of this signer in the arrays (0-indexed) + */ +SECP256K1_API int secp256k1_fullagg_partial_sign( + const secp256k1_context *ctx, + secp256k1_fullagg_partial_sig *partial_sig, + secp256k1_fullagg_secnonce *secnonce, + const secp256k1_keypair *keypair, + const secp256k1_fullagg_session *session, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t signer_index +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); + +/** Verifies an individual signer's partial signature + * + * The signature is verified for a specific signing session. + * + * It is not required to call this function in regular FullAgg sessions, + * because if any partial signature does not verify, the final signature + * will not verify either. However, this function provides the ability to + * identify which specific partial signature fails verification. + * + * Note: The arrays of public keys and messages must be provided again + * (in the same order as in session_init) because the session only stores the + * computed values, not the full context. + * + * Returns: 0 if the arguments are invalid or the partial signature does not + * verify, 1 otherwise + * Args ctx: pointer to a context object + * In: partial_sig: pointer to partial signature to verify + * pubnonce: public nonce of the signer being verified + * pubkey: public key of the signer being verified + * session: pointer to the session that was created with + * fullagg_session_init + * pubkeys: array of pointers to public keys (same as in session_init) + * messages: array of pointers to messages (same as in session_init) + * signer_index: the index of the signer being verified in the arrays + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_partial_sig_verify( + const secp256k1_context *ctx, + const secp256k1_fullagg_partial_sig *partial_sig, + const secp256k1_fullagg_pubnonce *pubnonce, + const secp256k1_pubkey *pubkey, + const secp256k1_fullagg_session *session, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t signer_index +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7); + +/** Aggregates partial signatures + * + * Produces the final aggregated signature from all partial signatures. + * + * Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean + * the resulting signature verifies). + * Args: ctx: pointer to a context object + * Out: sig64: complete (but possibly invalid) Schnorr signature + * In: session: pointer to the session that was created with + * fullagg_session_init + * partial_sigs: array of pointers to partial signatures to aggregate + * n_sigs: number of elements in the partial_sigs array. Must be + * greater than 0 and equal to the number of signers. + */ +SECP256K1_API int secp256k1_fullagg_partial_sig_agg( + const secp256k1_context *ctx, + unsigned char *sig64, + const secp256k1_fullagg_session *session, + const secp256k1_fullagg_partial_sig * const *partial_sigs, + size_t n_sigs +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Verify a FullAgg aggregate signature + * + * Verifies that the signature is valid for the given list of public keys + * and their corresponding messages. + * + * Returns: 1 if the signature is valid, 0 otherwise + * Args: ctx: pointer to a context object + * In: sig64: pointer to the 64-byte signature to verify + * pubkeys: array of pointers to public keys + * messages: array of pointers to 32-byte messages, where messages[i] + * corresponds to pubkeys[i] + * n_signers: number of signers (length of pubkeys and messages arrays) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_fullagg_verify( + const secp256k1_context *ctx, + const unsigned char *sig64, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa3b2903eb..329b34ddb5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,19 @@ if(SECP256K1_ENABLE_MODULE_MUSIG) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_musig.h) endif() +option(SECP256K1_EXPERIMENTAL "Allow experimental configuration options." OFF) +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG_FULLAGG) + if(NOT SECP256K1_EXPERIMENTAL) + message(FATAL_ERROR "Schnorrsig full-aggregation 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 full-aggregation module.") + endif() + set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON) + add_compile_definitions(ENABLE_MODULE_SCHNORRSIG_FULLAGG=1) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig_fullagg.h) +endif() + if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) if(DEFINED SECP256K1_ENABLE_MODULE_EXTRAKEYS AND NOT SECP256K1_ENABLE_MODULE_EXTRAKEYS) message(FATAL_ERROR "Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig module.") diff --git a/src/eckey.h b/src/eckey.h index d54d44c997..c62d23f150 100644 --- a/src/eckey.h +++ b/src/eckey.h @@ -17,6 +17,9 @@ static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size); static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed); +static void secp256k1_eckey_serialize_ext(unsigned char *out33, secp256k1_ge* ge); +static int secp256k1_eckey_parse_ext(secp256k1_ge* ge, const unsigned char *in33); + static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak); static int secp256k1_eckey_pubkey_tweak_add(secp256k1_ge *key, const secp256k1_scalar *tweak); static int secp256k1_eckey_privkey_tweak_mul(secp256k1_scalar *key, const secp256k1_scalar *tweak); diff --git a/src/eckey_impl.h b/src/eckey_impl.h index a88a5964d8..67816e19a3 100644 --- a/src/eckey_impl.h +++ b/src/eckey_impl.h @@ -55,6 +55,39 @@ static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *p return 1; } +/* Outputs 33 zero bytes if the given group element is the point at infinity, + * otherwise outputs the compressed serialization */ +static void secp256k1_eckey_serialize_ext(unsigned char *out33, secp256k1_ge* ge) { + if (secp256k1_ge_is_infinity(ge)) { + memset(out33, 0, 33); + } else { + int ret; + size_t size = 33; + ret = secp256k1_eckey_pubkey_serialize(ge, out33, &size, 1); +#ifdef VERIFY + /* Serialize must succeed because the point is not at infinity */ + VERIFY_CHECK(ret && size == 33); +#else + (void) ret; +#endif + } +} + +/* Outputs the point at infinity if the given byte array is all zero, + * otherwise attempts to parse compressed point serialization */ +static int secp256k1_eckey_parse_ext(secp256k1_ge* ge, const unsigned char *in33) { + unsigned char zeros[33] = { 0 }; + + if (secp256k1_memcmp_var(in33, zeros, sizeof(zeros)) == 0) { + secp256k1_ge_set_infinity(ge); + return 1; + } + if (!secp256k1_eckey_pubkey_parse(ge, in33, 33)) { + return 0; + } + return secp256k1_ge_is_in_correct_subgroup(ge); +} + static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak) { secp256k1_scalar_add(key, key, tweak); return !secp256k1_scalar_is_zero(key); diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 2c8778b3c0..a2403f9ebd 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -19,39 +19,6 @@ #include "../../scalar.h" #include "../../util.h" -/* Outputs 33 zero bytes if the given group element is the point at infinity and - * otherwise outputs the compressed serialization */ -static void secp256k1_musig_ge_serialize_ext(unsigned char *out33, secp256k1_ge* ge) { - if (secp256k1_ge_is_infinity(ge)) { - memset(out33, 0, 33); - } else { - int ret; - size_t size = 33; - ret = secp256k1_eckey_pubkey_serialize(ge, out33, &size, 1); -#ifdef VERIFY - /* Serialize must succeed because the point is not at infinity */ - VERIFY_CHECK(ret && size == 33); -#else - (void) ret; -#endif - } -} - -/* Outputs the point at infinity if the given byte array is all zero, otherwise - * attempts to parse compressed point serialization. */ -static int secp256k1_musig_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) { - unsigned char zeros[33] = { 0 }; - - if (secp256k1_memcmp_var(in33, zeros, sizeof(zeros)) == 0) { - secp256k1_ge_set_infinity(ge); - return 1; - } - if (!secp256k1_eckey_pubkey_parse(ge, in33, 33)) { - return 0; - } - return secp256k1_ge_is_in_correct_subgroup(ge); -} - static const unsigned char secp256k1_musig_secnonce_magic[4] = { 0x22, 0x0e, 0xdc, 0xf1 }; static void secp256k1_musig_secnonce_save(secp256k1_musig_secnonce *secnonce, const secp256k1_scalar *k, const secp256k1_ge *pk) { @@ -246,7 +213,7 @@ int secp256k1_musig_aggnonce_parse(const secp256k1_context* ctx, secp256k1_musig ARG_CHECK(in66 != NULL); for (i = 0; i < 2; i++) { - if (!secp256k1_musig_ge_parse_ext(&ges[i], &in66[33*i])) { + if (!secp256k1_eckey_parse_ext(&ges[i], &in66[33*i])) { return 0; } } @@ -267,7 +234,7 @@ int secp256k1_musig_aggnonce_serialize(const secp256k1_context* ctx, unsigned ch return 0; } for (i = 0; i < 2; i++) { - secp256k1_musig_ge_serialize_ext(&out66[33*i], &ges[i]); + secp256k1_eckey_serialize_ext(&out66[33*i], &ges[i]); } return 1; } @@ -580,7 +547,7 @@ static void secp256k1_musig_compute_noncehash(unsigned char *noncehash, secp256k secp256k1_musig_compute_noncehash_sha256_tagged(&sha); for (i = 0; i < 2; i++) { - secp256k1_musig_ge_serialize_ext(buf, &aggnonce[i]); + secp256k1_eckey_serialize_ext(buf, &aggnonce[i]); secp256k1_sha256_write(&sha, buf, sizeof(buf)); } secp256k1_sha256_write(&sha, agg_pk32, 32); diff --git a/src/modules/schnorrsig_fullagg/Makefile.am.include b/src/modules/schnorrsig_fullagg/Makefile.am.include new file mode 100644 index 0000000000..13f21dd370 --- /dev/null +++ b/src/modules/schnorrsig_fullagg/Makefile.am.include @@ -0,0 +1,3 @@ +include_HEADERS += include/secp256k1_schnorrsig_fullagg.h +noinst_HEADERS += src/modules/schnorrsig_fullagg/main_impl.h +noinst_HEADERS += src/modules/schnorrsig_fullagg/tests_impl.h diff --git a/src/modules/schnorrsig_fullagg/main_impl.h b/src/modules/schnorrsig_fullagg/main_impl.h new file mode 100644 index 0000000000..a21dd2684f --- /dev/null +++ b/src/modules/schnorrsig_fullagg/main_impl.h @@ -0,0 +1,1124 @@ +/*********************************************************************** + * Copyright (c) 2025 Fabian Jahr * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SCHNORRSIG_FULLAGG_MAIN_H +#define SECP256K1_MODULE_SCHNORRSIG_FULLAGG_MAIN_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_schnorrsig_fullagg.h" + +#include "../../eckey.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../scalar.h" +#include "../../util.h" + +static const unsigned char secp256k1_fullagg_secnonce_magic[4] = { 0xf1, 0x1a, 0x99, 0x01 }; + +/* TODO: Share with MuSig */ +static void secp256k1_fullagg_secnonce_save(secp256k1_fullagg_secnonce *secnonce, const secp256k1_scalar *k, const secp256k1_ge *pk) { + memcpy(&secnonce->data[0], secp256k1_fullagg_secnonce_magic, 4); + + secp256k1_scalar_get_b32(&secnonce->data[4], &k[0]); + secp256k1_scalar_get_b32(&secnonce->data[36], &k[1]); + secp256k1_ge_to_bytes(&secnonce->data[68], pk); +} + +/* TODO: Share with MuSig */ +static int secp256k1_fullagg_secnonce_load(const secp256k1_context* ctx, secp256k1_scalar *k, secp256k1_ge *pk, const secp256k1_fullagg_secnonce *secnonce) { + int is_zero; + + ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_fullagg_secnonce_magic, 4) == 0); + /* We make very sure that the nonce isn't invalidated by checking the values + * in addition to the magic. */ + is_zero = secp256k1_is_zero_array(&secnonce->data[4], 2 * 32); + secp256k1_declassify(ctx, &is_zero, sizeof(is_zero)); + ARG_CHECK(!is_zero); + + secp256k1_scalar_set_b32(&k[0], &secnonce->data[4], NULL); + secp256k1_scalar_set_b32(&k[1], &secnonce->data[36], NULL); + secp256k1_ge_from_bytes(pk, &secnonce->data[68]); + return 1; +} + +/* TODO: Share with MuSig */ +/* If flag is true, invalidate the secnonce; otherwise leave it. Constant-time. */ +static void secp256k1_fullagg_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_fullagg_secnonce *secnonce, int flag) { + secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag); + /* The flag argument is usually classified. So, the line above makes the + * magic and public key classified. However, we need both to be + * declassified. Note that we don't declassify the entire object, because if + * flag is 0, then k[0] and k[1] have not been zeroed. */ + secp256k1_declassify(ctx, secnonce->data, sizeof(secp256k1_fullagg_secnonce_magic)); + secp256k1_declassify(ctx, &secnonce->data[68], 64); +} + +static const unsigned char secp256k1_fullagg_pubnonce_magic[4] = { 0xf1, 0x1a, 0x99, 0x02 }; + +/* TODO: Share with MuSig */ +/* Saves two group elements into a pubnonce. */ +static void secp256k1_fullagg_pubnonce_save(secp256k1_fullagg_pubnonce* nonce, const secp256k1_ge* ges) { + int i; + + memcpy(&nonce->data[0], secp256k1_fullagg_pubnonce_magic, 4); + for (i = 0; i < 2; i++) { + secp256k1_ge_to_bytes(nonce->data + 4 + 64*i, &ges[i]); + } +} + +/* TODO: Share with MuSig */ +/* Loads two group elements from a pubnonce. */ +static int secp256k1_fullagg_pubnonce_load(const secp256k1_context* ctx, secp256k1_ge* ges, const secp256k1_fullagg_pubnonce* nonce) { + int i; + + ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_fullagg_pubnonce_magic, 4) == 0); + for (i = 0; i < 2; i++) { + secp256k1_ge_from_bytes(&ges[i], nonce->data + 4 + 64*i); + } + return 1; +} + +static const unsigned char secp256k1_fullagg_aggnonce_magic[4] = { 0xf1, 0x1a, 0x99, 0x03 }; + +/* TODO: Share with MuSig */ +/* Save aggregate nonce */ +static void secp256k1_fullagg_aggnonce_save(secp256k1_fullagg_aggnonce* nonce, const secp256k1_ge* ges) { + int i; + + memcpy(&nonce->data[0], secp256k1_fullagg_aggnonce_magic, 4); + for (i = 0; i < 2; i++) { + secp256k1_ge_to_bytes_ext(&nonce->data[4 + 64*i], &ges[i]); + } +} + +/* TODO: Share with MuSig */ +/* Load aggregate nonce */ +static int secp256k1_fullagg_aggnonce_load(const secp256k1_context* ctx, secp256k1_ge* ges, const secp256k1_fullagg_aggnonce* nonce) { + int i; + + ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_fullagg_aggnonce_magic, 4) == 0); + for (i = 0; i < 2; i++) { + secp256k1_ge_from_bytes_ext(&ges[i], &nonce->data[4 + 64*i]); + } + return 1; +} + +static const unsigned char secp256k1_fullagg_partial_sig_magic[4] = { 0xf1, 0x1a, 0x99, 0x05 }; + +/* TODO: Share with MuSig */ +/* Save partial signature */ +static void secp256k1_fullagg_partial_sig_save(secp256k1_fullagg_partial_sig* sig, secp256k1_scalar *s) { + memcpy(&sig->data[0], secp256k1_fullagg_partial_sig_magic, 4); + secp256k1_scalar_get_b32(&sig->data[4], s); +} + +/* TODO: Share with MuSig */ +/* Load partial signature */ +static int secp256k1_fullagg_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_fullagg_partial_sig* sig) { + int overflow; + + ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_fullagg_partial_sig_magic, 4) == 0); + secp256k1_scalar_set_b32(s, &sig->data[4], &overflow); + /* Parsed signatures can not overflow */ + VERIFY_CHECK(!overflow); + return 1; +} + +/* TODO: Share with MuSig */ +/* Parse/serialize functions for public interface */ +int secp256k1_fullagg_pubnonce_parse(const secp256k1_context* ctx, secp256k1_fullagg_pubnonce* nonce, const unsigned char *in66) { + secp256k1_ge ges[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(nonce != NULL); + ARG_CHECK(in66 != NULL); + + for (i = 0; i < 2; i++) { + if (!secp256k1_eckey_pubkey_parse(&ges[i], &in66[33*i], 33)) { + return 0; + } + if (!secp256k1_ge_is_in_correct_subgroup(&ges[i])) { + return 0; + } + } + secp256k1_fullagg_pubnonce_save(nonce, ges); + return 1; +} + +/* TODO: Share with MuSig */ +int secp256k1_fullagg_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_fullagg_pubnonce* nonce) { + secp256k1_ge ges[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out66 != NULL); + ARG_CHECK(nonce != NULL); + memset(out66, 0, 66); + + if (!secp256k1_fullagg_pubnonce_load(ctx, ges, nonce)) { + return 0; + } + for (i = 0; i < 2; i++) { + int ret; + size_t size = 33; + ret = secp256k1_eckey_pubkey_serialize(&ges[i], &out66[33*i], &size, 1); +#ifdef VERIFY + VERIFY_CHECK(ret && size == 33); +#else + (void) ret; +#endif + } + return 1; +} + +/* TODO: Share with MuSig */ +int secp256k1_fullagg_aggnonce_parse(const secp256k1_context* ctx, secp256k1_fullagg_aggnonce* nonce, const unsigned char *in66) { + secp256k1_ge ges[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(nonce != NULL); + ARG_CHECK(in66 != NULL); + + for (i = 0; i < 2; i++) { + if (!secp256k1_eckey_parse_ext(&ges[i], &in66[33*i])) { + return 0; + } + } + secp256k1_fullagg_aggnonce_save(nonce, ges); + return 1; +} + +/* TODO: Share with MuSig */ +int secp256k1_fullagg_aggnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_fullagg_aggnonce* nonce) { + secp256k1_ge ges[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out66 != NULL); + ARG_CHECK(nonce != NULL); + memset(out66, 0, 66); + + if (!secp256k1_fullagg_aggnonce_load(ctx, ges, nonce)) { + return 0; + } + for (i = 0; i < 2; i++) { + secp256k1_eckey_serialize_ext(&out66[33*i], &ges[i]); + } + return 1; +} + +/* TODO: Share with MuSig */ +int secp256k1_fullagg_partial_sig_parse(const secp256k1_context* ctx, secp256k1_fullagg_partial_sig* sig, const unsigned char *in32) { + secp256k1_scalar tmp; + int overflow; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(in32 != NULL); + + memset(sig, 0, sizeof(*sig)); + + secp256k1_scalar_set_b32(&tmp, in32, &overflow); + if (overflow) { + return 0; + } + secp256k1_fullagg_partial_sig_save(sig, &tmp); + return 1; +} + +/* TODO: Share with MuSig */ +int secp256k1_fullagg_partial_sig_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_fullagg_partial_sig* sig) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out32 != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_fullagg_partial_sig_magic, 4) == 0); + + memcpy(out32, &sig->data[4], 32); + return 1; +} + +/* Initializes SHA256 with fixed midstate for "FullAgg/aux" */ +static void secp256k1_fullagg_sha256_tagged_aux(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x4cb4139bul; + sha->s[1] = 0xac6dd715ul; + sha->s[2] = 0x46eb898bul; + sha->s[3] = 0xc13797e2ul; + sha->s[4] = 0xa7c1aea6ul; + sha->s[5] = 0x21aab077ul; + sha->s[6] = 0x6f1746b2ul; + sha->s[7] = 0x5c2bedd8ul; + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate for "FullAgg/nonce" */ +static void secp256k1_fullagg_sha256_tagged_nonce(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x742a20a9ul; + sha->s[1] = 0x2c939aaeul; + sha->s[2] = 0xf8f0f6c0ul; + sha->s[3] = 0x9b975422ul; + sha->s[4] = 0xbf5a4f08ul; + sha->s[5] = 0xe5fa99eeul; + sha->s[6] = 0xa64c241ful; + sha->s[7] = 0x5b12ebccul; + sha->bytes = 64; +} + +/* FullAgg nonce generation function */ +static void secp256k1_fullagg_nonce_function(secp256k1_scalar *k, const unsigned char *session_secrand, + const unsigned char *msg32, const unsigned char *seckey32, + const unsigned char *pk33, const unsigned char *extra_input32) { + secp256k1_sha256 sha; + unsigned char rand[32]; + unsigned char i; + + /* Bind nonce to secret key if it was provided, otherwise use secrand directly. */ + if (seckey32 != NULL) { + secp256k1_fullagg_sha256_tagged_aux(&sha); + secp256k1_sha256_write(&sha, session_secrand, 32); + secp256k1_sha256_finalize(&sha, rand); + for (i = 0; i < 32; i++) { + rand[i] ^= seckey32[i]; + } + } else { + memcpy(rand, session_secrand, sizeof(rand)); + } + + /* Write all relevant data into hash for nonce. */ + secp256k1_fullagg_sha256_tagged_nonce(&sha); + secp256k1_sha256_write(&sha, rand, sizeof(rand)); + secp256k1_sha256_write(&sha, pk33, 33); + if (msg32 != NULL) { + secp256k1_sha256_write(&sha, msg32, 32); + } + if (extra_input32 != NULL) { + secp256k1_sha256_write(&sha, extra_input32, 32); + } + + /* Generate the two nonces. */ + for (i = 0; i < 2; i++) { + unsigned char buf[32]; + secp256k1_sha256 sha_tmp = sha; + secp256k1_sha256_write(&sha_tmp, &i, 1); + secp256k1_sha256_finalize(&sha_tmp, buf); + secp256k1_scalar_set_b32(&k[i], buf, NULL); + + secp256k1_memclear_explicit(buf, sizeof(buf)); + secp256k1_sha256_clear(&sha_tmp); + } + secp256k1_memclear_explicit(rand, sizeof(rand)); + secp256k1_sha256_clear(&sha); +} + +/* Internal nonce generation */ +static int secp256k1_fullagg_nonce_gen_internal(const secp256k1_context* ctx, secp256k1_fullagg_secnonce *secnonce, + secp256k1_fullagg_pubnonce *pubnonce, const unsigned char *input_nonce, + const unsigned char *seckey, const secp256k1_pubkey *pubkey, + const unsigned char *msg32, const unsigned char *extra_input32) { + secp256k1_scalar k[2]; + secp256k1_ge nonce_pts[2]; + secp256k1_gej nonce_ptj[2]; + int i; + unsigned char pk_ser[33]; + size_t pk_ser_len = sizeof(pk_ser); + secp256k1_ge pk; + int pk_serialize_success; + int ret = 1; + + ARG_CHECK(pubnonce != NULL); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + memset(pubnonce, 0, sizeof(*pubnonce)); + + /* Check that the seckey is valid to be able to sign for it later. */ + if (seckey != NULL) { + secp256k1_scalar sk; + ret &= secp256k1_scalar_set_b32_seckey(&sk, seckey); + secp256k1_scalar_clear(&sk); + } + + if (!secp256k1_pubkey_load(ctx, &pk, pubkey)) { + return 0; + } + pk_serialize_success = secp256k1_eckey_pubkey_serialize(&pk, pk_ser, &pk_ser_len, 1); + +#ifdef VERIFY + VERIFY_CHECK(pk_serialize_success); + VERIFY_CHECK(pk_ser_len == sizeof(pk_ser)); +#else + (void) pk_serialize_success; +#endif + + /* Get secret nonce */ + secp256k1_fullagg_nonce_function(k, input_nonce, msg32, seckey, pk_ser, extra_input32); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[0])); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[1])); + secp256k1_fullagg_secnonce_save(secnonce, k, &pk); + secp256k1_fullagg_secnonce_invalidate(ctx, secnonce, !ret); + + /* Compute pubnonce as R1_i = k[0]*G, R2_i = k[1]*G */ + for (i = 0; i < 2; i++) { + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj[i], &k[i]); + secp256k1_scalar_clear(&k[i]); + } + + /* Convert pubnonce from jacobian to affine and mark as non-secret */ + secp256k1_ge_set_all_gej_var(nonce_pts, nonce_ptj, 2); + for (i = 0; i < 2; i++) { + secp256k1_gej_clear(&nonce_ptj[i]); + secp256k1_declassify(ctx, &nonce_pts[i], sizeof(nonce_pts[i])); + } + + secp256k1_fullagg_pubnonce_save(pubnonce, nonce_pts); + return ret; +} + +int secp256k1_fullagg_nonce_gen(const secp256k1_context* ctx, secp256k1_fullagg_secnonce *secnonce, + secp256k1_fullagg_pubnonce *pubnonce, unsigned char *session_secrand32, + const unsigned char *seckey, const secp256k1_pubkey *pubkey, + const unsigned char *msg32, const unsigned char *extra_input32) { + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secnonce != NULL); + ARG_CHECK(session_secrand32 != NULL); + memset(secnonce, 0, sizeof(*secnonce)); + + ret &= !secp256k1_is_zero_array(session_secrand32, 32); + secp256k1_declassify(ctx, &ret, sizeof(ret)); + if (ret == 0) { + secp256k1_fullagg_secnonce_invalidate(ctx, secnonce, 1); + return 0; + } + + ret &= secp256k1_fullagg_nonce_gen_internal(ctx, secnonce, pubnonce, session_secrand32, + seckey, pubkey, msg32, extra_input32); + secp256k1_memczero(session_secrand32, 32, ret); + return ret; +} + +int secp256k1_fullagg_nonce_gen_counter(const secp256k1_context* ctx, secp256k1_fullagg_secnonce *secnonce, + secp256k1_fullagg_pubnonce *pubnonce, uint64_t nonrepeating_cnt, + const secp256k1_keypair *keypair, const unsigned char *msg32, + const unsigned char *extra_input32) { + unsigned char buf[32] = { 0 }; + unsigned char seckey[32]; + secp256k1_pubkey pubkey; + int ret; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secnonce != NULL); + ARG_CHECK(keypair != NULL); + memset(secnonce, 0, sizeof(*secnonce)); + + secp256k1_write_be64(buf, nonrepeating_cnt); + ret = secp256k1_keypair_sec(ctx, seckey, keypair); + VERIFY_CHECK(ret); + ret = secp256k1_keypair_pub(ctx, &pubkey, keypair); + VERIFY_CHECK(ret); +#ifndef VERIFY + (void) ret; +#endif + + if (!secp256k1_fullagg_nonce_gen_internal(ctx, secnonce, pubnonce, buf, seckey, + &pubkey, msg32, extra_input32)) { + return 0; + } + secp256k1_memclear_explicit(seckey, sizeof(seckey)); + return 1; +} + +static int secp256k1_fullagg_sum_pubnonces(const secp256k1_context* ctx, secp256k1_gej *summed_pubnonces, const secp256k1_fullagg_pubnonce * const* pubnonces, size_t n_pubnonces) { + size_t i; + int j; + + secp256k1_gej_set_infinity(&summed_pubnonces[0]); + secp256k1_gej_set_infinity(&summed_pubnonces[1]); + + for (i = 0; i < n_pubnonces; i++) { + secp256k1_ge nonce_pts[2]; + if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[i])) { + return 0; + } + for (j = 0; j < 2; j++) { + secp256k1_gej_add_ge_var(&summed_pubnonces[j], &summed_pubnonces[j], &nonce_pts[j], NULL); + } + } + return 1; +} + +/* TODO: Share with MuSig */ +/* Aggregate nonces from all signers */ +int secp256k1_fullagg_nonce_agg(const secp256k1_context* ctx, secp256k1_fullagg_aggnonce *aggnonce, + const secp256k1_fullagg_pubnonce * const* pubnonces, size_t n_pubnonces) { + secp256k1_gej aggnonce_ptsj[2]; + secp256k1_ge aggnonce_pts[2]; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(aggnonce != NULL); + ARG_CHECK(pubnonces != NULL); + ARG_CHECK(n_pubnonces > 0); + + if (!secp256k1_fullagg_sum_pubnonces(ctx, aggnonce_ptsj, pubnonces, n_pubnonces)) { + return 0; + } + + secp256k1_ge_set_all_gej_var(aggnonce_pts, aggnonce_ptsj, 2); + secp256k1_fullagg_aggnonce_save(aggnonce, aggnonce_pts); + return 1; +} + +/* Initializes SHA256 with fixed midstate for "FullAgg/noncecoef" */ +static void secp256k1_fullagg_sha256_tagged_noncecoef(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xe7a1238aul; + sha->s[1] = 0x8d0cb445ul; + sha->s[2] = 0x82ea69faul; + sha->s[3] = 0x6cc8a517ul; + sha->s[4] = 0xfce019a0ul; + sha->s[5] = 0xdc36828ful; + sha->s[6] = 0x727f042bul; + sha->s[7] = 0xf325ff8eul; + sha->bytes = 64; +} + +/* Compute hash_nonce: H_non(R1 || R2 || X_i || m_i || R2_i for all signers) */ +static int secp256k1_fullagg_compute_noncehash(const secp256k1_context* ctx, + unsigned char *noncehash, + const secp256k1_ge *r1, + const secp256k1_ge *r2, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_signers) { + unsigned char buf[32]; + size_t i; + secp256k1_sha256 sha; + secp256k1_fullagg_sha256_tagged_noncecoef(&sha); + + if (secp256k1_ge_is_infinity(r1)) { + memset(buf, 0, 32); + } else { + secp256k1_fe_normalize_var((secp256k1_fe*)&r1->x); + secp256k1_fe_get_b32(buf, &r1->x); + } + secp256k1_sha256_write(&sha, buf, 32); + + if (secp256k1_ge_is_infinity(r2)) { + memset(buf, 0, 32); + } else { + secp256k1_fe_normalize_var((secp256k1_fe*)&r2->x); + secp256k1_fe_get_b32(buf, &r2->x); + } + secp256k1_sha256_write(&sha, buf, 32); + + /* Write X_i || m_i || R2_i for all signers */ + for (i = 0; i < n_signers; i++) { + secp256k1_ge pk_ge, nonce_pts[2]; + + /* Load and write X_i */ + if (!secp256k1_pubkey_load(ctx, &pk_ge, pubkeys[i])) { + return 0; + } + secp256k1_fe_normalize_var(&pk_ge.x); + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(&sha, buf, 32); + + /* Write m_i */ + secp256k1_sha256_write(&sha, messages[i], 32); + + /* Load and write R2_i */ + if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[i])) { + return 0; + } + if (secp256k1_ge_is_infinity(&nonce_pts[1])) { + memset(buf, 0, 32); + } else { + secp256k1_fe_normalize_var(&nonce_pts[1].x); + secp256k1_fe_get_b32(buf, &nonce_pts[1].x); + } + secp256k1_sha256_write(&sha, buf, 32); + } + + secp256k1_sha256_finalize(&sha, noncehash); + return 1; +} + +/* Initializes SHA256 with fixed midstate for "FullAgg/sig" */ +static void secp256k1_fullagg_sha256_tagged_sig(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xd25dfffbul; + sha->s[1] = 0xd0479fb3ul; + sha->s[2] = 0x32e0d40eul; + sha->s[3] = 0x9c4f065aul; + sha->s[4] = 0xf9bf9e14ul; + sha->s[5] = 0x8f22cce6ul; + sha->s[6] = 0x24f00eaeul; + sha->s[7] = 0xed749b73ul; + sha->bytes = 64; +} + +/* Compute hash_sig: H_sig(L, R, X_i, m_i) where L is list of (X_i, m_i) pairs */ +static int secp256k1_fullagg_compute_sighash(const secp256k1_context* ctx, + secp256k1_scalar *c_i, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers, + const secp256k1_ge *r, + size_t signer_index) { + unsigned char buf[32]; + unsigned char hash[32]; + size_t i; + secp256k1_ge pk_ge; + secp256k1_sha256 sha; + secp256k1_fullagg_sha256_tagged_sig(&sha); + + /* Write L (list of all X_i || m_i) */ + for (i = 0; i < n_signers; i++) { + if (!secp256k1_pubkey_load(ctx, &pk_ge, pubkeys[i])) { + return 0; + } + secp256k1_fe_normalize_var(&pk_ge.x); + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(&sha, buf, 32); + secp256k1_sha256_write(&sha, messages[i], 32); + } + + /* Write R */ + secp256k1_fe_normalize_var((secp256k1_fe*)&r->x); + secp256k1_fe_get_b32(buf, &r->x); + secp256k1_sha256_write(&sha, buf, 32); + + /* Write X_i for this signer */ + if (!secp256k1_pubkey_load(ctx, &pk_ge, pubkeys[signer_index])) { + return 0; + } + secp256k1_fe_normalize_var(&pk_ge.x); + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(&sha, buf, 32); + + /* Write m_i for this signer */ + secp256k1_sha256_write(&sha, messages[signer_index], 32); + + secp256k1_sha256_finalize(&sha, hash); + secp256k1_scalar_set_b32(c_i, hash, NULL); + + return 1; +} + +static const unsigned char secp256k1_fullagg_session_magic[4] = { 0xf1, 0x1a, 0x99, 0x04 }; + +static void secp256k1_fullagg_session_save(secp256k1_fullagg_session *session, + const unsigned char *fin_nonce, + int fin_nonce_parity, + const secp256k1_scalar *noncecoef, + size_t n_signers) { + unsigned char *ptr = session->data; + + memcpy(ptr, secp256k1_fullagg_session_magic, 4); + ptr += 4; + memcpy(ptr, fin_nonce, 32); + ptr += 32; + *ptr = (unsigned char)fin_nonce_parity; + ptr += 1; + secp256k1_scalar_get_b32(ptr, noncecoef); + ptr += 32; + secp256k1_write_be64(ptr, (uint64_t)n_signers); +} + +static int secp256k1_fullagg_session_load(const secp256k1_context* ctx, + unsigned char *fin_nonce, + int *fin_nonce_parity, + secp256k1_scalar *noncecoef, + size_t *n_signers, + const secp256k1_fullagg_session *session) { + const unsigned char *ptr = session->data; + + ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_fullagg_session_magic, 4) == 0); + ptr += 4; + memcpy(fin_nonce, ptr, 32); + ptr += 32; + *fin_nonce_parity = (int)*ptr; + ptr += 1; + secp256k1_scalar_set_b32(noncecoef, ptr, NULL); + ptr += 32; + *n_signers = (size_t)secp256k1_read_be64(ptr); + + return 1; +} + + +/* Initialize a FullAgg session */ +int secp256k1_fullagg_session_init(const secp256k1_context* ctx, secp256k1_fullagg_session *session, + const secp256k1_fullagg_aggnonce *aggnonce, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_signers) { + secp256k1_ge aggnonce_pts[2]; + secp256k1_ge r; + secp256k1_gej rj; + unsigned char noncehash[32]; + unsigned char fin_nonce[32]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(aggnonce != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(messages != NULL); + ARG_CHECK(pubnonces != NULL); + ARG_CHECK(n_signers > 0); + + if (!secp256k1_fullagg_aggnonce_load(ctx, aggnonce_pts, aggnonce)) { + return 0; + } + + /* Compute nonce hash b */ + if (!secp256k1_fullagg_compute_noncehash(ctx, noncehash, &aggnonce_pts[0], &aggnonce_pts[1], + pubkeys, messages, pubnonces, n_signers)) { + return 0; + } + secp256k1_scalar_set_b32(&noncecoef, noncehash, NULL); + + /* Compute effective final nonce R = R1 + b*R2 */ + if (secp256k1_ge_is_infinity(&aggnonce_pts[0]) && secp256k1_ge_is_infinity(&aggnonce_pts[1])) { + /* Both components are infinity, R is infinity */ + secp256k1_ge_set_infinity(&r); + } else if (secp256k1_ge_is_infinity(&aggnonce_pts[0])) { + /* Only R1 is infinity, R = b*R2 */ + secp256k1_gej_set_ge(&rj, &aggnonce_pts[1]); + secp256k1_ecmult(&rj, &rj, &noncecoef, NULL); + secp256k1_ge_set_gej(&r, &rj); + } else if (secp256k1_ge_is_infinity(&aggnonce_pts[1])) { + /* Only R2 is infinity, R = R1 */ + r = aggnonce_pts[0]; + } else { + /* Normal case: R = R1 + b*R2 */ + secp256k1_gej_set_ge(&rj, &aggnonce_pts[1]); + secp256k1_ecmult(&rj, &rj, &noncecoef, NULL); + secp256k1_gej_add_ge_var(&rj, &rj, &aggnonce_pts[0], NULL); + secp256k1_ge_set_gej(&r, &rj); + } + + /* Store final nonce */ + if (secp256k1_ge_is_infinity(&r)) { + /* R is infinity - store zeros for x-coordinate */ + memset(fin_nonce, 0, 32); + fin_nonce_parity = 0; + } else { + /* Normal case - normalize and store x-coordinate */ + secp256k1_fe_normalize_var(&r.x); + secp256k1_fe_normalize_var(&r.y); + secp256k1_fe_get_b32(fin_nonce, &r.x); + fin_nonce_parity = secp256k1_fe_is_odd(&r.y); + } + + secp256k1_fullagg_session_save(session, fin_nonce, fin_nonce_parity, &noncecoef, n_signers); + return 1; +} + +/* TODO: Share with MuSig */ +static void secp256k1_fullagg_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) { + secp256k1_scalar_clear(sk); + secp256k1_scalar_clear(&k[0]); + secp256k1_scalar_clear(&k[1]); +} + +/* Create a partial signature */ +int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fullagg_partial_sig *partial_sig, + secp256k1_fullagg_secnonce *secnonce, const secp256k1_keypair *keypair, + const secp256k1_fullagg_session *session, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t signer_index) { + secp256k1_scalar sk; + secp256k1_ge pk, keypair_pk; + secp256k1_scalar k[2]; + secp256k1_scalar s, c_i; + unsigned char fin_nonce[32]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + size_t n_signers; + secp256k1_ge r; + int ret; + int is_fin_nonce_zero; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secnonce != NULL); + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(keypair != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(messages != NULL); + ARG_CHECK(pubnonces != NULL); + + /* Load and invalidate secnonce */ + ret = secp256k1_fullagg_secnonce_load(ctx, k, &pk, secnonce); + /* Always clear the secnonce to avoid nonce reuse */ + memset(secnonce, 0, sizeof(*secnonce)); + if (!ret) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + if (!secp256k1_keypair_load(ctx, &sk, &keypair_pk, keypair)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Verify the keypair matches the secnonce */ + if (!secp256k1_fe_equal(&pk.x, &keypair_pk.x) || !secp256k1_fe_equal(&pk.y, &keypair_pk.y)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + if (!secp256k1_fullagg_session_load(ctx, fin_nonce, &fin_nonce_parity, &noncecoef, &n_signers, session)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + if (signer_index >= n_signers) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Verify this signer's pubkey matches */ + { + secp256k1_ge signer_pk; + if (!secp256k1_pubkey_load(ctx, &signer_pk, pubkeys[signer_index])) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + if (!secp256k1_fe_equal(&pk.x, &signer_pk.x) || !secp256k1_fe_equal(&pk.y, &signer_pk.y)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + } + + /* Verify this signer's R2 appears exactly once at the correct index */ + { + secp256k1_ge nonce_pts[2]; + secp256k1_gej r2j; + secp256k1_ge r2_self; + int found_count = 0; + int found_index = -1; + size_t j; + + /* Compute our R2 from k[1] */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &r2j, &k[1]); + secp256k1_ge_set_gej(&r2_self, &r2j); + + /* Check all pubnonces for our R2 */ + for (j = 0; j < n_signers; j++) { + if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[j])) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Check if this R2 matches ours (compare both x and y coordinates) */ + if (!secp256k1_ge_is_infinity(&nonce_pts[1]) && !secp256k1_ge_is_infinity(&r2_self)) { + if (secp256k1_fe_equal(&r2_self.x, &nonce_pts[1].x) && + secp256k1_fe_equal(&r2_self.y, &nonce_pts[1].y)) { + found_count++; + found_index = (int)j; + } + } else if (secp256k1_ge_is_infinity(&nonce_pts[1]) && secp256k1_ge_is_infinity(&r2_self)) { + /* Both are infinity */ + found_count++; + found_index = (int)j; + } + } + + /* R2 must appear exactly once */ + if (found_count != 1) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* The index where R2 was found must match our signer_index */ + if (found_index != (int)signer_index) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + } + + /* Check if fin_nonce is zero (infinity case) */ + is_fin_nonce_zero = secp256k1_is_zero_array(fin_nonce, 32); + + if (is_fin_nonce_zero) { + /* R is infinity - cannot sign */ + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Reconstruct R for computing challenge */ + secp256k1_fe_set_b32_mod(&r.x, fin_nonce); + if (!secp256k1_ge_set_xo_var(&r, &r.x, fin_nonce_parity)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Negate nonces if R has odd y */ + if (fin_nonce_parity) { + secp256k1_scalar_negate(&k[0], &k[0]); + secp256k1_scalar_negate(&k[1], &k[1]); + } + + /* Compute signer's challenge c_i */ + if (!secp256k1_fullagg_compute_sighash(ctx, &c_i, pubkeys, messages, n_signers, + &r, signer_index)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Compute s_i = k1_i + b*k2_i + c_i*sk_i */ + secp256k1_scalar_mul(&s, &noncecoef, &k[1]); /* b*k2_i */ + secp256k1_scalar_add(&s, &s, &k[0]); /* + k1_i */ + secp256k1_scalar_mul(&k[0], &c_i, &sk); /* c_i*sk_i (reuse k[0]) */ + secp256k1_scalar_add(&s, &s, &k[0]); /* + c_i*sk_i */ + + secp256k1_fullagg_partial_sig_save(partial_sig, &s); + + /* Clear sensitive data */ + secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_scalar_clear(&s); + secp256k1_scalar_clear(&c_i); + + return 1; +} + +/* TODO: Share with MuSig */ +static void secp256k1_fullagg_effective_nonce(secp256k1_gej *out_nonce, const secp256k1_ge *nonce_pts, const secp256k1_scalar *b) { + secp256k1_gej tmp; + + secp256k1_gej_set_ge(&tmp, &nonce_pts[1]); + secp256k1_ecmult(out_nonce, &tmp, b, NULL); + secp256k1_gej_add_ge_var(out_nonce, out_nonce, &nonce_pts[0], NULL); +} + +/* Verify a partial signature */ +int secp256k1_fullagg_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_fullagg_partial_sig *partial_sig, + const secp256k1_fullagg_pubnonce *pubnonce, const secp256k1_pubkey *pubkey, + const secp256k1_fullagg_session *session, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t signer_index) { + unsigned char fin_nonce[32]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + size_t n_signers; + secp256k1_scalar s, c_i; + secp256k1_ge pk, r; + secp256k1_ge nonce_pts[2]; + secp256k1_gej rj, pkj, tmp; + int result; + int is_fin_nonce_zero; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(pubnonce != NULL); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(messages != NULL); + + if (!secp256k1_fullagg_session_load(ctx, fin_nonce, &fin_nonce_parity, &noncecoef, &n_signers, session)) { + return 0; + } + + if (signer_index >= n_signers) { + return 0; + } + + if (!secp256k1_fullagg_partial_sig_load(ctx, &s, partial_sig)) { + return 0; + } + + if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonce)) { + return 0; + } + + if (!secp256k1_pubkey_load(ctx, &pk, pubkey)) { + return 0; + } + + if (secp256k1_ge_is_infinity(&pk)) { + return 0; + } + + /* Check if fin_nonce is zero (infinity case) */ + is_fin_nonce_zero = secp256k1_is_zero_array(fin_nonce, 32); + + if (is_fin_nonce_zero) { + return 0; + } + + /* Reconstruct R */ + secp256k1_fe_set_b32_mod(&r.x, fin_nonce); + if (!secp256k1_ge_set_xo_var(&r, &r.x, fin_nonce_parity)) { + return 0; + } + + /* Compute effective nonce: R_eff = R1_i + b*R2_i */ + if (secp256k1_ge_is_infinity(&nonce_pts[0]) && secp256k1_ge_is_infinity(&nonce_pts[1])) { + /* Both nonces are infinity */ + secp256k1_gej_set_infinity(&rj); + } else if (secp256k1_ge_is_infinity(&nonce_pts[0])) { + /* Only R1_i is infinity, R_eff = b*R2_i */ + secp256k1_gej_set_ge(&rj, &nonce_pts[1]); + secp256k1_ecmult(&rj, &rj, &noncecoef, NULL); + } else if (secp256k1_ge_is_infinity(&nonce_pts[1])) { + /* Only R2_i is infinity, R_eff = R1_i */ + secp256k1_gej_set_ge(&rj, &nonce_pts[0]); + } else { + /* Normal case: R_eff = R1_i + b*R2_i */ + secp256k1_fullagg_effective_nonce(&rj, nonce_pts, &noncecoef); + } + + /* Negate if R has odd y */ + if (fin_nonce_parity) { + secp256k1_gej_neg(&rj, &rj); + } + + /* Compute signer's challenge c_i */ + if (!secp256k1_fullagg_compute_sighash(ctx, &c_i, pubkeys, messages, n_signers, + &r, signer_index)) { + return 0; + } + + /* Verify: s_i*G = R_eff + c_i*pk_i */ + secp256k1_scalar_negate(&s, &s); + secp256k1_gej_set_ge(&pkj, &pk); + secp256k1_ecmult(&tmp, &pkj, &c_i, &s); + secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL); + + result = secp256k1_gej_is_infinity(&tmp); + + return result; +} + +/* Aggregate partial signatures */ +int secp256k1_fullagg_partial_sig_agg(const secp256k1_context* ctx, unsigned char *sig64, + const secp256k1_fullagg_session *session, + const secp256k1_fullagg_partial_sig * const *partial_sigs, + size_t n_sigs) { + unsigned char fin_nonce[32]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + size_t n_signers; + secp256k1_scalar s_agg; + size_t i; + int is_fin_nonce_zero; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(partial_sigs != NULL); + ARG_CHECK(n_sigs > 0); + + if (!secp256k1_fullagg_session_load(ctx, fin_nonce, &fin_nonce_parity, &noncecoef, &n_signers, session)) { + return 0; + } + + ARG_CHECK(n_sigs == n_signers); + + /* Check if fin_nonce (R) is zero (infinity case) */ + is_fin_nonce_zero = secp256k1_is_zero_array(fin_nonce, 32); + + if (is_fin_nonce_zero) { + return 0; + } + + /* Aggregate all the s values */ + secp256k1_scalar_set_int(&s_agg, 0); + for (i = 0; i < n_sigs; i++) { + secp256k1_scalar s_i; + if (!secp256k1_fullagg_partial_sig_load(ctx, &s_i, partial_sigs[i])) { + return 0; + } + secp256k1_scalar_add(&s_agg, &s_agg, &s_i); + } + + /* Output aggregate signature */ + memcpy(sig64, fin_nonce, 32); + secp256k1_scalar_get_b32(&sig64[32], &s_agg); + + return 1; +} + +/* Verify a FullAgg aggregate signature */ +int secp256k1_fullagg_verify(const secp256k1_context* ctx, const unsigned char *sig64, + const secp256k1_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers) { + secp256k1_scalar s; + secp256k1_ge r; + secp256k1_gej c_sum_pk; + secp256k1_gej tmp; + size_t i; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(messages != NULL); + ARG_CHECK(n_signers > 0); + + /* Parse signature */ + if (!secp256k1_fe_set_b32_limit(&r.x, sig64)) { + return 0; + } + secp256k1_scalar_set_b32(&s, &sig64[32], &overflow); + if (overflow) { + return 0; + } + + /* Check even y */ + if (!secp256k1_ge_set_xo_var(&r, &r.x, 0)) { + return 0; + } + + /* Compute sum of c_i*pk_i */ + secp256k1_gej_set_infinity(&c_sum_pk); + for (i = 0; i < n_signers; i++) { + secp256k1_scalar c_i; + secp256k1_gej pkj; + secp256k1_ge pk_ge; + + if (!secp256k1_pubkey_load(ctx, &pk_ge, pubkeys[i])) { + return 0; + } + + /* Check that no public key is the identity element */ + if (secp256k1_ge_is_infinity(&pk_ge)) { + return 0; + } + + if (!secp256k1_fullagg_compute_sighash(ctx, &c_i, pubkeys, messages, n_signers, &r, i)) { + return 0; + } + secp256k1_gej_set_ge(&pkj, &pk_ge); + secp256k1_ecmult(&tmp, &pkj, &c_i, NULL); + secp256k1_gej_add_var(&c_sum_pk, &c_sum_pk, &tmp, NULL); + } + + /* Verify: s*G = R + sum(c_i*pk_i) */ + secp256k1_scalar_negate(&s, &s); + secp256k1_ecmult(&tmp, &c_sum_pk, &secp256k1_scalar_one, &s); + secp256k1_gej_add_ge_var(&tmp, &tmp, &r, NULL); + + return secp256k1_gej_is_infinity(&tmp); +} + +#endif /* SECP256K1_MODULE_SCHNORRSIG_FULLAGG_MAIN_H */ diff --git a/src/modules/schnorrsig_fullagg/tests_impl.h b/src/modules/schnorrsig_fullagg/tests_impl.h new file mode 100644 index 0000000000..a70912b62b --- /dev/null +++ b/src/modules/schnorrsig_fullagg/tests_impl.h @@ -0,0 +1,505 @@ +/*********************************************************************** + * Copyright (c) 2025 Fabian Jahr * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SCHNORRSIG_FULLAGG_TESTS_IMPL_H +#define SECP256K1_MODULE_SCHNORRSIG_FULLAGG_TESTS_IMPL_H + +#include +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_schnorrsig_fullagg.h" + +#include "../../scalar.h" +#include "../../field.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../util.h" + +static int create_keypair_and_pk_fullagg(secp256k1_keypair *keypair, secp256k1_pubkey *pk, const unsigned char *sk) { + int ret; + secp256k1_keypair keypair_tmp; + ret = secp256k1_keypair_create(CTX, &keypair_tmp, sk); + ret &= secp256k1_keypair_pub(CTX, pk, &keypair_tmp); + if (keypair != NULL) { + *keypair = keypair_tmp; + } + return ret; +} + +/* Simple test with three signers */ +static void fullagg_simple_test(void) { + unsigned char sk[3][32]; + secp256k1_keypair keypair[3]; + secp256k1_fullagg_pubnonce pubnonce[3]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[3]; + secp256k1_fullagg_aggnonce aggnonce; + unsigned char msg[3][32]; + unsigned char session_secrand[3][32]; + secp256k1_fullagg_secnonce secnonce[3]; + secp256k1_pubkey pk[3]; + const secp256k1_pubkey *pk_ptr[3]; + const unsigned char *msg_ptr[3]; + secp256k1_fullagg_partial_sig partial_sig[3]; + const secp256k1_fullagg_partial_sig *partial_sig_ptr[3]; + unsigned char final_sig[64]; + secp256k1_fullagg_session session; + int i; + const secp256k1_pubkey *pk_wrong_order[3]; + int sign_success; + int retry_count = 0; + + testrand256(msg[0]); + testrand256(msg[1]); + testrand256(msg[2]); + + for (i = 0; i < 3; i++) { + testrand256(sk[i]); + pk_ptr[i] = &pk[i]; + msg_ptr[i] = msg[i]; + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + + CHECK(create_keypair_and_pk_fullagg(&keypair[i], &pk[i], sk[i])); + } + + /* Try up to 3 times in case we get unlucky with infinity aggregate nonces */ + sign_success = 0; + while (!sign_success && retry_count < 3) { + retry_count++; + + /* Generate nonces */ + for (i = 0; i < 3; i++) { + testrand256(session_secrand[i]); + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_secrand[i], + sk[i], &pk[i], msg[i], NULL) == 1); + } + + /* Aggregate nonces */ + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 3) == 1); + + /* Initialize session */ + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 3) == 1); + + /* The signers create their partial signature */ + sign_success = 1; + for (i = 0; i < 3; i++) { + if (secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce[i], + &keypair[i], &session, pk_ptr, msg_ptr, + pubnonce_ptr, i) != 1) { + sign_success = 0; + break; + } + /* Verify partial signature */ + if (secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], + &pk[i], &session, pk_ptr, msg_ptr, i) != 1) { + sign_success = 0; + break; + } + } + } + CHECK(sign_success); + + /* Aggregate partial signatures */ + CHECK(secp256k1_fullagg_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1); + + /* Verify the aggregate signature */ + CHECK(secp256k1_fullagg_verify(CTX, final_sig, pk_ptr, msg_ptr, 3) == 1); + + /* Test that verification fails with wrong message */ + msg[0][0] ^= 1; /* Maleate the message */ + CHECK(secp256k1_fullagg_verify(CTX, final_sig, pk_ptr, msg_ptr, 3) == 0); + msg[0][0] ^= 1; /* Restore original message */ + + /* Test that verification fails with wrong public key order */ + pk_wrong_order[0] = &pk[1]; + pk_wrong_order[1] = &pk[0]; + pk_wrong_order[2] = &pk[2]; + CHECK(secp256k1_fullagg_verify(CTX, final_sig, pk_wrong_order, msg_ptr, 3) == 0); + + /* Test that verification fails with incomplete list */ + CHECK(secp256k1_fullagg_verify(CTX, final_sig, pk_ptr, msg_ptr, 2) == 0); +} + +/* Test API parameter validation */ +static void fullagg_api_tests(void) { + secp256k1_fullagg_partial_sig partial_sig[2]; + const secp256k1_fullagg_partial_sig *partial_sig_ptr[2]; + unsigned char pre_sig[64]; + unsigned char sk[2][32]; + secp256k1_keypair keypair[2]; + unsigned char zeros132[132] = { 0 }; + unsigned char session_secrand[2][32]; + secp256k1_fullagg_secnonce secnonce[2]; + secp256k1_fullagg_pubnonce pubnonce[2]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[2]; + unsigned char pubnonce_ser[66]; + secp256k1_fullagg_aggnonce aggnonce; + unsigned char aggnonce_ser[66]; + unsigned char msg[2][32]; + const unsigned char *msg_ptr[2]; + secp256k1_fullagg_session session; + secp256k1_pubkey pk[2]; + const secp256k1_pubkey *pk_ptr[2]; + int i; + + for (i = 0; i < 2; i++) { + pk_ptr[i] = &pk[i]; + msg_ptr[i] = msg[i]; + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + testrand256(session_secrand[i]); + testrand256(sk[i]); + testrand256(msg[i]); + CHECK(create_keypair_and_pk_fullagg(&keypair[i], &pk[i], sk[i])); + } + + /* Nonce generation */ + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_secrand[0], + sk[0], &pk[0], msg[0], NULL) == 1); + /* Check that session_secrand is zeroed */ + CHECK(secp256k1_memcmp_var(session_secrand[0], zeros132, sizeof(session_secrand[0])) == 0); + + /* session_secrand = 0 is disallowed */ + memset(session_secrand[0], 0, sizeof(session_secrand[0])); + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_secrand[0], + sk[0], &pk[0], msg[0], NULL) == 0); + + /* Test NULL parameters */ + testrand256(session_secrand[0]); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, NULL, &pubnonce[0], session_secrand[0], + sk[0], &pk[0], msg[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], NULL, session_secrand[0], + sk[0], &pk[0], msg[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, + sk[0], &pk[0], msg[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_secrand[0], + sk[0], NULL, msg[0], NULL)); + + /* Generate valid nonces for both signers */ + for (i = 0; i < 2; i++) { + testrand256(session_secrand[i]); + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_secrand[i], + sk[i], &pk[i], msg[i], NULL) == 1); + } + + /* Serialize and parse public nonces */ + CHECK(secp256k1_fullagg_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_pubnonce_serialize(CTX, NULL, &pubnonce[0])); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_pubnonce_serialize(CTX, pubnonce_ser, NULL)); + + CHECK(secp256k1_fullagg_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_pubnonce_parse(CTX, NULL, pubnonce_ser)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_pubnonce_parse(CTX, &pubnonce[0], NULL)); + + /* Nonce aggregation */ + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_agg(CTX, NULL, pubnonce_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_agg(CTX, &aggnonce, NULL, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 0)); + + /* Serialize and parse aggregate nonces */ + CHECK(secp256k1_fullagg_aggnonce_serialize(CTX, aggnonce_ser, &aggnonce) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_aggnonce_serialize(CTX, NULL, &aggnonce)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_aggnonce_serialize(CTX, aggnonce_ser, NULL)); + + CHECK(secp256k1_fullagg_aggnonce_parse(CTX, &aggnonce, aggnonce_ser) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_aggnonce_parse(CTX, NULL, aggnonce_ser)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_aggnonce_parse(CTX, &aggnonce, NULL)); + + /* Session initialization */ + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 2) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_session_init(CTX, NULL, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_session_init(CTX, &session, NULL, pk_ptr, msg_ptr, + pubnonce_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_session_init(CTX, &session, &aggnonce, NULL, msg_ptr, + pubnonce_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, NULL, + pubnonce_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + NULL, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 0)); + + /* Partial signing */ + { + int sign_success = 1; + for (i = 0; i < 2; i++) { + secp256k1_fullagg_secnonce secnonce_tmp; + memcpy(&secnonce_tmp, &secnonce[i], sizeof(secnonce_tmp)); + if (secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce_tmp, &keypair[i], + &session, pk_ptr, msg_ptr, pubnonce_ptr, i) != 1) { + sign_success = 0; + break; + } + /* The secnonce is set to 0 and following signing attempts fail */ + CHECK(secp256k1_memcmp_var(&secnonce_tmp, zeros132, sizeof(secnonce_tmp)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce_tmp, + &keypair[i], &session, pk_ptr, msg_ptr, + pubnonce_ptr, i)); + } + + /* If signing failed due to infinity aggregate nonce, regenerate and try once more */ + if (!sign_success) { + /* Generate new nonces */ + for (i = 0; i < 2; i++) { + testrand256(session_secrand[i]); + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_secrand[i], + sk[i], &pk[i], msg[i], NULL) == 1); + } + + /* Re-aggregate and re-initialize session */ + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 2) == 1); + + /* Try signing again */ + for (i = 0; i < 2; i++) { + secp256k1_fullagg_secnonce secnonce_tmp; + memcpy(&secnonce_tmp, &secnonce[i], sizeof(secnonce_tmp)); + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce_tmp, &keypair[i], + &session, pk_ptr, msg_ptr, pubnonce_ptr, i) == 1); + /* The secnonce is set to 0 and subsequent signing attempts fail */ + CHECK(secp256k1_memcmp_var(&secnonce_tmp, zeros132, sizeof(secnonce_tmp)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce_tmp, + &keypair[i], &session, pk_ptr, msg_ptr, + pubnonce_ptr, i)); + } + } + } + + /* Regenerate nonces and sign again with fresh secnonces */ + { + int sign_success = 0; + int retry_count = 0; + /* Try up to 3 times in case we get unlucky with infinity aggregate nonces */ + while (!sign_success && retry_count < 3) { + retry_count++; + + /* Generate new nonces */ + for (i = 0; i < 2; i++) { + testrand256(session_secrand[i]); + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_secrand[i], + sk[i], &pk[i], msg[i], NULL) == 1); + } + + /* Re-aggregate nonces and re-initialize session with new nonces */ + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 2) == 1); + + /* Try signing with the fresh nonces */ + sign_success = 1; + for (i = 0; i < 2; i++) { + if (secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce[i], &keypair[i], + &session, pk_ptr, msg_ptr, pubnonce_ptr, i) != 1) { + sign_success = 0; + break; + } + } + } + CHECK(sign_success); /* Should succeed within 3 attempts */ + } + + /** Partial signature verification **/ + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pk[0], + &session, pk_ptr, msg_ptr, 0) == 1); + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pk[1], + &session, pk_ptr, msg_ptr, 1) == 1); + /* Wrong signer index */ + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pk[0], + &session, pk_ptr, msg_ptr, 1) == 0); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, NULL, &pubnonce[0], &pk[0], + &session, pk_ptr, msg_ptr, 0)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], NULL, &pk[0], + &session, pk_ptr, msg_ptr, 0)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, + &session, pk_ptr, msg_ptr, 0)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pk[0], + NULL, pk_ptr, msg_ptr, 0)); + + /** Signature aggregation **/ + CHECK(secp256k1_fullagg_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 2) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_agg(CTX, NULL, &session, partial_sig_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_agg(CTX, pre_sig, NULL, partial_sig_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_agg(CTX, pre_sig, &session, NULL, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 0)); + + /** Verification **/ + CHECK(secp256k1_fullagg_verify(CTX, pre_sig, pk_ptr, msg_ptr, 2) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_verify(CTX, NULL, pk_ptr, msg_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_verify(CTX, pre_sig, NULL, msg_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_verify(CTX, pre_sig, pk_ptr, NULL, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_verify(CTX, pre_sig, pk_ptr, msg_ptr, 0)); +} + +/* Test with counter-based nonce generation */ +static void fullagg_nonce_gen_counter_test(void) { + unsigned char sk[2][32]; + secp256k1_keypair keypair[2]; + secp256k1_pubkey pk[2]; + const secp256k1_pubkey *pk_ptr[2]; + secp256k1_fullagg_pubnonce pubnonce[2]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[2]; + secp256k1_fullagg_aggnonce aggnonce; + secp256k1_fullagg_secnonce secnonce[2]; + secp256k1_fullagg_session session; + secp256k1_fullagg_partial_sig partial_sig[2]; + const secp256k1_fullagg_partial_sig *partial_sig_ptr[2]; + unsigned char msg[2][32]; + const unsigned char *msg_ptr[2]; + unsigned char final_sig[64]; + uint64_t nonrepeating_cnt = 0; + int i; + int sign_success; + int retry_count; + + for (i = 0; i < 2; i++) { + testrand256(sk[i]); + testrand256(msg[i]); + CHECK(create_keypair_and_pk_fullagg(&keypair[i], &pk[i], sk[i])); + pk_ptr[i] = &pk[i]; + msg_ptr[i] = msg[i]; + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + } + + sign_success = 0; + retry_count = 0; + while (!sign_success && retry_count < 3) { + retry_count++; + + /* Generate nonces using counter */ + for (i = 0; i < 2; i++) { + CHECK(secp256k1_fullagg_nonce_gen_counter(CTX, &secnonce[i], &pubnonce[i], + nonrepeating_cnt + i, &keypair[i], + msg[i], NULL) == 1); + } + nonrepeating_cnt += 2; /* Increment for next attempt if needed */ + + /* Complete the protocol */ + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 2) == 1); + + sign_success = 1; + for (i = 0; i < 2; i++) { + if (secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce[i], &keypair[i], + &session, pk_ptr, msg_ptr, pubnonce_ptr, i) != 1) { + sign_success = 0; + break; + } + } + } + CHECK(sign_success); + + CHECK(secp256k1_fullagg_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 2) == 1); + CHECK(secp256k1_fullagg_verify(CTX, final_sig, pk_ptr, msg_ptr, 2) == 1); +} + +/* Test serialization round-trip */ +static void fullagg_serialization_test(void) { + secp256k1_fullagg_pubnonce pubnonce, pubnonce2; + secp256k1_fullagg_aggnonce aggnonce, aggnonce2; + secp256k1_fullagg_partial_sig partial_sig, partial_sig2; + unsigned char pubnonce_ser[66]; + unsigned char aggnonce_ser[66]; + unsigned char partial_sig_ser[32]; + unsigned char session_secrand[32]; + unsigned char sk[32]; + unsigned char msg[32]; + secp256k1_pubkey pk; + secp256k1_fullagg_secnonce secnonce; + secp256k1_scalar s; + const secp256k1_fullagg_pubnonce *pubnonce_ptr; + + testrand256(sk); + testrand256(msg); + testrand256(session_secrand); + CHECK(create_keypair_and_pk_fullagg(NULL, &pk, sk)); + + /* Test pubnonce serialization */ + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce, &pubnonce, session_secrand, + sk, &pk, msg, NULL) == 1); + CHECK(secp256k1_fullagg_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce) == 1); + CHECK(secp256k1_fullagg_pubnonce_parse(CTX, &pubnonce2, pubnonce_ser) == 1); + CHECK(secp256k1_memcmp_var(&pubnonce, &pubnonce2, sizeof(pubnonce)) == 0); + + /* Test aggnonce serialization */ + pubnonce_ptr = &pubnonce; + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, &pubnonce_ptr, 1) == 1); + CHECK(secp256k1_fullagg_aggnonce_serialize(CTX, aggnonce_ser, &aggnonce) == 1); + CHECK(secp256k1_fullagg_aggnonce_parse(CTX, &aggnonce2, aggnonce_ser) == 1); + CHECK(secp256k1_memcmp_var(&aggnonce, &aggnonce2, sizeof(aggnonce)) == 0); + + /* Test partial_sig serialization */ + testutil_random_scalar_order_test(&s); + secp256k1_fullagg_partial_sig_save(&partial_sig, &s); + CHECK(secp256k1_fullagg_partial_sig_serialize(CTX, partial_sig_ser, &partial_sig) == 1); + CHECK(secp256k1_fullagg_partial_sig_parse(CTX, &partial_sig2, partial_sig_ser) == 1); + CHECK(secp256k1_memcmp_var(&partial_sig, &partial_sig2, sizeof(partial_sig)) == 0); +} + +static void fullagg_duplicate_pubnonce_test(void) { + unsigned char sk[32]; + secp256k1_keypair keypair; + secp256k1_pubkey pk; + const secp256k1_pubkey *pk_ptr[2]; + secp256k1_fullagg_pubnonce pubnonce[2]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[2]; + secp256k1_fullagg_aggnonce aggnonce; + secp256k1_fullagg_secnonce secnonce; + secp256k1_fullagg_session session; + secp256k1_fullagg_partial_sig partial_sig; + unsigned char msg[2][32]; + const unsigned char *msg_ptr[2]; + unsigned char session_secrand[32]; + + testrand256(sk); + testrand256(msg[0]); + testrand256(msg[1]); + CHECK(create_keypair_and_pk_fullagg(&keypair, &pk, sk)); + + testrand256(session_secrand); + CHECK(secp256k1_fullagg_nonce_gen(CTX, &secnonce, &pubnonce[0], session_secrand, + sk, &pk, msg[0], NULL) == 1); + + /* Duplicate the pubnonce */ + pubnonce[1] = pubnonce[0]; + + pk_ptr[0] = &pk; + pk_ptr[1] = &pk; + msg_ptr[0] = msg[0]; + msg_ptr[1] = msg[1]; + pubnonce_ptr[0] = &pubnonce[0]; + pubnonce_ptr[1] = &pubnonce[1]; + + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, 2) == 1); + + /* Fails because R2 appears twice */ + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig, &secnonce, &keypair, + &session, pk_ptr, msg_ptr, pubnonce_ptr, 0) == 0); +} + +static void run_schnorrsig_fullagg_tests(void) { + int i; + + for (i = 0; i < COUNT; i++) { + fullagg_simple_test(); + } + fullagg_api_tests(); + fullagg_nonce_gen_counter_test(); + fullagg_serialization_test(); + fullagg_duplicate_pubnonce_test(); +} + +#endif /* SECP256K1_MODULE_SCHNORRSIG_FULLAGG_TESTS_IMPL_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index 26336a45cc..a5eefa5c84 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -810,6 +810,10 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, # include "modules/schnorrsig/main_impl.h" #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG +# include "modules/schnorrsig_fullagg/main_impl.h" +#endif + #ifdef ENABLE_MODULE_MUSIG # include "modules/musig/main_impl.h" #endif diff --git a/src/tests.c b/src/tests.c index cb3b3c4248..a9a63dbed5 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7434,6 +7434,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/schnorrsig/tests_impl.h" #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG +# include "modules/schnorrsig_fullagg/tests_impl.h" +#endif + #ifdef ENABLE_MODULE_MUSIG # include "modules/musig/tests_impl.h" #endif @@ -7802,6 +7806,10 @@ int main(int argc, char **argv) { run_schnorrsig_tests(); #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG + run_schnorrsig_fullagg_tests(); +#endif + #ifdef ENABLE_MODULE_MUSIG run_musig_tests(); #endif