From c218cee30d2539f2903e050df741edaadeeaeda3 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Tue, 9 Sep 2025 23:51:40 +0200 Subject: [PATCH 01/10] refactor: Move parsing helpers from musig to eckey The naming of these functions suggested they should be moved to group.h but this didn't seem practical since these functions depended on eckey.h internally which is higher level than group.h. --- src/eckey.h | 3 +++ src/eckey_impl.h | 26 ++++++++++++++++++++++++++ src/modules/musig/session_impl.h | 32 +++----------------------------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/eckey.h b/src/eckey.h index c2bbc4703e..5b328a80c0 100644 --- a/src/eckey.h +++ b/src/eckey.h @@ -20,6 +20,9 @@ static void secp256k1_eckey_pubkey_serialize33(secp256k1_ge *elem, unsigned char /** Serialize a group element (that is not allowed to be infinity) to an uncompressed public key (65 bytes). */ static void secp256k1_eckey_pubkey_serialize65(secp256k1_ge *elem, unsigned char *pub65); +static 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 57024e409d..30cf03914d 100644 --- a/src/eckey_impl.h +++ b/src/eckey_impl.h @@ -54,6 +54,32 @@ static void secp256k1_eckey_pubkey_serialize65(secp256k1_ge *elem, unsigned char secp256k1_fe_get_b32(&pub65[33], &elem->y); } +/* 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 { + /* Serialize must succeed because the point is not at infinity */ + secp256k1_eckey_pubkey_serialize33(ge, out33); + } +} + +/* 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 c05801eecf..924738a3d4 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -19,32 +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 { - /* Serialize must succeed because the point is not at infinity */ - secp256k1_eckey_pubkey_serialize33(ge, out33); - } -} - -/* 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) { @@ -233,7 +207,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; } } @@ -254,7 +228,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; } @@ -546,7 +520,7 @@ static void secp256k1_musig_compute_noncehash(const secp256k1_hash_ctx *hash_ctx 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(hash_ctx, &sha, buf, sizeof(buf)); } secp256k1_sha256_write(hash_ctx, &sha, agg_pk32, 32); From ede95ae94ca99baebfc101b167bf0f0c716dd7fa Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 12 Sep 2025 17:55:17 +0200 Subject: [PATCH 02/10] fullagg: Add module --- .../schnorrsig_fullagg/Makefile.am.include | 4 + src/modules/schnorrsig_fullagg/main_impl.h | 1144 +++++++++++++++++ src/modules/schnorrsig_fullagg/tests_impl.h | 580 +++++++++ src/modules/schnorrsig_fullagg/vectors.h | 679 ++++++++++ src/secp256k1.c | 4 + src/tests.c | 7 + tools/test_vectors_fullagg_generate.py | 212 +++ 7 files changed, 2630 insertions(+) create mode 100644 src/modules/schnorrsig_fullagg/Makefile.am.include create mode 100644 src/modules/schnorrsig_fullagg/main_impl.h create mode 100644 src/modules/schnorrsig_fullagg/tests_impl.h create mode 100644 src/modules/schnorrsig_fullagg/vectors.h create mode 100755 tools/test_vectors_fullagg_generate.py diff --git a/src/modules/schnorrsig_fullagg/Makefile.am.include b/src/modules/schnorrsig_fullagg/Makefile.am.include new file mode 100644 index 0000000000..0ac566f4c9 --- /dev/null +++ b/src/modules/schnorrsig_fullagg/Makefile.am.include @@ -0,0 +1,4 @@ +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 +noinst_HEADERS += src/modules/schnorrsig_fullagg/vectors.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..98deb2655a --- /dev/null +++ b/src/modules/schnorrsig_fullagg/main_impl.h @@ -0,0 +1,1144 @@ +/*********************************************************************** + * 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 "../../ecmult.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 }; + +/* Save aggregate nonce. The points are guaranteed not to be the point at + * infinity because nonce aggregation fails in that case. */ +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(&nonce->data[4 + 64*i], &ges[i]); + } +} + +/* 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(&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++) { + secp256k1_eckey_pubkey_serialize33(&ges[i], &out66[33*i]); + } + return 1; +} + +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); + + /* The aggregate nonce consists of two compressed points. There is no + * encoding for the point at infinity because nonce aggregation fails in + * that case. */ + 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_aggnonce_save(nonce, ges); + return 1; +} + +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_pubkey_serialize33(&ges[i], &out66[33*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: + * r_i = int(hash_{FullAgg/nonce}(rand || extra_in || i)) mod n */ +static void secp256k1_fullagg_nonce_function(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *k, + const unsigned char *session_secrand, const unsigned char *seckey32, + 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(hash_ctx, &sha, session_secrand, 32); + secp256k1_sha256_finalize(hash_ctx, &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(hash_ctx, &sha, rand, sizeof(rand)); + if (extra_input32 != NULL) { + secp256k1_sha256_write(hash_ctx, &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(hash_ctx, &sha_tmp, &i, 1); + secp256k1_sha256_finalize(hash_ctx, &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_xonly_pubkey *pubkey, + const unsigned char *extra_input32) { + secp256k1_scalar k[2]; + secp256k1_ge nonce_pts[2]; + secp256k1_gej nonce_ptj[2]; + int i; + secp256k1_ge pk; + 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_xonly_pubkey_load(ctx, &pk, pubkey)) { + return 0; + } + + /* Get secret nonce */ + secp256k1_fullagg_nonce_function(secp256k1_get_hash_context(ctx), k, input_nonce, seckey, 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_gej(&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(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_xonly_pubkey *pubkey, + 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, 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 *extra_input32) { + unsigned char buf[32] = { 0 }; + unsigned char seckey[32]; + secp256k1_xonly_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); + /* keypair_sec and keypair_xonly_pub do not fail if the arguments are not NULL */ + ret = secp256k1_keypair_sec(ctx, seckey, keypair); + VERIFY_CHECK(ret); + ret = secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, keypair); + VERIFY_CHECK(ret); +#ifndef VERIFY + (void) ret; +#endif + + ret = secp256k1_fullagg_nonce_gen_internal(ctx, secnonce, pubnonce, buf, seckey, + &pubkey, extra_input32); + secp256k1_memclear_explicit(seckey, sizeof(seckey)); + return ret; +} + +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; +} + +/* 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]; + size_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(aggnonce != NULL); + ARG_CHECK(pubnonces != NULL); + ARG_CHECK(n_pubnonces > 0); + for (i = 0; i < n_pubnonces; i++) { + ARG_CHECK(pubnonces[i] != NULL); + } + + if (!secp256k1_fullagg_sum_pubnonces(ctx, aggnonce_ptsj, pubnonces, n_pubnonces)) { + return 0; + } + if (secp256k1_gej_is_infinity(&aggnonce_ptsj[0]) || secp256k1_gej_is_infinity(&aggnonce_ptsj[1])) { + 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_noncecoef: b = H_noncecoef(cbytes(R1) || cbytes(R2) || pk_i || m_i || cbytes(R2_i) for all signers). + * aggnonce_ser66 is the serialized aggregate nonce cbytes(R1) || cbytes(R2). */ +static int secp256k1_fullagg_compute_noncehash(const secp256k1_context* ctx, + unsigned char *noncehash, + const unsigned char *aggnonce_ser66, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_signers) { + const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx); + unsigned char buf[33]; + size_t i; + secp256k1_sha256 sha; + secp256k1_fullagg_sha256_tagged_noncecoef(&sha); + + secp256k1_sha256_write(hash_ctx, &sha, aggnonce_ser66, 66); + + /* Write pk_i || m_i || cbytes(R2_i) for all signers */ + for (i = 0; i < n_signers; i++) { + secp256k1_ge pk_ge, nonce_pts[2]; + + /* Load and write pk_i */ + if (!secp256k1_xonly_pubkey_load(ctx, &pk_ge, pubkeys[i])) { + return 0; + } + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(hash_ctx, &sha, buf, 32); + + /* Write m_i */ + secp256k1_sha256_write(hash_ctx, &sha, messages[i], 32); + + /* Load and write R2_i. The pubnonce points are never the point at + * infinity because parsing and generation do not allow it. */ + if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[i])) { + return 0; + } + secp256k1_eckey_pubkey_serialize33(&nonce_pts[1], buf); + secp256k1_sha256_write(hash_ctx, &sha, buf, 33); + } + + secp256k1_sha256_finalize(hash_ctx, &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: c_i = H_sig(L || bytes(R) || pk_i || m_i) where L is the + * list of all pk_i || m_i pairs */ +static int secp256k1_fullagg_compute_sighash(const secp256k1_context* ctx, + secp256k1_scalar *c_i, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers, + const unsigned char *r32, + size_t signer_index) { + const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx); + 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 pk_i || m_i) */ + for (i = 0; i < n_signers; i++) { + if (!secp256k1_xonly_pubkey_load(ctx, &pk_ge, pubkeys[i])) { + return 0; + } + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(hash_ctx, &sha, buf, 32); + secp256k1_sha256_write(hash_ctx, &sha, messages[i], 32); + } + + /* Write bytes(R) */ + secp256k1_sha256_write(hash_ctx, &sha, r32, 32); + + /* Write pk_i for this signer */ + if (!secp256k1_xonly_pubkey_load(ctx, &pk_ge, pubkeys[signer_index])) { + return 0; + } + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(hash_ctx, &sha, buf, 32); + + /* Write m_i for this signer */ + secp256k1_sha256_write(hash_ctx, &sha, messages[signer_index], 32); + + secp256k1_sha256_finalize(hash_ctx, &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, + const unsigned char *aggnonce_ser66, + 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); + ptr += 8; + memcpy(ptr, aggnonce_ser66, 66); +} + +static int secp256k1_fullagg_session_load(const secp256k1_context* ctx, + unsigned char *fin_nonce, + int *fin_nonce_parity, + secp256k1_scalar *noncecoef, + unsigned char *aggnonce_ser66, + 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); + ptr += 8; + memcpy(aggnonce_ser66, ptr, 66); + + return 1; +} + + +/* TODO: Share with MuSig */ +/* out_nonce = nonce_pts[0] + b*nonce_pts[1] */ +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); +} + +/* Initialize a FullAgg session. This implements GetSessionValues. */ +int secp256k1_fullagg_session_init(const secp256k1_context* ctx, secp256k1_fullagg_session *session, + const secp256k1_fullagg_aggnonce *aggnonce, + const secp256k1_xonly_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]; + unsigned char aggnonce_ser[66]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + size_t i; + + 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); + for (i = 0; i < n_signers; i++) { + ARG_CHECK(pubkeys[i] != NULL); + ARG_CHECK(messages[i] != NULL); + ARG_CHECK(pubnonces[i] != NULL); + } + + if (!secp256k1_fullagg_aggnonce_load(ctx, aggnonce_pts, aggnonce)) { + return 0; + } + secp256k1_eckey_pubkey_serialize33(&aggnonce_pts[0], &aggnonce_ser[0]); + secp256k1_eckey_pubkey_serialize33(&aggnonce_pts[1], &aggnonce_ser[33]); + + /* Compute nonce coefficient b */ + if (!secp256k1_fullagg_compute_noncehash(ctx, noncehash, aggnonce_ser, + pubkeys, messages, pubnonces, n_signers)) { + return 0; + } + secp256k1_scalar_set_b32(&noncecoef, noncehash, NULL); + + /* Compute final nonce R = R1 + b*R2 and fail if it is the point at + * infinity */ + secp256k1_fullagg_effective_nonce(&rj, aggnonce_pts, &noncecoef); + secp256k1_ge_set_gej(&r, &rj); + if (secp256k1_ge_is_infinity(&r)) { + return 0; + } + + 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, aggnonce_ser, 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 unsigned char *msg32, + const secp256k1_fullagg_session *session, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_signers, + 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; + unsigned char aggnonce_ser[66]; + size_t n_session_signers; + size_t i; + int ret; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secnonce != NULL); + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(keypair != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(messages != NULL); + ARG_CHECK(pubnonces != NULL); + ARG_CHECK(n_signers > 0); + for (i = 0; i < n_signers; i++) { + ARG_CHECK(pubkeys[i] != NULL); + ARG_CHECK(messages[i] != NULL); + ARG_CHECK(pubnonces[i] != NULL); + } + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + + /* Load and invalidate secnonce */ + ret = secp256k1_fullagg_secnonce_load(ctx, k, &pk, secnonce); + /* Always clear the secnonce to avoid nonce reuse */ + secp256k1_memzero_explicit(secnonce, 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. The secnonce stores the point + * with even Y, so only the X coordinates are compared. */ + if (!secp256k1_fe_equal(&pk.x, &keypair_pk.x)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* d' = d if has_even_y(P), otherwise d' = n - d */ + secp256k1_fe_normalize_var(&keypair_pk.y); + if (secp256k1_fe_is_odd(&keypair_pk.y)) { + secp256k1_scalar_negate(&sk, &sk); + } + + if (!secp256k1_fullagg_session_load(ctx, fin_nonce, &fin_nonce_parity, &noncecoef, aggnonce_ser, &n_session_signers, session)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + ARG_CHECK(n_signers == n_session_signers); + + if (signer_index >= n_signers) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Verify that the public key at the signer's position matches the + * signer's key and that the message at the signer's position is the + * message the signer intends to sign. Omitting the message check would + * let a malicious coordinator substitute a different message at the + * signer's position. */ + { + secp256k1_ge signer_pk; + if (!secp256k1_xonly_pubkey_load(ctx, &signer_pk, pubkeys[signer_index])) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + if (!secp256k1_fe_equal(&keypair_pk.x, &signer_pk.x)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + } + if (secp256k1_memcmp_var(messages[signer_index], msg32, 32) != 0) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Verify this signer's R2 appears exactly once at the correct index. The + * uniqueness check is security-critical: if the signer's R2 appears at a + * second index, signing enables a key extraction attack (see Section 4.2 + * of the DahLIAS paper). */ + { + 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]. The point is public because it has been + * broadcast as part of the pubnonce, so it can be declassified. */ + secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &r2j, &k[1]); + secp256k1_ge_set_gej(&r2_self, &r2j); + secp256k1_declassify(ctx, &r2_self, sizeof(r2_self)); + secp256k1_gej_clear(&r2j); + + /* Check all pubnonces for our R2. The pubnonce points are never the + * point at infinity, so a match requires r2_self not to be infinity + * either. */ + 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; + } + + if (!secp256k1_ge_is_infinity(&r2_self) + && 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; + } + } + + /* R2 must appear exactly once and at the signer's index */ + if (found_count != 1 || found_index != (int)signer_index) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + } + + /* Verify that the session was initialized with the same aggregate nonce + * and the same lists of public keys, messages and pubnonces by + * recomputing the nonce coefficient */ + { + unsigned char noncehash[32]; + secp256k1_scalar noncecoef_check; + if (!secp256k1_fullagg_compute_noncehash(ctx, noncehash, aggnonce_ser, pubkeys, + messages, pubnonces, n_signers)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + secp256k1_scalar_set_b32(&noncecoef_check, noncehash, NULL); + if (!secp256k1_scalar_eq(&noncecoef_check, &noncecoef)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + } + + /* e = 1 if has_even_y(R), otherwise e = n - 1. Multiply the secret + * nonces by e. */ + 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, + fin_nonce, signer_index)) { + secp256k1_fullagg_partial_sign_clear(&sk, k); + return 0; + } + + /* Compute s_i = e*(k1_i + b*k2_i) + c_i*d' */ + 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*d' (reuse k[0]) */ + secp256k1_scalar_add(&s, &s, &k[0]); /* + c_i*d' */ + + secp256k1_fullagg_partial_sig_save(partial_sig, &s); + + /* Clear sensitive data */ + secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_scalar_clear(&s); + + return 1; +} + +/* Verify a partial signature. The aggregate nonce and the session values are + * recomputed from the pubnonces so that the result cannot be influenced by + * the coordinator. */ +int secp256k1_fullagg_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_fullagg_partial_sig *partial_sig, + const secp256k1_fullagg_pubnonce * const *pubnonces, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers, + size_t signer_index) { + secp256k1_gej aggnonce_ptsj[2]; + secp256k1_ge aggnonce_pts[2]; + unsigned char noncehash[32]; + unsigned char fin_nonce[32]; + unsigned char aggnonce_ser[66]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + secp256k1_scalar s, c_i; + secp256k1_ge pk, r; + secp256k1_ge nonce_pts[2]; + secp256k1_gej rj, pkj, tmp; + size_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(pubnonces != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(messages != NULL); + ARG_CHECK(n_signers > 0); + for (i = 0; i < n_signers; i++) { + ARG_CHECK(pubnonces[i] != NULL); + ARG_CHECK(pubkeys[i] != NULL); + ARG_CHECK(messages[i] != NULL); + } + + if (signer_index >= n_signers) { + return 0; + } + + if (!secp256k1_fullagg_partial_sig_load(ctx, &s, partial_sig)) { + return 0; + } + + /* aggnonce = NonceAgg(pubnonce_0..u-1) */ + if (!secp256k1_fullagg_sum_pubnonces(ctx, aggnonce_ptsj, pubnonces, n_signers)) { + return 0; + } + if (secp256k1_gej_is_infinity(&aggnonce_ptsj[0]) || secp256k1_gej_is_infinity(&aggnonce_ptsj[1])) { + return 0; + } + secp256k1_ge_set_all_gej_var(aggnonce_pts, aggnonce_ptsj, 2); + secp256k1_eckey_pubkey_serialize33(&aggnonce_pts[0], &aggnonce_ser[0]); + secp256k1_eckey_pubkey_serialize33(&aggnonce_pts[1], &aggnonce_ser[33]); + + /* (R, b) = GetSessionValues(aggnonce, pk_0..u-1, m_0..u-1, pubnonce_0..u-1) */ + if (!secp256k1_fullagg_compute_noncehash(ctx, noncehash, aggnonce_ser, + pubkeys, messages, pubnonces, n_signers)) { + return 0; + } + secp256k1_scalar_set_b32(&noncecoef, noncehash, NULL); + secp256k1_fullagg_effective_nonce(&rj, aggnonce_pts, &noncecoef); + secp256k1_ge_set_gej(&r, &rj); + if (secp256k1_ge_is_infinity(&r)) { + return 0; + } + 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); + + /* Compute effective nonce of this signer: R'_i = R1_i + b*R2_i */ + if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[signer_index])) { + return 0; + } + secp256k1_fullagg_effective_nonce(&rj, nonce_pts, &noncecoef); + + /* Multiply by e: negate if R has odd Y */ + if (fin_nonce_parity) { + secp256k1_gej_neg(&rj, &rj); + } + + /* P_i = lift_x(int(pk_i)) */ + if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkeys[signer_index])) { + return 0; + } + + /* Compute signer's challenge c_i */ + if (!secp256k1_fullagg_compute_sighash(ctx, &c_i, pubkeys, messages, n_signers, + fin_nonce, signer_index)) { + return 0; + } + + /* Verify: s_i*G = e*R'_i + c_i*P_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); + + return secp256k1_gej_is_infinity(&tmp); +} + +/* 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]; + unsigned char aggnonce_ser[66]; + int fin_nonce_parity; + secp256k1_scalar noncecoef; + size_t n_signers; + secp256k1_scalar s_agg; + size_t i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(partial_sigs != NULL); + ARG_CHECK(n_sigs > 0); + for (i = 0; i < n_sigs; i++) { + ARG_CHECK(partial_sigs[i] != NULL); + } + + if (!secp256k1_fullagg_session_load(ctx, fin_nonce, &fin_nonce_parity, &noncecoef, aggnonce_ser, &n_signers, session)) { + return 0; + } + + ARG_CHECK(n_sigs == n_signers); + + /* 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; +} + +typedef struct { + const secp256k1_context *ctx; + /* Hash state after writing the tagged hash midstate and L, the list of + * all pk_i || m_i */ + secp256k1_sha256 sha_l; + const unsigned char *sig64; + const secp256k1_xonly_pubkey * const *pubkeys; + const unsigned char * const *messages; +} secp256k1_fullagg_verify_ecmult_data; + +/* Provides the c_i*P_i terms for the verification multi-exponentiation */ +static int secp256k1_fullagg_verify_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_fullagg_verify_ecmult_data *ecmult_data = (secp256k1_fullagg_verify_ecmult_data *)data; + const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ecmult_data->ctx); + secp256k1_sha256 sha = ecmult_data->sha_l; + unsigned char buf[32]; + + if (!secp256k1_xonly_pubkey_load(ecmult_data->ctx, pt, ecmult_data->pubkeys[idx])) { + return 0; + } + /* c_i = hash_sig(L || bytes(R) || pk_i || m_i) */ + secp256k1_sha256_write(hash_ctx, &sha, ecmult_data->sig64, 32); + secp256k1_fe_get_b32(buf, &pt->x); + secp256k1_sha256_write(hash_ctx, &sha, buf, 32); + secp256k1_sha256_write(hash_ctx, &sha, ecmult_data->messages[idx], 32); + secp256k1_sha256_finalize(hash_ctx, &sha, buf); + secp256k1_scalar_set_b32(sc, buf, NULL); + return 1; +} + +/* Verify a FullAgg aggregate signature */ +int secp256k1_fullagg_verify(const secp256k1_context* ctx, const unsigned char *sig64, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers) { + const secp256k1_hash_ctx *hash_ctx; + secp256k1_fullagg_verify_ecmult_data ecmult_data; + secp256k1_scalar s; + secp256k1_ge r; + secp256k1_gej resj; + unsigned char buf[32]; + 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); + for (i = 0; i < n_signers; i++) { + ARG_CHECK(pubkeys[i] != NULL); + ARG_CHECK(messages[i] != NULL); + } + + /* 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; + } + + /* R = lift_x(int(r)) */ + if (!secp256k1_ge_set_xo_var(&r, &r.x, 0)) { + return 0; + } + + /* Precompute the hash state after writing L, the list of all pk_i || m_i. + * Computing it once here instead of once per challenge avoids hashing + * quadratic in the number of signers. */ + hash_ctx = secp256k1_get_hash_context(ctx); + secp256k1_fullagg_sha256_tagged_sig(&ecmult_data.sha_l); + for (i = 0; i < n_signers; i++) { + secp256k1_ge pk_ge; + if (!secp256k1_xonly_pubkey_load(ctx, &pk_ge, pubkeys[i])) { + return 0; + } + secp256k1_fe_get_b32(buf, &pk_ge.x); + secp256k1_sha256_write(hash_ctx, &ecmult_data.sha_l, buf, 32); + secp256k1_sha256_write(hash_ctx, &ecmult_data.sha_l, messages[i], 32); + } + ecmult_data.ctx = ctx; + ecmult_data.sig64 = sig64; + ecmult_data.pubkeys = pubkeys; + ecmult_data.messages = messages; + + /* Compute resj = -s*G + sum(c_i*P_i). Note that the public keys are never + * the point at infinity because they are the result of lift_x. */ + /* TODO: actually use optimized ecmult_multi algorithms by providing a + * scratch space */ + secp256k1_scalar_negate(&s, &s); + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &resj, &s, secp256k1_fullagg_verify_ecmult_callback, (void *) &ecmult_data, n_signers)) { + return 0; + } + + /* Verify: s*G = R + sum(c_i*P_i) */ + secp256k1_gej_add_ge_var(&resj, &resj, &r, NULL); + return secp256k1_gej_is_infinity(&resj); +} + +#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..1d8f17e7f2 --- /dev/null +++ b/src/modules/schnorrsig_fullagg/tests_impl.h @@ -0,0 +1,580 @@ +/*********************************************************************** + * 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" + +#include "vectors.h" + +static int create_keypair_and_pk_fullagg(secp256k1_keypair *keypair, secp256k1_xonly_pubkey *pk, const unsigned char *sk) { + int ret; + secp256k1_keypair keypair_tmp; + ret = secp256k1_keypair_create(CTX, &keypair_tmp, sk); + ret &= secp256k1_keypair_xonly_pub(CTX, pk, NULL, &keypair_tmp); + if (keypair != NULL) { + *keypair = keypair_tmp; + } + return ret; +} + +/* Simple test with three signers */ +static void fullagg_simple_test_internal(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_xonly_pubkey pk[3]; + const secp256k1_xonly_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; + + 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])); + } + + /* 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], 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 and verify their partial signatures */ + for (i = 0; i < 3; i++) { + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce[i], + &keypair[i], msg[i], &session, pk_ptr, msg_ptr, + pubnonce_ptr, 3, i) == 1); + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[i], pubnonce_ptr, + pk_ptr, msg_ptr, 3, i) == 1); + } + + /* 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 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 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_xonly_pubkey pk[2]; + const secp256k1_xonly_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], 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], 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], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], NULL, session_secrand[0], + sk[0], &pk[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, + sk[0], &pk[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_secrand[0], + sk[0], NULL, 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], 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 requires a context with the generator context built */ + CHECK_ILLEGAL(STATIC_CTX, secp256k1_fullagg_partial_sign(STATIC_CTX, &partial_sig[0], &secnonce[0], + &keypair[0], msg[0], &session, pk_ptr, msg_ptr, + pubnonce_ptr, 2, 0)); + + /* n_signers must match the number of signers in the session */ + { + secp256k1_fullagg_secnonce secnonce_tmp; + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &keypair[0], + msg[0], &session, pk_ptr, msg_ptr, + pubnonce_ptr, 1, 0)); + } + + /* Signing fails if the lists differ from those the session was + * initialized with */ + { + secp256k1_fullagg_secnonce secnonce_tmp; + unsigned char other_msg[32]; + const unsigned char *wrong_msg_ptr[2]; + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + memcpy(other_msg, msg[1], 32); + other_msg[0] ^= 1; + wrong_msg_ptr[0] = msg[0]; + wrong_msg_ptr[1] = other_msg; + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &keypair[0], + msg[0], &session, pk_ptr, wrong_msg_ptr, + pubnonce_ptr, 2, 0) == 0); + } + + /* Partial signing */ + 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], + msg[i], &session, pk_ptr, msg_ptr, pubnonce_ptr, 2, i) == 1); + /* 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], msg[i], &session, pk_ptr, msg_ptr, + pubnonce_ptr, 2, i)); + } + + /** Partial signature verification **/ + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], pubnonce_ptr, pk_ptr, + msg_ptr, 2, 0) == 1); + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[1], pubnonce_ptr, pk_ptr, + msg_ptr, 2, 1) == 1); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, NULL, pubnonce_ptr, pk_ptr, + msg_ptr, 2, 0)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], NULL, pk_ptr, + msg_ptr, 2, 0)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], pubnonce_ptr, NULL, + msg_ptr, 2, 0)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[0], pubnonce_ptr, pk_ptr, + NULL, 2, 0)); + + /** Signature aggregation **/ + CHECK(secp256k1_fullagg_partial_sig_agg(CTX, 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, sig, NULL, partial_sig_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_agg(CTX, sig, &session, NULL, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_partial_sig_agg(CTX, sig, &session, partial_sig_ptr, 0)); + + /** Verification **/ + CHECK(secp256k1_fullagg_verify(CTX, 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, sig, NULL, msg_ptr, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_verify(CTX, sig, pk_ptr, NULL, 2)); + CHECK_ILLEGAL(CTX, secp256k1_fullagg_verify(CTX, 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_xonly_pubkey pk[2]; + const secp256k1_xonly_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; + + 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]; + } + + /* 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], + NULL) == 1); + } + + /* 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); + + for (i = 0; i < 2; i++) { + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig[i], &secnonce[i], &keypair[i], + msg[i], &session, pk_ptr, msg_ptr, pubnonce_ptr, 2, i) == 1); + } + + 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]; + secp256k1_xonly_pubkey pk; + secp256k1_fullagg_secnonce secnonce; + secp256k1_scalar s; + const secp256k1_fullagg_pubnonce *pubnonce_ptr; + + testrand256(sk); + 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, 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); +} + +/* Writes a test vector secnonce (two secret nonce scalars) into a secnonce + * object */ +static void fullagg_test_set_secnonce(secp256k1_fullagg_secnonce *secnonce, const unsigned char *secnonce64, const secp256k1_xonly_pubkey *pubkey) { + secp256k1_ge pk; + secp256k1_scalar k[2]; + + secp256k1_scalar_set_b32(&k[0], &secnonce64[0], NULL); + secp256k1_scalar_set_b32(&k[1], &secnonce64[32], NULL); + CHECK(secp256k1_xonly_pubkey_load(CTX, &pk, pubkey)); + secp256k1_fullagg_secnonce_save(secnonce, k, &pk); +} + +/* Computes the pubnonce (R1_i, R2_i) = (k1_i*G, k2_i*G) corresponding to a + * test vector secnonce */ +static void fullagg_test_pubnonce_from_secnonce(secp256k1_fullagg_pubnonce *pubnonce, const unsigned char *secnonce64) { + unsigned char pubnonce_ser[66]; + secp256k1_pubkey tmp; + int i; + + for (i = 0; i < 2; i++) { + size_t len = 33; + CHECK(secp256k1_ec_pubkey_create(CTX, &tmp, &secnonce64[32*i])); + CHECK(secp256k1_ec_pubkey_serialize(CTX, &pubnonce_ser[33*i], &len, &tmp, SECP256K1_EC_COMPRESSED)); + } + CHECK(secp256k1_fullagg_pubnonce_parse(CTX, pubnonce, pubnonce_ser)); +} + +static void fullagg_test_vectors_sign(void) { + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(fullagg_sign_cases); i++) { + const struct fullagg_sign_case *c = &fullagg_sign_cases[i]; + secp256k1_keypair keypair[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_xonly_pubkey pk[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_xonly_pubkey *pk_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + const unsigned char *msg_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_pubnonce pubnonce[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_partial_sig partial_sig[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_fullagg_partial_sig *partial_sig_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_aggnonce aggnonce; + unsigned char aggnonce_ser[66]; + secp256k1_fullagg_session session; + unsigned char sig[64]; + + for (j = 0; j < c->n_signers; j++) { + CHECK(create_keypair_and_pk_fullagg(&keypair[j], &pk[j], c->sks[j])); + fullagg_test_pubnonce_from_secnonce(&pubnonce[j], c->secnonces[j]); + pk_ptr[j] = &pk[j]; + msg_ptr[j] = c->msgs[j]; + pubnonce_ptr[j] = &pubnonce[j]; + partial_sig_ptr[j] = &partial_sig[j]; + } + + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, c->n_signers)); + CHECK(secp256k1_fullagg_aggnonce_serialize(CTX, aggnonce_ser, &aggnonce)); + CHECK(secp256k1_memcmp_var(aggnonce_ser, c->expected_aggnonce, sizeof(aggnonce_ser)) == 0); + + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, c->n_signers)); + + for (j = 0; j < c->n_signers; j++) { + unsigned char psig_ser[32]; + secp256k1_fullagg_secnonce secnonce; + + fullagg_test_set_secnonce(&secnonce, c->secnonces[j], &pk[j]); + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig[j], &secnonce, &keypair[j], + c->msgs[j], &session, pk_ptr, msg_ptr, pubnonce_ptr, + c->n_signers, j)); + CHECK(secp256k1_fullagg_partial_sig_serialize(CTX, psig_ser, &partial_sig[j])); + CHECK(secp256k1_memcmp_var(psig_ser, c->expected_psigs[j], sizeof(psig_ser)) == 0); + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig[j], pubnonce_ptr, pk_ptr, + msg_ptr, c->n_signers, j)); + } + + CHECK(secp256k1_fullagg_partial_sig_agg(CTX, sig, &session, partial_sig_ptr, c->n_signers)); + CHECK(secp256k1_memcmp_var(sig, c->expected_sig, sizeof(sig)) == 0); + CHECK(secp256k1_fullagg_verify(CTX, sig, pk_ptr, msg_ptr, c->n_signers)); + } +} + +static void fullagg_test_vectors_sign_error(void) { + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(fullagg_sign_error_cases); i++) { + const struct fullagg_sign_error_case *c = &fullagg_sign_error_cases[i]; + secp256k1_keypair keypair; + secp256k1_xonly_pubkey signer_pk; + secp256k1_xonly_pubkey pk[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_xonly_pubkey *pk_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + const unsigned char *msg_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_pubnonce pubnonce[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_aggnonce aggnonce; + secp256k1_fullagg_session session; + secp256k1_fullagg_secnonce secnonce; + secp256k1_fullagg_partial_sig partial_sig; + + CHECK(create_keypair_and_pk_fullagg(&keypair, &signer_pk, c->sk)); + for (j = 0; j < c->n_signers; j++) { + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk[j], c->pubkeys[j])); + CHECK(secp256k1_fullagg_pubnonce_parse(CTX, &pubnonce[j], c->pubnonces[j])); + pk_ptr[j] = &pk[j]; + msg_ptr[j] = c->msgs[j]; + pubnonce_ptr[j] = &pubnonce[j]; + } + + CHECK(secp256k1_fullagg_nonce_agg(CTX, &aggnonce, pubnonce_ptr, c->n_signers)); + CHECK(secp256k1_fullagg_session_init(CTX, &session, &aggnonce, pk_ptr, msg_ptr, + pubnonce_ptr, c->n_signers)); + /* The signer's slot is index 0 in all sign error vectors. Signing + * must fail. */ + fullagg_test_set_secnonce(&secnonce, c->secnonce, &signer_pk); + CHECK(secp256k1_fullagg_partial_sign(CTX, &partial_sig, &secnonce, &keypair, + c->msg, &session, pk_ptr, msg_ptr, pubnonce_ptr, + c->n_signers, 0) == 0); + } +} + +static void fullagg_test_vectors_verify(void) { + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(fullagg_verify_cases); i++) { + const struct fullagg_verify_case *c = &fullagg_verify_cases[i]; + secp256k1_xonly_pubkey pk[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_xonly_pubkey *pk_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + const unsigned char *msg_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + int parse_ok = 1; + + for (j = 0; j < c->n_signers; j++) { + parse_ok &= secp256k1_xonly_pubkey_parse(CTX, &pk[j], c->pubkeys[j]); + pk_ptr[j] = &pk[j]; + msg_ptr[j] = c->msgs[j]; + } + /* A public key that is not on the curve fails to parse */ + if (!parse_ok) { + CHECK(!c->expected); + continue; + } + CHECK(secp256k1_fullagg_verify(CTX, c->sig, pk_ptr, msg_ptr, c->n_signers) == c->expected); + } +} + +static void fullagg_test_vectors_partial_sig_verify(void) { + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(fullagg_partial_sig_verify_cases); i++) { + const struct fullagg_partial_sig_verify_case *c = &fullagg_partial_sig_verify_cases[i]; + secp256k1_xonly_pubkey pk[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_xonly_pubkey *pk_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + const unsigned char *msg_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_pubnonce pubnonce[FULLAGG_VECTORS_MAX_SIGNERS]; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[FULLAGG_VECTORS_MAX_SIGNERS]; + secp256k1_fullagg_partial_sig partial_sig; + + for (j = 0; j < c->n_signers; j++) { + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk[j], c->pubkeys[j])); + CHECK(secp256k1_fullagg_pubnonce_parse(CTX, &pubnonce[j], c->pubnonces[j])); + pk_ptr[j] = &pk[j]; + msg_ptr[j] = c->msgs[j]; + pubnonce_ptr[j] = &pubnonce[j]; + } + + /* A partial signature that is not less than the group order fails to + * parse */ + if (!secp256k1_fullagg_partial_sig_parse(CTX, &partial_sig, c->psig)) { + CHECK(!c->expected); + continue; + } + CHECK(secp256k1_fullagg_partial_sig_verify(CTX, &partial_sig, pubnonce_ptr, pk_ptr, + msg_ptr, c->n_signers, c->signer_index) == c->expected); + } +} + +static void fullagg_test_vectors_tweak(void) { + size_t i; + + for (i = 0; i < ARRAY_SIZE(fullagg_tweak_cases); i++) { + const struct fullagg_tweak_case *c = &fullagg_tweak_cases[i]; + unsigned char sk[32]; + unsigned char pk_ser[33]; + size_t len = 33; + secp256k1_pubkey pk; + + if (c->is_xonly) { + secp256k1_keypair keypair; + CHECK(secp256k1_keypair_create(CTX, &keypair, c->sk)); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, c->tweak)); + CHECK(secp256k1_keypair_sec(CTX, sk, &keypair)); + CHECK(secp256k1_keypair_pub(CTX, &pk, &keypair)); + } else { + memcpy(sk, c->sk, sizeof(sk)); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, sk, c->tweak)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, sk)); + } + CHECK(secp256k1_memcmp_var(sk, c->expected_sk, sizeof(sk)) == 0); + CHECK(secp256k1_ec_pubkey_serialize(CTX, pk_ser, &len, &pk, SECP256K1_EC_COMPRESSED)); + CHECK(secp256k1_memcmp_var(pk_ser, c->expected_pk, sizeof(pk_ser)) == 0); + } +} + +/* --- Test registry --- */ +REPEAT_TEST(fullagg_simple_test) + +static const struct tf_test_entry tests_schnorrsig_fullagg[] = { + CASE1(fullagg_simple_test), + CASE1(fullagg_api_tests), + CASE1(fullagg_nonce_gen_counter_test), + CASE1(fullagg_serialization_test), + CASE1(fullagg_test_vectors_sign), + CASE1(fullagg_test_vectors_sign_error), + CASE1(fullagg_test_vectors_verify), + CASE1(fullagg_test_vectors_partial_sig_verify), + CASE1(fullagg_test_vectors_tweak), +}; + +#endif /* SECP256K1_MODULE_SCHNORRSIG_FULLAGG_TESTS_IMPL_H */ diff --git a/src/modules/schnorrsig_fullagg/vectors.h b/src/modules/schnorrsig_fullagg/vectors.h new file mode 100644 index 0000000000..c86632d175 --- /dev/null +++ b/src/modules/schnorrsig_fullagg/vectors.h @@ -0,0 +1,679 @@ +/** + * Automatically generated by ./tools/test_vectors_fullagg_generate.py. */ + +struct fullagg_sign_case { + size_t n_signers; + unsigned char sks[5][32]; + unsigned char msgs[5][32]; + unsigned char secnonces[5][64]; + unsigned char expected_aggnonce[66]; + unsigned char expected_psigs[5][32]; + unsigned char expected_sig[64]; +}; + +static const struct fullagg_sign_case fullagg_sign_cases[7] = { + /* Single signer */ + { + 1, + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 } + }, + { + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 } + }, + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { + { 0x6C, 0x10, 0x7E, 0x8B, 0x71, 0x45, 0x55, 0x05, 0x1F, 0x1D, 0x56, 0x63, 0x7B, 0x92, 0x86, 0x75, 0xAE, 0x21, 0x53, 0x14, 0xBB, 0xE5, 0x11, 0x56, 0x29, 0xA6, 0xE3, 0x09, 0x84, 0x31, 0x3C, 0x95 } + }, + { 0x55, 0x74, 0xD2, 0xBA, 0x5A, 0xB8, 0x91, 0xF9, 0xE5, 0xBA, 0x99, 0xF3, 0x60, 0xFE, 0x45, 0x98, 0xAE, 0x8B, 0x71, 0x8C, 0xC7, 0xE4, 0xC6, 0x07, 0x73, 0xFB, 0x7D, 0xE1, 0xC6, 0x15, 0xC0, 0xC9, 0x6C, 0x10, 0x7E, 0x8B, 0x71, 0x45, 0x55, 0x05, 0x1F, 0x1D, 0x56, 0x63, 0x7B, 0x92, 0x86, 0x75, 0xAE, 0x21, 0x53, 0x14, 0xBB, 0xE5, 0x11, 0x56, 0x29, 0xA6, 0xE3, 0x09, 0x84, 0x31, 0x3C, 0x95 } + }, + /* Two signers */ + { + 2, + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFA, 0xB6, 0xAA, 0xD8, 0xE2, 0xAB, 0x44, 0x9C, 0x37, 0xBB, 0xCE, 0x5A, 0x88, 0xCC, 0x32, 0x3D, 0x3D } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + { 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06 } + }, + { 0x02, 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F, 0x02, 0x56, 0xB3, 0x28, 0xB3, 0x0C, 0x8B, 0xF5, 0x83, 0x9E, 0x24, 0x05, 0x87, 0x47, 0x87, 0x94, 0x08, 0xBD, 0xB3, 0x62, 0x41, 0xDC, 0x9C, 0x2E, 0x7C, 0x61, 0x9F, 0xAA, 0x12, 0xB2, 0x92, 0x09, 0x67 }, + { + { 0x22, 0xB4, 0xD1, 0x8F, 0xB6, 0x1A, 0xAA, 0xDD, 0xC4, 0x53, 0x04, 0x60, 0xBA, 0xF0, 0xC5, 0x0D, 0xD3, 0xF6, 0x18, 0x5A, 0x2A, 0x8C, 0x17, 0x31, 0x84, 0x68, 0x18, 0xE8, 0xB1, 0x91, 0x05, 0x7E }, + { 0x6D, 0xBD, 0x27, 0xBD, 0x37, 0xC2, 0x57, 0x36, 0xEA, 0x15, 0xAC, 0x8D, 0x87, 0x7B, 0x00, 0x4A, 0xC9, 0x55, 0xB1, 0x5D, 0x28, 0x4D, 0x61, 0xE1, 0xFC, 0x81, 0xC7, 0x2D, 0x56, 0xE7, 0xEA, 0x77 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 } + }, + /* Three signers */ + { + 3, + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFA, 0xB6, 0xAA, 0xD8, 0xE2, 0xAB, 0x44, 0x9C, 0x37, 0xBB, 0xCE, 0x5A, 0x88, 0xCC, 0x32, 0x3D, 0x3D }, + { 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 } + }, + { + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + { 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06 }, + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09 } + }, + { 0x02, 0x1A, 0x7A, 0x56, 0x9E, 0x91, 0xDB, 0xF6, 0x05, 0x81, 0x50, 0x9C, 0x7F, 0xC9, 0x46, 0xD1, 0x00, 0x3B, 0x60, 0xC7, 0xDE, 0xE8, 0x52, 0x99, 0x53, 0x8D, 0xB6, 0x35, 0x35, 0x38, 0xD5, 0x95, 0x74, 0x03, 0x63, 0x60, 0xE8, 0x56, 0x31, 0x0C, 0xE5, 0xD2, 0x94, 0xE8, 0xBE, 0x33, 0xFC, 0x80, 0x70, 0x77, 0xDC, 0x56, 0xAC, 0x80, 0xD9, 0x5D, 0x9C, 0xD4, 0xDD, 0xBD, 0x21, 0x32, 0x5E, 0xFF, 0x73, 0xF7 }, + { + { 0x42, 0x1A, 0xD0, 0x11, 0x36, 0x2C, 0x0B, 0x2E, 0x71, 0xD6, 0x56, 0x8E, 0xBB, 0x9B, 0xCD, 0xF3, 0x5E, 0x30, 0x91, 0x90, 0x57, 0xD3, 0x21, 0x5C, 0x80, 0x8D, 0xA4, 0x2D, 0xAB, 0x81, 0xA2, 0xF2 }, + { 0x17, 0xA3, 0x03, 0x15, 0x3A, 0x3D, 0x20, 0xCB, 0xDB, 0x70, 0x12, 0xC9, 0xE3, 0xD1, 0x68, 0x7A, 0xF2, 0x4B, 0x35, 0x0D, 0x94, 0x19, 0xB1, 0x51, 0x43, 0xED, 0xE5, 0xBE, 0xC3, 0x09, 0x54, 0x10 }, + { 0x65, 0x4E, 0x63, 0xBB, 0x8B, 0x84, 0x01, 0x9B, 0x39, 0xB7, 0x2D, 0xAA, 0x5D, 0x9B, 0x4C, 0x15, 0xD1, 0xF1, 0xA8, 0x50, 0x7C, 0xB8, 0xB7, 0x1E, 0x6B, 0xA8, 0x4C, 0x98, 0x7F, 0x99, 0x61, 0x78 } + }, + { 0xAF, 0xFD, 0xF8, 0x03, 0xD4, 0x8F, 0xC2, 0x16, 0x0A, 0x7D, 0x14, 0xF2, 0x3F, 0x3F, 0x95, 0x74, 0xFE, 0x3A, 0xF9, 0x94, 0xE9, 0x32, 0xDC, 0x0D, 0x0B, 0x20, 0xDB, 0xF2, 0x08, 0xBD, 0xD0, 0xEC, 0xBF, 0x0C, 0x36, 0xE1, 0xFB, 0xED, 0x2D, 0x95, 0x86, 0xFD, 0x97, 0x02, 0xFD, 0x08, 0x82, 0x84, 0x22, 0x6D, 0x6E, 0xEE, 0x68, 0xA5, 0x89, 0xCC, 0x30, 0x23, 0xD6, 0x84, 0xEE, 0x24, 0x58, 0x7A } + }, + /* Five signers */ + { + 5, + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFA, 0xB6, 0xAA, 0xD8, 0xE2, 0xAB, 0x44, 0x9C, 0x37, 0xBB, 0xCE, 0x5A, 0x88, 0xCC, 0x32, 0x3D, 0x3D }, + { 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 }, + { 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF4, 0xB0, 0xA4, 0xD2, 0xDC, 0xA5, 0x3E, 0x96, 0x31, 0xB5, 0xC8, 0x54, 0x82, 0xC6, 0x2C, 0x37, 0x37 }, + { 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 }, + { 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83 }, + { 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84 } + }, + { + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + { 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06 }, + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09 }, + { 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C }, + { 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F } + }, + { 0x02, 0xBA, 0x72, 0xA6, 0xE8, 0xBA, 0x53, 0xE8, 0xB9, 0x71, 0xAD, 0x0C, 0x98, 0x23, 0x96, 0x8A, 0xEF, 0x4D, 0x78, 0xCE, 0x8A, 0xF2, 0x55, 0xAB, 0x43, 0xDF, 0xF8, 0x30, 0x03, 0xC9, 0x02, 0xFB, 0x8D, 0x03, 0x94, 0x8B, 0x53, 0xDA, 0x97, 0xFD, 0xF6, 0x74, 0xC0, 0x87, 0x73, 0x15, 0xAC, 0xBC, 0xC8, 0x76, 0x1A, 0xA3, 0xB9, 0xA5, 0x82, 0xB4, 0x39, 0x98, 0x2F, 0xBA, 0xFA, 0xC9, 0x9F, 0x97, 0x21, 0x0F }, + { + { 0x64, 0xDD, 0xCF, 0x21, 0x90, 0x21, 0x68, 0x65, 0x17, 0xB6, 0x42, 0x2A, 0x99, 0x07, 0xFE, 0x4D, 0xEA, 0xF6, 0x9F, 0xE8, 0x52, 0x97, 0xBA, 0x59, 0x8B, 0x52, 0xD6, 0x76, 0xD9, 0xE2, 0xF3, 0x88 }, + { 0x71, 0x6A, 0xCB, 0x88, 0x5C, 0xE4, 0x8B, 0x44, 0x6E, 0x27, 0x47, 0xEC, 0x0D, 0x27, 0x49, 0xDA, 0x8F, 0x7F, 0x08, 0x3B, 0x5A, 0x6C, 0x62, 0x3E, 0xE2, 0x58, 0xD5, 0x31, 0x9A, 0x15, 0x62, 0xB8 }, + { 0xFB, 0xB2, 0xF7, 0x63, 0xA3, 0x3A, 0xC9, 0x73, 0x7D, 0x65, 0x33, 0xBD, 0xDD, 0xF1, 0xE7, 0x49, 0xA3, 0xE2, 0x90, 0x1F, 0x13, 0xEF, 0xFD, 0x00, 0xAA, 0x46, 0xA0, 0x9E, 0xDA, 0x52, 0xC1, 0x5C }, + { 0x7B, 0x98, 0xD3, 0x64, 0x57, 0x54, 0x0E, 0x38, 0x50, 0x05, 0x22, 0x44, 0x13, 0xEA, 0xBC, 0x9D, 0x84, 0x65, 0xA4, 0x83, 0x65, 0x79, 0x16, 0xBE, 0xE3, 0x7C, 0x17, 0xF6, 0xED, 0xB5, 0xE2, 0xA7 }, + { 0xA5, 0x6E, 0xE5, 0x40, 0x4D, 0x3E, 0x8C, 0x45, 0xBC, 0xC5, 0xEB, 0x07, 0x27, 0x4F, 0xD2, 0x0E, 0xDB, 0x62, 0xF5, 0xDE, 0x09, 0x18, 0x52, 0x06, 0xE9, 0x95, 0x8C, 0x76, 0x00, 0x12, 0x11, 0x2D } + }, + { 0x72, 0x5E, 0xD0, 0xEA, 0x19, 0x8C, 0x1B, 0x61, 0x44, 0x77, 0x49, 0x32, 0xFF, 0x1D, 0xBC, 0x77, 0xB1, 0x90, 0x29, 0x90, 0x23, 0xBC, 0x6A, 0x3F, 0x3C, 0x66, 0x21, 0x86, 0xF1, 0x2D, 0xBC, 0x7F, 0xF3, 0x03, 0x4A, 0xB2, 0x34, 0xD3, 0x57, 0x9B, 0x10, 0x0D, 0xCB, 0x1F, 0xBF, 0x5B, 0xBE, 0x21, 0x08, 0xC3, 0x18, 0xD6, 0xD0, 0xF4, 0x41, 0xE7, 0x65, 0x5F, 0x33, 0x9A, 0x9B, 0xA6, 0x88, 0xEE } + }, + /* Same pubkey signs two different messages */ + { + 2, + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 } + }, + { + { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0 }, + { 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1 } + }, + { + { 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C }, + { 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E } + }, + { 0x03, 0x29, 0xCE, 0x75, 0x8B, 0xB2, 0xD2, 0x1D, 0xEF, 0x21, 0x4C, 0xEA, 0x06, 0x9A, 0xDD, 0xBD, 0x16, 0x89, 0x6E, 0xC7, 0x79, 0x24, 0xBF, 0x0E, 0xC9, 0x21, 0x9E, 0x00, 0xDC, 0xF0, 0x77, 0x60, 0x5E, 0x02, 0xDC, 0xEC, 0x80, 0x6D, 0xEB, 0x8D, 0xA0, 0x77, 0x4E, 0xC5, 0xA5, 0xD0, 0xF5, 0x09, 0x7F, 0xFD, 0xE1, 0xF7, 0x0A, 0x9F, 0x40, 0xD4, 0x43, 0x63, 0xBD, 0x3D, 0xC2, 0xDC, 0x25, 0x90, 0xCA, 0x5D }, + { + { 0x85, 0x6B, 0x27, 0x43, 0xB7, 0x83, 0x3E, 0x2E, 0xB0, 0xB6, 0x71, 0x09, 0xCC, 0xD1, 0x15, 0x43, 0x26, 0xB1, 0x7D, 0xAF, 0x86, 0xBF, 0xB4, 0xE7, 0xEB, 0xF2, 0x02, 0x97, 0xEA, 0x1F, 0xA6, 0x42 }, + { 0x9E, 0xF6, 0x0A, 0xB2, 0x7A, 0xA0, 0x5A, 0xE6, 0xC1, 0x2E, 0x15, 0x06, 0xB1, 0x52, 0xA4, 0x1C, 0x7B, 0x84, 0xB1, 0x31, 0x9B, 0x94, 0x64, 0x51, 0xA5, 0x3D, 0x16, 0x02, 0xB3, 0x2A, 0xA0, 0x30 } + }, + { 0x49, 0x37, 0xFA, 0x0F, 0xBE, 0xD6, 0x24, 0x08, 0xA9, 0x01, 0x5A, 0x24, 0x47, 0xB4, 0xC6, 0x10, 0x24, 0x96, 0x4D, 0x55, 0x7D, 0x09, 0x40, 0x98, 0x8F, 0x04, 0x60, 0xE9, 0xBE, 0xEA, 0x9D, 0x82, 0x24, 0x61, 0x31, 0xF6, 0x32, 0x23, 0x99, 0x15, 0x71, 0xE4, 0x86, 0x10, 0x7E, 0x23, 0xB9, 0x60, 0xE7, 0x87, 0x51, 0xFA, 0x73, 0x0B, 0x78, 0xFD, 0xD1, 0x5C, 0xBA, 0x0D, 0xCD, 0x14, 0x05, 0x31 } + }, + /* Signers with tweaked keys */ + { + 2, + { + { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, + { 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + { 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06 } + }, + { 0x02, 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F, 0x02, 0x56, 0xB3, 0x28, 0xB3, 0x0C, 0x8B, 0xF5, 0x83, 0x9E, 0x24, 0x05, 0x87, 0x47, 0x87, 0x94, 0x08, 0xBD, 0xB3, 0x62, 0x41, 0xDC, 0x9C, 0x2E, 0x7C, 0x61, 0x9F, 0xAA, 0x12, 0xB2, 0x92, 0x09, 0x67 }, + { + { 0xBD, 0xCF, 0xA7, 0x76, 0x6B, 0x41, 0x7E, 0x05, 0xEB, 0x21, 0x76, 0x43, 0xF0, 0x0F, 0x60, 0xAA, 0x4C, 0xF0, 0xFA, 0x5A, 0xE4, 0x28, 0x7F, 0xB0, 0xCA, 0xD4, 0x39, 0xD8, 0xFA, 0x35, 0x1D, 0xA6 }, + { 0x28, 0x30, 0x1B, 0x09, 0x00, 0x0D, 0x4C, 0xF0, 0x33, 0xBD, 0x7A, 0xA5, 0x1F, 0x2D, 0xF6, 0xFD, 0x0A, 0x72, 0xBA, 0x13, 0x8E, 0xD2, 0x23, 0x9A, 0x17, 0x50, 0x83, 0x44, 0x4D, 0xAE, 0x5B, 0xC6 } + }, + { 0x70, 0x86, 0xC4, 0xFE, 0xE3, 0x01, 0x4A, 0x26, 0xE2, 0x95, 0x38, 0x91, 0x3B, 0x79, 0x1B, 0x72, 0xD4, 0xDC, 0xE7, 0x42, 0x56, 0x1B, 0x92, 0xD6, 0x21, 0xE6, 0xB2, 0xE4, 0xB3, 0xDE, 0xE2, 0xA4, 0xE5, 0xFF, 0xC2, 0x7F, 0x6B, 0x4E, 0xCA, 0xF6, 0x1E, 0xDE, 0xF0, 0xE9, 0x0F, 0x3D, 0x57, 0xA7, 0x57, 0x63, 0xB4, 0x6E, 0x72, 0xFA, 0xA3, 0x4A, 0xE2, 0x24, 0xBD, 0x1D, 0x47, 0xE3, 0x79, 0x6C } + }, + /* Exact duplicate (pk, m) entries */ + { + 2, + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 } + }, + { + { 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 }, + { 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 } + }, + { + { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15 }, + { 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17 } + }, + { 0x03, 0x5B, 0xE5, 0xE9, 0x47, 0x82, 0x09, 0x67, 0x4A, 0x96, 0xE6, 0x0F, 0x1F, 0x03, 0x7F, 0x61, 0x76, 0x54, 0x0F, 0xD0, 0x01, 0xFA, 0x1D, 0x64, 0x69, 0x47, 0x70, 0xC5, 0x6A, 0x77, 0x09, 0xC4, 0x2C, 0x02, 0x8F, 0x53, 0x04, 0xE2, 0x37, 0x3E, 0x56, 0xEE, 0x7D, 0x77, 0x4C, 0xB8, 0x9E, 0x9F, 0x1A, 0xFE, 0xCF, 0x0E, 0xE7, 0xE3, 0xE3, 0x75, 0x7F, 0x18, 0x99, 0x08, 0xF0, 0x69, 0xDA, 0xA3, 0x6C, 0x60 }, + { + { 0x69, 0xBA, 0xC5, 0x75, 0x7B, 0x4C, 0x51, 0xE7, 0xB3, 0x00, 0x8C, 0xBD, 0x28, 0xF3, 0xFE, 0x38, 0xE0, 0x3D, 0x90, 0xEF, 0xDC, 0x00, 0x2B, 0x4C, 0x41, 0x03, 0x7A, 0x0C, 0xA3, 0x81, 0xB6, 0xEB }, + { 0x0D, 0x32, 0xBD, 0x47, 0xC6, 0xAD, 0x27, 0x19, 0x27, 0x90, 0x3F, 0x64, 0x48, 0xB0, 0x2B, 0xBE, 0x53, 0x3B, 0xBF, 0xB1, 0xC8, 0x27, 0x3E, 0x7F, 0xB0, 0x23, 0x58, 0x86, 0x23, 0x9B, 0x04, 0xA9 } + }, + { 0x45, 0xD4, 0xDB, 0x5A, 0xE5, 0xDE, 0x74, 0xA9, 0xF0, 0x4C, 0xF4, 0x04, 0xD4, 0x34, 0x03, 0x1A, 0x42, 0x9C, 0x69, 0x4B, 0xBA, 0xE2, 0x19, 0x61, 0xF2, 0xB7, 0x9A, 0xE0, 0x35, 0x30, 0x93, 0xBF, 0x76, 0xED, 0x82, 0xBD, 0x41, 0xF9, 0x79, 0x00, 0xDA, 0x90, 0xCC, 0x21, 0x71, 0xA4, 0x29, 0xF7, 0x33, 0x79, 0x50, 0xA1, 0xA4, 0x27, 0x69, 0xCB, 0xF1, 0x26, 0xD2, 0x92, 0xC7, 0x1C, 0xBB, 0x94 } + }, +}; + +struct fullagg_sign_error_case { + unsigned char sk[32]; + unsigned char msg[32]; + unsigned char secnonce[64]; + size_t n_signers; + unsigned char pubkeys[3][32]; + unsigned char msgs[3][32]; + unsigned char pubnonces[3][66]; +}; + +static const struct fullagg_sign_error_case fullagg_sign_error_cases[4] = { + /* Signer's R2 appears at two indices */ + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + 3, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B }, + { 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A }, + { 0x03, 0xF9, 0x91, 0xF9, 0x44, 0xD1, 0xE1, 0x95, 0x4A, 0x7F, 0xC8, 0xB9, 0xBF, 0x62, 0xE0, 0xD7, 0x8F, 0x01, 0x5F, 0x4C, 0x07, 0x76, 0x2D, 0x50, 0x5E, 0x20, 0xE6, 0xC4, 0x52, 0x60, 0xA3, 0x66, 0x1B, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 } + } + }, + /* Signer's R2 appears at no index */ + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + 3, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B }, + { 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x03, 0x11, 0x95, 0xA8, 0x04, 0x6D, 0xCB, 0xB8, 0xE1, 0x70, 0x34, 0xBC, 0xA6, 0x30, 0x06, 0x5E, 0x7A, 0x09, 0x82, 0xE4, 0xE3, 0x6F, 0x6F, 0x7E, 0x5A, 0x8D, 0x45, 0x54, 0xE4, 0x84, 0x6F, 0xCD, 0x99 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A }, + { 0x03, 0xF9, 0x91, 0xF9, 0x44, 0xD1, 0xE1, 0x95, 0x4A, 0x7F, 0xC8, 0xB9, 0xBF, 0x62, 0xE0, 0xD7, 0x8F, 0x01, 0x5F, 0x4C, 0x07, 0x76, 0x2D, 0x50, 0x5E, 0x20, 0xE6, 0xC4, 0x52, 0x60, 0xA3, 0x66, 0x1B, 0x02, 0x56, 0xB3, 0x28, 0xB3, 0x0C, 0x8B, 0xF5, 0x83, 0x9E, 0x24, 0x05, 0x87, 0x47, 0x87, 0x94, 0x08, 0xBD, 0xB3, 0x62, 0x41, 0xDC, 0x9C, 0x2E, 0x7C, 0x61, 0x9F, 0xAA, 0x12, 0xB2, 0x92, 0x09, 0x67 } + } + }, + /* Message at signer's index was substituted */ + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + 3, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B }, + { 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F } + }, + { + { 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A }, + { 0x03, 0xF9, 0x91, 0xF9, 0x44, 0xD1, 0xE1, 0x95, 0x4A, 0x7F, 0xC8, 0xB9, 0xBF, 0x62, 0xE0, 0xD7, 0x8F, 0x01, 0x5F, 0x4C, 0x07, 0x76, 0x2D, 0x50, 0x5E, 0x20, 0xE6, 0xC4, 0x52, 0x60, 0xA3, 0x66, 0x1B, 0x02, 0x56, 0xB3, 0x28, 0xB3, 0x0C, 0x8B, 0xF5, 0x83, 0x9E, 0x24, 0x05, 0x87, 0x47, 0x87, 0x94, 0x08, 0xBD, 0xB3, 0x62, 0x41, 0xDC, 0x9C, 0x2E, 0x7C, 0x61, 0x9F, 0xAA, 0x12, 0xB2, 0x92, 0x09, 0x67 } + } + }, + /* Public key at signer's index was substituted */ + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + 3, + { + { 0x9A, 0xC2, 0x03, 0x35, 0xEB, 0x38, 0x76, 0x8D, 0x20, 0x52, 0xBE, 0x1D, 0xBB, 0xC3, 0xC8, 0xF6, 0x17, 0x84, 0x07, 0x45, 0x8E, 0x51, 0xE6, 0xB4, 0xAD, 0x22, 0xF1, 0xD9, 0x17, 0x58, 0x89, 0x5B }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B }, + { 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A }, + { 0x03, 0xF9, 0x91, 0xF9, 0x44, 0xD1, 0xE1, 0x95, 0x4A, 0x7F, 0xC8, 0xB9, 0xBF, 0x62, 0xE0, 0xD7, 0x8F, 0x01, 0x5F, 0x4C, 0x07, 0x76, 0x2D, 0x50, 0x5E, 0x20, 0xE6, 0xC4, 0x52, 0x60, 0xA3, 0x66, 0x1B, 0x02, 0x56, 0xB3, 0x28, 0xB3, 0x0C, 0x8B, 0xF5, 0x83, 0x9E, 0x24, 0x05, 0x87, 0x47, 0x87, 0x94, 0x08, 0xBD, 0xB3, 0x62, 0x41, 0xDC, 0x9C, 0x2E, 0x7C, 0x61, 0x9F, 0xAA, 0x12, 0xB2, 0x92, 0x09, 0x67 } + } + }, +}; + +/* The verify vectors whose inputs cannot be expressed through the C API are + * omitted (signatures whose length is not 64 bytes, lists of different + * lengths and empty signer lists). */ +struct fullagg_verify_case { + size_t n_signers; + unsigned char pubkeys[3][32]; + unsigned char msgs[3][32]; + unsigned char sig[64]; + int expected; +}; + +static const struct fullagg_verify_case fullagg_verify_cases[11] = { + /* Two-signer valid aggregate */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 }, + 1 + }, + /* Three-signer valid aggregate */ + { + 3, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B }, + { 0x98, 0x9C, 0x0B, 0x76, 0xCB, 0x56, 0x39, 0x71, 0xFD, 0xC9, 0xBE, 0xF3, 0x1E, 0xC0, 0x6C, 0x35, 0x60, 0xF3, 0x24, 0x9D, 0x6E, 0xE9, 0xE5, 0xD8, 0x3C, 0x57, 0x62, 0x55, 0x96, 0xE0, 0x5F, 0x6F } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }, + { 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82 } + }, + { 0xAF, 0xFD, 0xF8, 0x03, 0xD4, 0x8F, 0xC2, 0x16, 0x0A, 0x7D, 0x14, 0xF2, 0x3F, 0x3F, 0x95, 0x74, 0xFE, 0x3A, 0xF9, 0x94, 0xE9, 0x32, 0xDC, 0x0D, 0x0B, 0x20, 0xDB, 0xF2, 0x08, 0xBD, 0xD0, 0xEC, 0xBF, 0x0C, 0x36, 0xE1, 0xFB, 0xED, 0x2D, 0x95, 0x86, 0xFD, 0x97, 0x02, 0xFD, 0x08, 0x82, 0x84, 0x22, 0x6D, 0x6E, 0xEE, 0x68, 0xA5, 0x89, 0xCC, 0x30, 0x23, 0xD6, 0x84, 0xEE, 0x24, 0x58, 0x7A }, + 1 + }, + /* Wrong message for second signer */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 }, + 0 + }, + /* Pubkey order swapped */ + { + 2, + { + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B }, + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 }, + 0 + }, + /* Dropped second signer */ + { + 1, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 }, + 0 + }, + /* Tampered s (bit flip) */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF4 }, + 0 + }, + /* s = 0 (in range, wrong) */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + 0 + }, + /* s = n (out of range) */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41 }, + 0 + }, + /* s = 2^256 - 1 */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + 0 + }, + /* R x-coord not on curve */ + { + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 }, + 0 + }, + /* Public key not on curve */ + { + 2, + { + { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { 0x8F, 0xB6, 0x0A, 0xB3, 0x9F, 0x70, 0x8A, 0xFE, 0xE7, 0x9A, 0xD2, 0x5E, 0x6F, 0x55, 0x28, 0xBF, 0x4F, 0xCC, 0x5E, 0x56, 0x08, 0x3E, 0x82, 0x41, 0x73, 0x57, 0x82, 0x6E, 0x19, 0xE2, 0x50, 0xBC, 0x90, 0x71, 0xF9, 0x4C, 0xED, 0xDD, 0x02, 0x14, 0xAE, 0x68, 0xB0, 0xEE, 0x42, 0x6B, 0xC5, 0x58, 0x9D, 0x4B, 0xC9, 0xB7, 0x52, 0xD9, 0x79, 0x13, 0x80, 0xE9, 0xE0, 0x16, 0x08, 0x78, 0xEF, 0xF5 }, + 0 + }, +}; + +struct fullagg_partial_sig_verify_case { + size_t signer_index; + unsigned char psig[32]; + size_t n_signers; + unsigned char pubkeys[2][32]; + unsigned char msgs[2][32]; + unsigned char pubnonces[2][66]; + int expected; +}; + +static const struct fullagg_partial_sig_verify_case fullagg_partial_sig_verify_cases[9] = { + /* Honest partial sig (signer 0) */ + { + 0, + { 0x22, 0xB4, 0xD1, 0x8F, 0xB6, 0x1A, 0xAA, 0xDD, 0xC4, 0x53, 0x04, 0x60, 0xBA, 0xF0, 0xC5, 0x0D, 0xD3, 0xF6, 0x18, 0x5A, 0x2A, 0x8C, 0x17, 0x31, 0x84, 0x68, 0x18, 0xE8, 0xB1, 0x91, 0x05, 0x7E }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 1 + }, + /* Honest partial sig (signer 1) */ + { + 1, + { 0x6D, 0xBD, 0x27, 0xBD, 0x37, 0xC2, 0x57, 0x36, 0xEA, 0x15, 0xAC, 0x8D, 0x87, 0x7B, 0x00, 0x4A, 0xC9, 0x55, 0xB1, 0x5D, 0x28, 0x4D, 0x61, 0xE1, 0xFC, 0x81, 0xC7, 0x2D, 0x56, 0xE7, 0xEA, 0x77 }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 1 + }, + /* Honest psig verified at the wrong signer_index */ + { + 1, + { 0x22, 0xB4, 0xD1, 0x8F, 0xB6, 0x1A, 0xAA, 0xDD, 0xC4, 0x53, 0x04, 0x60, 0xBA, 0xF0, 0xC5, 0x0D, 0xD3, 0xF6, 0x18, 0x5A, 0x2A, 0x8C, 0x17, 0x31, 0x84, 0x68, 0x18, 0xE8, 0xB1, 0x91, 0x05, 0x7E }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, + /* Tampered partial sig (bit flip) */ + { + 0, + { 0x22, 0xB4, 0xD1, 0x8F, 0xB6, 0x1A, 0xAA, 0xDD, 0xC4, 0x53, 0x04, 0x60, 0xBA, 0xF0, 0xC5, 0x0D, 0xD3, 0xF6, 0x18, 0x5A, 0x2A, 0x8C, 0x17, 0x31, 0x84, 0x68, 0x18, 0xE8, 0xB1, 0x91, 0x05, 0x7F }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, + /* psig = 0 (in range, wrong) */ + { + 0, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, + /* psig = n (out of range) */ + { + 0, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41 }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, + /* signer_index out of range */ + { + 2, + { 0x22, 0xB4, 0xD1, 0x8F, 0xB6, 0x1A, 0xAA, 0xDD, 0xC4, 0x53, 0x04, 0x60, 0xBA, 0xF0, 0xC5, 0x0D, 0xD3, 0xF6, 0x18, 0x5A, 0x2A, 0x8C, 0x17, 0x31, 0x84, 0x68, 0x18, 0xE8, 0xB1, 0x91, 0x05, 0x7E }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, + /* psig for a different aggregate nonce */ + { + 0, + { 0x80, 0xBA, 0xC2, 0xC1, 0xFA, 0xA8, 0xF2, 0x95, 0xF5, 0x13, 0x59, 0x8F, 0x0E, 0x7B, 0x01, 0xEA, 0xFD, 0x05, 0xA9, 0x6B, 0x20, 0xAC, 0x7D, 0x9B, 0x8A, 0x2F, 0x24, 0x85, 0xBD, 0x22, 0x0B, 0xEA }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x62, 0xC0, 0xA0, 0x46, 0xDA, 0xCC, 0xE8, 0x6D, 0xDD, 0x03, 0x43, 0xC6, 0xD3, 0xC7, 0xC7, 0x9C, 0x22, 0x08, 0xBA, 0x0D, 0x9C, 0x9C, 0xF2, 0x4A, 0x6D, 0x04, 0x6D, 0x21, 0xD2, 0x1F, 0x90, 0xF7, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, + /* Aggregate nonce R1 is infinity */ + { + 0, + { 0x22, 0xB4, 0xD1, 0x8F, 0xB6, 0x1A, 0xAA, 0xDD, 0xC4, 0x53, 0x04, 0x60, 0xBA, 0xF0, 0xC5, 0x0D, 0xD3, 0xF6, 0x18, 0x5A, 0x2A, 0x8C, 0x17, 0x31, 0x84, 0x68, 0x18, 0xE8, 0xB1, 0x91, 0x05, 0x7E }, + 2, + { + { 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F }, + { 0x46, 0x27, 0x79, 0xAD, 0x4A, 0xAD, 0x39, 0x51, 0x46, 0x14, 0x75, 0x1A, 0x71, 0x08, 0x5F, 0x2F, 0x10, 0xE1, 0xC7, 0xA5, 0x93, 0xE4, 0xE0, 0x30, 0xEF, 0xB5, 0xB8, 0x72, 0x1C, 0xE5, 0x5B, 0x0B } + }, + { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 } + }, + { + { 0x02, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 }, + { 0x03, 0x4D, 0x4B, 0x6C, 0xD1, 0x36, 0x10, 0x32, 0xCA, 0x9B, 0xD2, 0xAE, 0xB9, 0xD9, 0x00, 0xAA, 0x4D, 0x45, 0xD9, 0xEA, 0xD8, 0x0A, 0xC9, 0x42, 0x33, 0x74, 0xC4, 0x51, 0xA7, 0x25, 0x4D, 0x07, 0x66, 0x03, 0xF0, 0x06, 0xA1, 0x8D, 0x56, 0x53, 0xC4, 0xED, 0xF5, 0x39, 0x1F, 0xF2, 0x3A, 0x61, 0xF0, 0x3F, 0xF8, 0x3D, 0x23, 0x7E, 0x88, 0x0E, 0xE6, 0x11, 0x87, 0xFA, 0x9F, 0x37, 0x9A, 0x02, 0x8E, 0x0A } + }, + 0 + }, +}; + +struct fullagg_tweak_case { + unsigned char sk[32]; + unsigned char tweak[32]; + int is_xonly; + unsigned char expected_sk[32]; + unsigned char expected_pk[33]; +}; + +static const struct fullagg_tweak_case fullagg_tweak_cases[4] = { + /* Plain tweak of an even-y key */ + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, + 0, + { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, + { 0x03, 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F } + }, + /* Plain tweak of an odd-y key */ + { + { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, + 0, + { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }, + { 0x02, 0x53, 0x1F, 0xE6, 0x06, 0x81, 0x34, 0x50, 0x3D, 0x27, 0x23, 0x13, 0x32, 0x27, 0xC8, 0x67, 0xAC, 0x8F, 0xA6, 0xC8, 0x3C, 0x53, 0x7E, 0x9A, 0x44, 0xC3, 0xC5, 0xBD, 0xBD, 0xCB, 0x1F, 0xE3, 0x37 } + }, + /* X-only tweak of an even-y key */ + { + { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xB9, 0xAD, 0xDB, 0xE5, 0xAE, 0x47, 0x9F, 0x3A, 0xBE, 0xD1, 0x5D, 0x8B, 0xCF, 0x35, 0x40, 0x40 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, + 1, + { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, + { 0x03, 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F } + }, + /* X-only tweak of an odd-y key */ + { + { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, + 1, + { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, + { 0x03, 0x1B, 0x84, 0xC5, 0x56, 0x7B, 0x12, 0x64, 0x40, 0x99, 0x5D, 0x3E, 0xD5, 0xAA, 0xBA, 0x05, 0x65, 0xD7, 0x1E, 0x18, 0x34, 0x60, 0x48, 0x19, 0xFF, 0x9C, 0x17, 0xF5, 0xE9, 0xD5, 0xDD, 0x07, 0x8F } + }, +}; + +enum { FULLAGG_VECTORS_MAX_SIGNERS = 5 }; diff --git a/src/secp256k1.c b/src/secp256k1.c index b216872e12..b6c5ffadb2 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -842,6 +842,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 e821738d5a..3d6730146e 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7719,6 +7719,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 @@ -8062,6 +8066,9 @@ static const struct tf_test_module registry_modules[] = { #ifdef ENABLE_MODULE_SCHNORRSIG MAKE_TEST_MODULE(schnorrsig), #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG + MAKE_TEST_MODULE(schnorrsig_fullagg), +#endif #ifdef ENABLE_MODULE_MUSIG MAKE_TEST_MODULE(musig), #endif diff --git a/tools/test_vectors_fullagg_generate.py b/tools/test_vectors_fullagg_generate.py new file mode 100755 index 0000000000..4b14703211 --- /dev/null +++ b/tools/test_vectors_fullagg_generate.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +import csv +import os +import sys + +if len(sys.argv) < 2: + print( + "This script converts BIP FullAgg test vectors in a given directory to a C file that can be used in the test framework." + ) + print("Usage: %s " % sys.argv[0]) + sys.exit(1) + + +def read_csv(filename): + with open(os.path.join(sys.argv[1], filename), newline="") as f: + rows = list(csv.reader(f)) + return rows[1:] + + +def hexstr_to_intarray(s): + return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(s)]) + + +def hexlist(s): + return s.split(";") if s else [] + + +def init_int(value, indent): + return indent * " " + "%d" % value + + +def init_array(s, indent): + return indent * " " + "{ %s }" % hexstr_to_intarray(s) + + +def init_arrays(strs, indent): + out = indent * " " + "{\n" + out += ",\n".join([init_array(x, indent + 4) for x in strs]) + out += "\n" + indent * " " + "}" + return out + + +def init_case(comment, fields): + out = " /* %s */\n" % comment + out += " {\n" + out += ",\n".join(fields) + out += "\n },\n" + return out + + +max_signers = 0 + +s = ( + """/** + * Automatically generated by %s. */ +""" + % sys.argv[0] +) + +# sign vectors +rows = read_csv("test_vectors_sign.csv") +n = max(len(hexlist(row[1])) for row in rows) +max_signers = max(max_signers, n) +s += """ +struct fullagg_sign_case { + size_t n_signers; + unsigned char sks[%d][32]; + unsigned char msgs[%d][32]; + unsigned char secnonces[%d][64]; + unsigned char expected_aggnonce[66]; + unsigned char expected_psigs[%d][32]; + unsigned char expected_sig[64]; +}; + +static const struct fullagg_sign_case fullagg_sign_cases[%d] = { +""" % (n, n, n, n, len(rows)) +for row in rows: + _, sks, msgs, secnonces, aggnonce, psigs, sig, comment = row + s += init_case(comment, [ + init_int(len(hexlist(sks)), 8), + init_arrays(hexlist(sks), 8), + init_arrays(hexlist(msgs), 8), + init_arrays(hexlist(secnonces), 8), + init_array(aggnonce, 8), + init_arrays(hexlist(psigs), 8), + init_array(sig, 8), + ]) +s += "};\n" + +# sign error vectors +rows = read_csv("test_vectors_sign_error.csv") +n = max(len(hexlist(row[4])) for row in rows) +max_signers = max(max_signers, n) +s += """ +struct fullagg_sign_error_case { + unsigned char sk[32]; + unsigned char msg[32]; + unsigned char secnonce[64]; + size_t n_signers; + unsigned char pubkeys[%d][32]; + unsigned char msgs[%d][32]; + unsigned char pubnonces[%d][66]; +}; + +static const struct fullagg_sign_error_case fullagg_sign_error_cases[%d] = { +""" % (n, n, n, len(rows)) +for row in rows: + _, sk, msg, secnonce, pubkeys, msgs, pubnonces, comment = row + s += init_case(comment, [ + init_array(sk, 8), + init_array(msg, 8), + init_array(secnonce, 8), + init_int(len(hexlist(pubkeys)), 8), + init_arrays(hexlist(pubkeys), 8), + init_arrays(hexlist(msgs), 8), + init_arrays(hexlist(pubnonces), 8), + ]) +s += "};\n" + +# verify vectors. The cases with signatures whose length is not 64 bytes, +# with lists of different lengths and with an empty signer list are omitted +# because they cannot be expressed through the C API. +rows = [ + row for row in read_csv("test_vectors_verify.csv") + if len(row[3]) == 128 + and len(hexlist(row[1])) == len(hexlist(row[2])) + and len(hexlist(row[1])) > 0 +] +n = max(len(hexlist(row[1])) for row in rows) +max_signers = max(max_signers, n) +s += """ +/* The verify vectors whose inputs cannot be expressed through the C API are + * omitted (signatures whose length is not 64 bytes, lists of different + * lengths and empty signer lists). */ +struct fullagg_verify_case { + size_t n_signers; + unsigned char pubkeys[%d][32]; + unsigned char msgs[%d][32]; + unsigned char sig[64]; + int expected; +}; + +static const struct fullagg_verify_case fullagg_verify_cases[%d] = { +""" % (n, n, len(rows)) +for row in rows: + _, pubkeys, msgs, sig, expected, comment = row + s += init_case(comment, [ + init_int(len(hexlist(pubkeys)), 8), + init_arrays(hexlist(pubkeys), 8), + init_arrays(hexlist(msgs), 8), + init_array(sig, 8), + init_int(int(expected == "TRUE"), 8), + ]) +s += "};\n" + +# partial signature verify vectors +rows = read_csv("test_vectors_partial_sig_verify.csv") +n = max(len(hexlist(row[3])) for row in rows) +max_signers = max(max_signers, n) +s += """ +struct fullagg_partial_sig_verify_case { + size_t signer_index; + unsigned char psig[32]; + size_t n_signers; + unsigned char pubkeys[%d][32]; + unsigned char msgs[%d][32]; + unsigned char pubnonces[%d][66]; + int expected; +}; + +static const struct fullagg_partial_sig_verify_case fullagg_partial_sig_verify_cases[%d] = { +""" % (n, n, n, len(rows)) +for row in rows: + _, signer_index, psig, pubkeys, msgs, pubnonces, expected, comment = row + s += init_case(comment, [ + init_int(int(signer_index), 8), + init_array(psig, 8), + init_int(len(hexlist(pubkeys)), 8), + init_arrays(hexlist(pubkeys), 8), + init_arrays(hexlist(msgs), 8), + init_arrays(hexlist(pubnonces), 8), + init_int(int(expected == "TRUE"), 8), + ]) +s += "};\n" + +# tweak vectors +rows = read_csv("test_vectors_tweak.csv") +s += """ +struct fullagg_tweak_case { + unsigned char sk[32]; + unsigned char tweak[32]; + int is_xonly; + unsigned char expected_sk[32]; + unsigned char expected_pk[33]; +}; + +static const struct fullagg_tweak_case fullagg_tweak_cases[%d] = { +""" % len(rows) +for row in rows: + _, sk, tweak, is_xonly, expected_sk, expected_pk, comment = row + s += init_case(comment, [ + init_array(sk, 8), + init_array(tweak, 8), + init_int(int(is_xonly == "TRUE"), 8), + init_array(expected_sk, 8), + init_array(expected_pk, 8), + ]) +s += "};\n" + +s += "\nenum { FULLAGG_VECTORS_MAX_SIGNERS = %d };" % max_signers +print(s) From 5a45c8e6873836c214291d26d15995104fdc643a Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 12 Sep 2025 17:55:33 +0200 Subject: [PATCH 03/10] fullagg: Add include file --- include/secp256k1_schnorrsig_fullagg.h | 448 +++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 include/secp256k1_schnorrsig_fullagg.h diff --git a/include/secp256k1_schnorrsig_fullagg.h b/include/secp256k1_schnorrsig_fullagg.h new file mode 100644 index 0000000000..d9849db74c --- /dev/null +++ b/include/secp256k1_schnorrsig_fullagg.h @@ -0,0 +1,448 @@ +#ifndef SECP256K1_SCHNORRSIG_FULLAGG_H +#define SECP256K1_SCHNORRSIG_FULLAGG_H + +#include "secp256k1_extrakeys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** This module implements BIP 459 "Full-Aggregation of BIP 340 Signatures" + * (https://github.com/bitcoin/bips/blob/master/bip-0459.mediawiki), + * allowing multiple signers to each sign different messages and aggregate + * their signatures into a single signature. You can find an example + * demonstrating the fullagg module in examples/fullagg.c. + */ + +/** 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, the aggregate nonce, and the + * number of signers. + * + * Guaranteed to be 143 bytes in size. + */ +typedef struct secp256k1_fullagg_session { + unsigned char data[143]; +} 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, it 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: x-only 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. + * 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_xonly_pubkey *pubkey, + 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_counter. + * keypair: keypair of the signer creating the nonce. The secnonce + * output of this function cannot be used to sign for any + * other keypair. + * 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 *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 or if an aggregate nonce is the + * point at infinity, 1 otherwise. In the (cryptographically + * unreachable for honest signers) failure case of an aggregate nonce + * at infinity, the signers should restart the session with fresh + * nonces. + * 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 or if the final nonce is the point + * at infinity, 1 otherwise. In the (cryptographically unreachable + * for honest signers) failure case of a final nonce at infinity, the + * signers should restart the session with fresh nonces. + * 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 x-only 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]. + * 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_xonly_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. + * + * This function checks that the signer's public key and intended message + * appear at signer_index in the arrays and that the signer's R2_i nonce + * appears exactly once in the pubnonces array, at signer_index. It also + * verifies that the session was initialized with the same lists of public + * keys, messages, and public nonces by recomputing the nonce coefficient. + * + * 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 (not secp256k1_context_static) + * 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 + * msg32: the 32-byte message that the signer intends to sign. It + * must match the message at signer_index in the messages + * array. + * session: pointer to the session that was created with + * fullagg_session_init + * pubkeys: array of pointers to x-only 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) + * n_signers: number of signers (length of the pubkeys, messages, and + * pubnonces arrays). Must be greater than 0 and equal to + * the number of signers in the session. + * 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 unsigned char *msg32, + const secp256k1_fullagg_session *session, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + const secp256k1_fullagg_pubnonce * const *pubnonces, + size_t n_signers, + 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) SECP256K1_ARG_NONNULL(9); + +/** Verifies an individual signer's partial signature + * + * The aggregate nonce and the session values are recomputed from the + * pubnonces, so the result cannot be influenced by the coordinator. If the + * public nonces are received over authenticated channels and this function + * succeeds for the partial signatures of all signers, then partial_sig_agg + * produces a valid aggregate signature. + * + * 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. + * + * 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 + * pubnonces: array of pointers to public nonces (same as in + * session_init) + * pubkeys: array of pointers to x-only public keys (same as in + * session_init) + * messages: array of pointers to messages (same as in session_init) + * n_signers: number of signers (length of pubnonces, pubkeys, and + * messages arrays). Must be greater than 0. + * 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 * const *pubnonces, + const secp256k1_xonly_pubkey * const *pubkeys, + const unsigned char * const *messages, + size_t n_signers, + size_t signer_index +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** 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 x-only 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_xonly_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 From c205fd4ff97a9eb7752c6ff7e26fbba4dff13e49 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 12 Sep 2025 17:55:53 +0200 Subject: [PATCH 04/10] fullagg: Add example --- .gitignore | 1 + examples/CMakeLists.txt | 4 + examples/fullagg.c | 208 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 examples/fullagg.c diff --git a/.gitignore b/.gitignore index fbc311d7b5..d65a50b61d 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/examples/CMakeLists.txt b/examples/CMakeLists.txt index 808917c4d6..a99d886ea1 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -31,3 +31,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..9286c54ace --- /dev/null +++ b/examples/fullagg.c @@ -0,0 +1,208 @@ +/************************************************************************* + * 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 for three signers who each sign their own message. + * Additionally, see the documentation in + * include/secp256k1_schnorrsig_fullagg.h and doc/fullagg.md. + */ + +#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_xonly_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_xonly_pub(ctx, &signer->pubkey, NULL, &signer_secrets->keypair)) { + return 0; + } + + secure_erase(seckey, sizeof(seckey)); + return 1; +} + +/* Every signer signs their own message */ +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); +} + +/* Sign the messages of all signers and store the aggregate signature in sig64 */ +static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signers, const secp256k1_xonly_pubkey **pubkeys, const unsigned char **messages, unsigned char *sig64) { + int i; + const secp256k1_fullagg_pubnonce *pubnonces[N_SIGNERS]; + const secp256k1_fullagg_partial_sig *partial_sigs[N_SIGNERS]; + /* The same for all signers */ + secp256k1_fullagg_session session; + secp256k1_fullagg_aggnonce agg_pubnonce; + + 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. */ + if (!secp256k1_fullagg_nonce_gen(ctx, &signer_secrets[i].secnonce, &signers[i].pubnonce, session_secrand, seckey, &signers[i].pubkey, NULL)) { + return 0; + } + pubnonces[i] = &signers[i].pubnonce; + + secure_erase(seckey, sizeof(seckey)); + } + + /* Communication round 1: Every signer sends their pubnonce to the + * coordinator. The coordinator runs secp256k1_fullagg_nonce_agg and sends + * agg_pubnonce back to each signer, along with the ordered lists of all + * public keys, messages and pubnonces. */ + if (!secp256k1_fullagg_nonce_agg(ctx, &agg_pubnonce, pubnonces, N_SIGNERS)) { + return 0; + } + + /* Every signer creates a partial signature for their own message */ + for (i = 0; i < N_SIGNERS; i++) { + /* Initialize the signing session by processing the aggregate nonce. + * Every signer computes the session itself and must not use session + * data from an untrusted third party. */ + if (!secp256k1_fullagg_session_init(ctx, &session, &agg_pubnonce, pubkeys, messages, pubnonces, N_SIGNERS)) { + return 0; + } + /* partial_sign will clear the secnonce by setting it to 0. That's because + * you must _never_ reuse the secnonce (or use the same session_secrand to + * create a 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, signers[i].message, &session, pubkeys, messages, pubnonces, N_SIGNERS, i)) { + return 0; + } + partial_sigs[i] = &signers[i].partial_sig; + } + /* Communication round 2: Every signer sends their partial signature to the + * coordinator, who verifies the partial signatures and aggregates them. */ + for (i = 0; i < N_SIGNERS; i++) { + /* To check whether signing was successful, it suffices to either verify + * the aggregate signature with secp256k1_fullagg_verify, or verify all + * partial signatures of all signers individually. Verifying the + * aggregate signature is cheaper but verifying the individual partial + * signatures has the advantage that it can be used to determine which + * of the partial signatures are invalid (if any), i.e., which of the + * partial signatures cause the aggregate signature to be invalid and + * thus the protocol run to fail. It's also fine to first verify the + * aggregate sig, and only verify the individual sigs if it does not + * work. + */ + if (!secp256k1_fullagg_partial_sig_verify(ctx, &signers[i].partial_sig, pubnonces, pubkeys, messages, N_SIGNERS, i)) { + return 0; + } + } + return secp256k1_fullagg_partial_sig_agg(ctx, sig64, &session, partial_sigs, N_SIGNERS); +} + +int main(void) { + secp256k1_context* ctx; + int i; + struct signer_secrets signer_secrets[N_SIGNERS]; + struct signer signers[N_SIGNERS]; + const secp256k1_xonly_pubkey *pubkeys_ptr[N_SIGNERS]; + const unsigned char *messages_ptr[N_SIGNERS]; + unsigned char sig[64]; + + /* Create a secp256k1 context */ + ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + printf("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_ptr[i] = &signers[i].pubkey; + } + printf("ok\n"); + + /* The aggregate signature and the challenges depend on the order of the + * lists of public keys, messages and pubnonces. All signers, the + * coordinator and the verifier must use the same order. */ + setup_messages(signers); + for (i = 0; i < N_SIGNERS; i++) { + messages_ptr[i] = signers[i].message; + } + + printf("Signing messages........"); + fflush(stdout); + if (!sign(ctx, signer_secrets, signers, pubkeys_ptr, messages_ptr, sig)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\n"); + printf("Verifying signature....."); + fflush(stdout); + if (!secp256k1_fullagg_verify(ctx, sig, pubkeys_ptr, messages_ptr, N_SIGNERS)) { + printf("FAILED\n"); + return EXIT_FAILURE; + } + printf("ok\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; +} From 20b353d4cb5c2f29916a42c40d89b5f985f93709 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 12 Sep 2025 17:59:55 +0200 Subject: [PATCH 05/10] fullagg: Add to CI --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++------------ ci/ci.sh | 3 ++- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b836cd6d6..bb1ea348d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ env: EXTRAKEYS: 'no' SCHNORRSIG: 'no' MUSIG: 'no' + SCHNORRSIG_FULLAGG: 'no' ELLSWIFT: 'no' ### test options SECP256K1_TEST_ITERS: 64 @@ -96,14 +97,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' } @@ -158,6 +159,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CC: ${{ matrix.cc }} @@ -186,6 +189,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -216,6 +221,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -237,6 +244,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -277,6 +286,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -322,6 +333,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -351,6 +364,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -397,6 +412,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CC: ${{ matrix.cc }} @@ -422,6 +439,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' CTIMETESTS: 'no' @@ -456,14 +475,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' @@ -513,13 +532,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', 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', 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: @@ -629,6 +648,8 @@ jobs: RECOVERY: 'yes' EXTRAKEYS: 'yes' SCHNORRSIG: 'yes' + EXPERIMENTAL: 'yes' + SCHNORRSIG_FULLAGG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' diff --git a/ci/ci.sh b/ci/ci.sh index 515c14cd04..5cd668739a 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 \ @@ -64,6 +64,7 @@ fi --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" \ From 07beb1f03161bd626dc14f3738f6f07c15005438 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 12 Sep 2025 18:11:20 +0200 Subject: [PATCH 06/10] fullagg: Add to build system --- CMakeLists.txt | 5 ++++ Makefile.am | 15 +++++++++++ configure.ac | 64 +++++++++++++++++++++++++++++----------------- src/CMakeLists.txt | 9 +++++++ 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a84305c340..494701868b 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) @@ -117,6 +118,9 @@ if(NOT SECP256K1_EXPERIMENTAL) if(SECP256K1_ASM STREQUAL "arm32") message(FATAL_ERROR "ARM32 assembly is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") endif() + if(SECP256K1_ENABLE_MODULE_SCHNORRSIG_FULLAGG) + message(FATAL_ERROR "Schnorrsig full-aggregation is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") + endif() endif() set(SECP256K1_VALGRIND "AUTO" CACHE STRING "Build with extra checks for running inside Valgrind. [default=AUTO]") @@ -288,6 +292,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 07d7a2ba7f..efdbd10a60 100644 --- a/Makefile.am +++ b/Makefile.am @@ -210,6 +210,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 @@ -311,6 +322,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/configure.ac b/configure.ac index a21447ced5..5aaea2f21d 100644 --- a/configure.ac +++ b/configure.ac @@ -189,6 +189,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])]) @@ -399,6 +403,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_extrakeys" = x"no"; then + AC_MSG_ERROR([Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig fullagg module.]) + fi + enable_module_extrakeys=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 @@ -443,6 +455,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 # Check for concurrency support (tests only) @@ -470,6 +486,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"]) @@ -483,34 +500,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/src/CMakeLists.txt b/src/CMakeLists.txt index a45eeb93e4..0c882cca34 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,15 @@ if(SECP256K1_ENABLE_MODULE_MUSIG) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_musig.h) endif() +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG_FULLAGG) + 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 full-aggregation module.") + endif() + set(SECP256K1_ENABLE_MODULE_EXTRAKEYS 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.") From 831408265f7c3b1d1e14d3c27ec3b08b287c77d5 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 12 Sep 2025 18:11:34 +0200 Subject: [PATCH 07/10] fullagg: Add docs --- Makefile.am | 2 +- README.md | 2 ++ doc/fullagg.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 doc/fullagg.md diff --git a/Makefile.am b/Makefile.am index efdbd10a60..59e07f9978 100644 --- a/Makefile.am +++ b/Makefile.am @@ -288,7 +288,7 @@ maintainer-clean-local: clean-testvectors ### Additional files to distribute EXTRA_DIST = autogen.sh CHANGELOG.md SECURITY.md EXTRA_DIST += doc/release-process.md doc/safegcd_implementation.md -EXTRA_DIST += doc/ellswift.md doc/musig.md +EXTRA_DIST += doc/ellswift.md doc/musig.md doc/fullagg.md EXTRA_DIST += examples/EXAMPLES_COPYING EXTRA_DIST += sage/gen_exhaustive_groups.sage EXTRA_DIST += sage/gen_split_lambda_constants.sage diff --git a/README.md b/README.md index daa38d44ab..58242b104c 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 [BIP-459](https://github.com/bitcoin/bips/blob/master/bip-0459.mediawiki). Implementation details ---------------------- @@ -151,6 +152,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/doc/fullagg.md b/doc/fullagg.md new file mode 100644 index 0000000000..56c3376276 --- /dev/null +++ b/doc/fullagg.md @@ -0,0 +1,54 @@ +Notes on the fullagg module API +=============================== + +The following sections contain additional notes on the API of the fullagg module (`include/secp256k1_schnorrsig_fullagg.h`). +A usage example can be found in `examples/fullagg.c`. + +## API misuse + +The fullagg API is designed with a focus on misuse resistance. +However, due to the interactive nature of the signing protocol, there are additional failure modes that are not present in regular (single-party) Schnorr signature creation. +While the results can be catastrophic (e.g. leaking of the secret key), it is unfortunately not possible for the fullagg implementation to prevent all such failure modes. + +Therefore, users of the fullagg module must take great care to make sure of the following: + +1. A unique nonce per signing session is generated in `secp256k1_fullagg_nonce_gen`. + See the corresponding comment in `include/secp256k1_schnorrsig_fullagg.h` for how to ensure that. +2. The `secp256k1_fullagg_secnonce` structure is never copied or serialized. + See also the comment on `secp256k1_fullagg_secnonce` in `include/secp256k1_schnorrsig_fullagg.h`. +3. Opaque data structures are never written to or read from directly. + Instead, only the provided accessor functions are used. +4. The session is initialized with `secp256k1_fullagg_session_init` by the signer itself, using the lists of public keys, messages, and public nonces it received. + A session obtained from an untrusted third party such as the coordinator must not be used for signing. + +## Signing + +Each signer signs their own message under their own public key; there is no aggregate public key. +Essentially, the protocol proceeds in the following steps: + +1. Generate a keypair with `secp256k1_keypair_create` and obtain the x-only public key with `secp256k1_keypair_xonly_pub`. +2. Generate a pair of secret and public nonce with `secp256k1_fullagg_nonce_gen` and send the public nonce to the coordinator. +3. The coordinator aggregates the public nonces of all signers with `secp256k1_fullagg_nonce_agg` and sends the aggregate nonce back to the signers, along with the ordered lists of all public keys, messages, and public nonces. +4. Initialize the session with `secp256k1_fullagg_session_init`. +5. Create a partial signature with `secp256k1_fullagg_partial_sign`. +6. Verify the partial signatures (optional in some scenarios) with `secp256k1_fullagg_partial_sig_verify`. +7. The coordinator obtains all partial signatures and aggregates them into the final signature using `secp256k1_fullagg_partial_sig_agg`. + +The aggregate signature can be verified with `secp256k1_fullagg_verify`. +The signature depends on the order of the lists of public keys, messages, and public nonces, so all signers, the coordinator, and the verifier must use the same order. + +Steps 1 and 2 above can occur as a preprocessing step before the messages or the set of signers are known. +However, applications that use this method presumably store the nonces for a longer time and must be even more careful not to reuse them. + +## Tweaking + +The scheme supports additive tweaking of individual key pairs without changes to the verification algorithm. +Since the keys are individual keys rather than an aggregate, the existing key APIs are used and no fullagg-specific functions are needed. +A plain tweak as used for BIP 32 unhardened derivation can be applied to the secret key with `secp256k1_ec_seckey_tweak_add`. +A Taproot tweak as defined in BIP 341 can be applied to the keypair with `secp256k1_keypair_xonly_tweak_add`. +The tweaked x-only public key appears in the public key list and the signer uses the tweaked keypair in `secp256k1_fullagg_partial_sign`. + +## Verification + +A participant who wants to verify the partial signatures, but does not sign itself, may do so using the above instructions except that the participant skips steps 1, 2, 4, 5 and 7. +A valid partial signature does not prove that its signer knows the corresponding secret key, so partial signature verification only serves to identify disruptive signers, and only if the public nonces were received over authenticated channels. From 48ce168c5de6b920ee9f13a3fcf01e35d99bfc0a Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 10 Jul 2026 10:57:30 +0200 Subject: [PATCH 08/10] fullagg: Add benchmarks --- src/bench.c | 30 ++++- .../schnorrsig_fullagg/Makefile.am.include | 1 + src/modules/schnorrsig_fullagg/bench_impl.h | 106 ++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/modules/schnorrsig_fullagg/bench_impl.h diff --git a/src/bench.c b/src/bench.c index f561ad1c9d..a3b9ac869c 100644 --- a/src/bench.c +++ b/src/bench.c @@ -32,6 +32,10 @@ static void help(const char *executable_path, int default_iters) { printf(" - ElligatorSwift (optional module)\n"); #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG + printf(" - Schnorr signature full aggregation (optional module)\n"); +#endif + printf("\n"); printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters); printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n"); @@ -68,6 +72,12 @@ static void help(const char *executable_path, int default_iters) { printf(" ellswift_ecdh : ECDH on ElligatorSwift keys\n"); #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG + printf(" fullagg : all full aggregation algorithms (sign, verify)\n"); + printf(" fullagg_sign : full aggregation partial signing algorithm\n"); + printf(" fullagg_verify : full aggregation verification algorithm\n"); +#endif + printf("\n"); } @@ -170,6 +180,10 @@ static void bench_keygen_run(void *arg, int iters) { # include "modules/ellswift/bench_impl.h" #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG +# include "modules/schnorrsig_fullagg/bench_impl.h" +#endif + int main(int argc, char** argv) { int i; secp256k1_pubkey pubkey; @@ -182,7 +196,8 @@ int main(int argc, char** argv) { char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", - "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; + "ellswift_decode", "ellswift_keygen", "ellswift_ecdh", "fullagg", + "fullagg_sign", "fullagg_verify"}; int invalid_args = have_invalid_args(argc, argv, valid_args, ARRAY_SIZE(valid_args)); int default_iters = 20000; @@ -240,6 +255,14 @@ int main(int argc, char** argv) { } #endif +#ifndef ENABLE_MODULE_SCHNORRSIG_FULLAGG + if (have_flag(argc, argv, "fullagg") || have_flag(argc, argv, "fullagg_sign") || have_flag(argc, argv, "fullagg_verify")) { + fprintf(stderr, "./bench: Schnorr signature full aggregation module not enabled.\n"); + fprintf(stderr, "See README.md for configuration instructions.\n\n"); + return EXIT_FAILURE; + } +#endif + /* ECDSA benchmark */ data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -284,5 +307,10 @@ int main(int argc, char** argv) { run_ellswift_bench(iters, argc, argv); #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG + /* Schnorr signature full aggregation benchmarks */ + run_fullagg_bench(iters, argc, argv); +#endif + return EXIT_SUCCESS; } diff --git a/src/modules/schnorrsig_fullagg/Makefile.am.include b/src/modules/schnorrsig_fullagg/Makefile.am.include index 0ac566f4c9..6944e012ec 100644 --- a/src/modules/schnorrsig_fullagg/Makefile.am.include +++ b/src/modules/schnorrsig_fullagg/Makefile.am.include @@ -2,3 +2,4 @@ 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 noinst_HEADERS += src/modules/schnorrsig_fullagg/vectors.h +noinst_HEADERS += src/modules/schnorrsig_fullagg/bench_impl.h diff --git a/src/modules/schnorrsig_fullagg/bench_impl.h b/src/modules/schnorrsig_fullagg/bench_impl.h new file mode 100644 index 0000000000..a1feef154d --- /dev/null +++ b/src/modules/schnorrsig_fullagg/bench_impl.h @@ -0,0 +1,106 @@ +/*********************************************************************** + * Copyright (c) 2026 The libsecp256k1 developers * + * 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_BENCH_H +#define SECP256K1_MODULE_SCHNORRSIG_FULLAGG_BENCH_H + +#include "../../../include/secp256k1_schnorrsig_fullagg.h" + +/* Number of signers in the benchmarked signing session */ +#define FULLAGG_BENCH_N_SIGNERS 3 + +typedef struct { + secp256k1_context *ctx; + + secp256k1_keypair keypairs[FULLAGG_BENCH_N_SIGNERS]; + secp256k1_xonly_pubkey pks[FULLAGG_BENCH_N_SIGNERS]; + const secp256k1_xonly_pubkey *pk_ptrs[FULLAGG_BENCH_N_SIGNERS]; + unsigned char msgs[FULLAGG_BENCH_N_SIGNERS][32]; + const unsigned char *msg_ptrs[FULLAGG_BENCH_N_SIGNERS]; + secp256k1_fullagg_pubnonce pubnonces[FULLAGG_BENCH_N_SIGNERS]; + const secp256k1_fullagg_pubnonce *pubnonce_ptrs[FULLAGG_BENCH_N_SIGNERS]; + secp256k1_fullagg_secnonce secnonce; + secp256k1_fullagg_session session; + unsigned char sig[64]; +} bench_fullagg_data; + +static void bench_fullagg_sign(void* arg, int iters) { + bench_fullagg_data *data = (bench_fullagg_data *)arg; + int i; + secp256k1_fullagg_partial_sig partial_sig; + + for (i = 0; i < iters; i++) { + secp256k1_fullagg_secnonce secnonce; + /* The secnonce is copied because signing invalidates it and this + * benchmark signs repeatedly with the same session. Application code + * must never copy a secnonce (see secp256k1_fullagg_secnonce). */ + memcpy(&secnonce, &data->secnonce, sizeof(secnonce)); + CHECK(secp256k1_fullagg_partial_sign(data->ctx, &partial_sig, &secnonce, &data->keypairs[0], + data->msgs[0], &data->session, data->pk_ptrs, + data->msg_ptrs, data->pubnonce_ptrs, + FULLAGG_BENCH_N_SIGNERS, 0)); + } +} + +static void bench_fullagg_verify(void* arg, int iters) { + bench_fullagg_data *data = (bench_fullagg_data *)arg; + int i; + + for (i = 0; i < iters; i++) { + CHECK(secp256k1_fullagg_verify(data->ctx, data->sig, data->pk_ptrs, data->msg_ptrs, + FULLAGG_BENCH_N_SIGNERS)); + } +} + +static void run_fullagg_bench(int iters, int argc, char** argv) { + int i; + bench_fullagg_data data; + secp256k1_fullagg_secnonce secnonces[FULLAGG_BENCH_N_SIGNERS]; + secp256k1_fullagg_aggnonce aggnonce; + secp256k1_fullagg_partial_sig psigs[FULLAGG_BENCH_N_SIGNERS]; + const secp256k1_fullagg_partial_sig *psig_ptrs[FULLAGG_BENCH_N_SIGNERS]; + int d = argc == 1; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + + for (i = 0; i < FULLAGG_BENCH_N_SIGNERS; i++) { + unsigned char sk[32]; + unsigned char secrand[32]; + + memset(sk, (unsigned char)(i + 1), sizeof(sk)); + memset(secrand, (unsigned char)(i + 0x40), sizeof(secrand)); + memset(data.msgs[i], (unsigned char)(i + 0x80), sizeof(data.msgs[i])); + data.pk_ptrs[i] = &data.pks[i]; + data.msg_ptrs[i] = data.msgs[i]; + data.pubnonce_ptrs[i] = &data.pubnonces[i]; + psig_ptrs[i] = &psigs[i]; + + CHECK(secp256k1_keypair_create(data.ctx, &data.keypairs[i], sk)); + CHECK(secp256k1_keypair_xonly_pub(data.ctx, &data.pks[i], NULL, &data.keypairs[i])); + CHECK(secp256k1_fullagg_nonce_gen(data.ctx, &secnonces[i], &data.pubnonces[i], secrand, + sk, &data.pks[i], NULL)); + } + /* Save signer 0's secnonce for bench_fullagg_sign before it is consumed */ + memcpy(&data.secnonce, &secnonces[0], sizeof(data.secnonce)); + + CHECK(secp256k1_fullagg_nonce_agg(data.ctx, &aggnonce, data.pubnonce_ptrs, FULLAGG_BENCH_N_SIGNERS)); + CHECK(secp256k1_fullagg_session_init(data.ctx, &data.session, &aggnonce, data.pk_ptrs, + data.msg_ptrs, data.pubnonce_ptrs, FULLAGG_BENCH_N_SIGNERS)); + for (i = 0; i < FULLAGG_BENCH_N_SIGNERS; i++) { + CHECK(secp256k1_fullagg_partial_sign(data.ctx, &psigs[i], &secnonces[i], &data.keypairs[i], + data.msgs[i], &data.session, data.pk_ptrs, + data.msg_ptrs, data.pubnonce_ptrs, + FULLAGG_BENCH_N_SIGNERS, i)); + } + CHECK(secp256k1_fullagg_partial_sig_agg(data.ctx, data.sig, &data.session, psig_ptrs, FULLAGG_BENCH_N_SIGNERS)); + + if (d || have_flag(argc, argv, "fullagg") || have_flag(argc, argv, "sign") || have_flag(argc, argv, "fullagg_sign")) run_benchmark("fullagg_sign", bench_fullagg_sign, NULL, NULL, (void *) &data, 10, iters); + if (d || have_flag(argc, argv, "fullagg") || have_flag(argc, argv, "verify") || have_flag(argc, argv, "fullagg_verify")) run_benchmark("fullagg_verify", bench_fullagg_verify, NULL, NULL, (void *) &data, 10, iters); + + secp256k1_context_destroy(data.ctx); +} + +#endif From 07e817b266d353b583b845497ef62df80a8f9b42 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 10 Jul 2026 14:48:26 +0200 Subject: [PATCH 09/10] fullagg: Add ctime tests --- src/ctime_tests.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 8a885ca2b4..88d9c051c9 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -36,6 +36,10 @@ #include "../include/secp256k1_musig.h" #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG +#include "../include/secp256k1_schnorrsig_fullagg.h" +#endif + #ifdef ENABLE_MODULE_ELLSWIFT #include "../include/secp256k1_ellswift.h" #endif @@ -243,6 +247,57 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { } #endif +#ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG + { + secp256k1_xonly_pubkey pk; + const secp256k1_xonly_pubkey *pk_ptr[1]; + const unsigned char *msg_ptr[1]; + unsigned char session_secrand[32]; + uint64_t nonrepeating_cnt = 0; + secp256k1_fullagg_secnonce secnonce; + secp256k1_fullagg_pubnonce pubnonce; + const secp256k1_fullagg_pubnonce *pubnonce_ptr[1]; + secp256k1_fullagg_aggnonce aggnonce; + secp256k1_fullagg_session session; + secp256k1_fullagg_partial_sig partial_sig; + unsigned char extra_input[32]; + + pk_ptr[0] = &pk; + msg_ptr[0] = msg; + pubnonce_ptr[0] = &pubnonce; + SECP256K1_CHECKMEM_DEFINE(key, 32); + memcpy(session_secrand, key, sizeof(session_secrand)); + session_secrand[0] = session_secrand[0] + 1; + memcpy(extra_input, key, sizeof(extra_input)); + extra_input[0] = extra_input[0] + 2; + + CHECK(secp256k1_keypair_create(ctx, &keypair, key)); + CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)); + + SECP256K1_CHECKMEM_UNDEFINE(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(session_secrand, sizeof(session_secrand)); + SECP256K1_CHECKMEM_UNDEFINE(extra_input, sizeof(extra_input)); + ret = secp256k1_fullagg_nonce_gen(ctx, &secnonce, &pubnonce, session_secrand, key, &pk, extra_input); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_fullagg_nonce_gen_counter(ctx, &secnonce, &pubnonce, nonrepeating_cnt, &keypair, extra_input); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + + CHECK(secp256k1_fullagg_nonce_agg(ctx, &aggnonce, pubnonce_ptr, 1)); + /* Make sure that previous tests don't undefine msg. It's not used as a secret here. */ + SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg)); + CHECK(secp256k1_fullagg_session_init(ctx, &session, &aggnonce, pk_ptr, msg_ptr, pubnonce_ptr, 1) == 1); + + ret = secp256k1_keypair_create(ctx, &keypair, key); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_fullagg_partial_sign(ctx, &partial_sig, &secnonce, &keypair, msg, &session, pk_ptr, msg_ptr, pubnonce_ptr, 1, 0); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + } +#endif + #ifdef ENABLE_MODULE_ELLSWIFT SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ellswift_create(ctx, ellswift, key, NULL); From 866e01507ccd195d8e2c3b4cd6750407a166c936 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 10 Jul 2026 23:50:50 +0200 Subject: [PATCH 10/10] TBD: Sharing common code between fullagg and musig --- Makefile.am | 2 + src/modules/musig/session_impl.h | 100 +++--------- src/modules/nonce_common.h | 52 +++++++ src/modules/nonce_common_impl.h | 122 +++++++++++++++ src/modules/schnorrsig_fullagg/main_impl.h | 167 ++++----------------- src/secp256k1.c | 4 + 6 files changed, 230 insertions(+), 217 deletions(-) create mode 100644 src/modules/nonce_common.h create mode 100644 src/modules/nonce_common_impl.h diff --git a/Makefile.am b/Makefile.am index 59e07f9978..bdc2baf706 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,6 +70,8 @@ noinst_HEADERS += src/bench.h noinst_HEADERS += src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h noinst_HEADERS += src/hsort.h noinst_HEADERS += src/hsort_impl.h +noinst_HEADERS += src/modules/nonce_common.h +noinst_HEADERS += src/modules/nonce_common_impl.h noinst_HEADERS += contrib/lax_der_parsing.h noinst_HEADERS += contrib/lax_der_parsing.c noinst_HEADERS += contrib/lax_der_privatekey_parsing.h diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 924738a3d4..5326a971e5 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -14,6 +14,7 @@ #include "keyagg.h" #include "session.h" +#include "../nonce_common.h" #include "../../eckey.h" #include "../../hash.h" #include "../../scalar.h" @@ -22,61 +23,25 @@ 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) { - memcpy(&secnonce->data[0], secp256k1_musig_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); + secp256k1_secnonce_save(secnonce->data, secp256k1_musig_secnonce_magic, k, pk); } static int secp256k1_musig_secnonce_load(const secp256k1_context* ctx, secp256k1_scalar *k, secp256k1_ge *pk, const secp256k1_musig_secnonce *secnonce) { - int is_zero; - ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_musig_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; + return secp256k1_secnonce_load(ctx, k, pk, secnonce->data, secp256k1_musig_secnonce_magic); } -/* If flag is 1, invalidate the secnonce; if flag is 0, leave it. - * Constant-time. Flag must be 0 or 1. */ static void secp256k1_musig_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_musig_secnonce *secnonce, int flag) { - secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag); - /* The flag argument is usually classified. So, the line above makes the - * 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_musig_secnonce_magic)); - secp256k1_declassify(ctx, &secnonce->data[68], 64); + secp256k1_secnonce_invalidate(ctx, secnonce->data, flag); } static const unsigned char secp256k1_musig_pubnonce_magic[4] = { 0xf5, 0x7a, 0x3d, 0xa0 }; -/* Saves two group elements into a pubnonce. Requires that none of the provided - * group elements is infinity. */ static void secp256k1_musig_pubnonce_save(secp256k1_musig_pubnonce* nonce, const secp256k1_ge* ges) { - int i; - memcpy(&nonce->data[0], secp256k1_musig_pubnonce_magic, 4); - for (i = 0; i < 2; i++) { - secp256k1_ge_to_bytes(nonce->data + 4+64*i, &ges[i]); - } + secp256k1_nonce_points_save(nonce->data, secp256k1_musig_pubnonce_magic, ges); } -/* Loads two group elements from a pubnonce. Returns 1 unless the nonce wasn't - * properly initialized */ static int secp256k1_musig_pubnonce_load(const secp256k1_context* ctx, secp256k1_ge* ges, const secp256k1_musig_pubnonce* nonce) { - int i; - - ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_musig_pubnonce_magic, 4) == 0); - for (i = 0; i < 2; i++) { - secp256k1_ge_from_bytes(&ges[i], nonce->data + 4 + 64*i); - } - return 1; + return secp256k1_nonce_points_load(ctx, ges, nonce->data, secp256k1_musig_pubnonce_magic); } static const unsigned char secp256k1_musig_aggnonce_magic[4] = { 0xa8, 0xb7, 0xe4, 0x67 }; @@ -145,35 +110,22 @@ static int secp256k1_musig_session_load(const secp256k1_context* ctx, secp256k1_ static const unsigned char secp256k1_musig_partial_sig_magic[4] = { 0xeb, 0xfb, 0x1a, 0x32 }; static void secp256k1_musig_partial_sig_save(secp256k1_musig_partial_sig* sig, secp256k1_scalar *s) { - memcpy(&sig->data[0], secp256k1_musig_partial_sig_magic, 4); - secp256k1_scalar_get_b32(&sig->data[4], s); + secp256k1_partial_sig_save(sig->data, secp256k1_musig_partial_sig_magic, s); } static int secp256k1_musig_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_musig_partial_sig* sig) { - int overflow; - - ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_musig_partial_sig_magic, 4) == 0); - secp256k1_scalar_set_b32(s, &sig->data[4], &overflow); - /* Parsed signatures can not overflow */ - VERIFY_CHECK(!overflow); - return 1; + return secp256k1_partial_sig_load(ctx, s, sig->data, secp256k1_musig_partial_sig_magic); } int secp256k1_musig_pubnonce_parse(const secp256k1_context* ctx, secp256k1_musig_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; - } + if (!secp256k1_nonce_points_parse(ges, in66)) { + return 0; } secp256k1_musig_pubnonce_save(nonce, ges); return 1; @@ -181,7 +133,6 @@ int secp256k1_musig_pubnonce_parse(const secp256k1_context* ctx, secp256k1_musig int secp256k1_musig_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_musig_pubnonce* nonce) { secp256k1_ge ges[2]; - int i; VERIFY_CHECK(ctx != NULL); ARG_CHECK(out66 != NULL); @@ -191,10 +142,8 @@ int secp256k1_musig_pubnonce_serialize(const secp256k1_context* ctx, unsigned ch if (!secp256k1_musig_pubnonce_load(ctx, ges, nonce)) { return 0; } - for (i = 0; i < 2; i++) { - /* serialize must succeed because the point was just loaded */ - secp256k1_eckey_pubkey_serialize33(&ges[i], &out66[33*i]); - } + /* serialize must succeed because the points were just loaded */ + secp256k1_nonce_points_serialize(out66, ges); return 1; } @@ -528,15 +477,6 @@ static void secp256k1_musig_compute_noncehash(const secp256k1_hash_ctx *hash_ctx secp256k1_sha256_finalize(hash_ctx, &sha, noncehash); } -/* out_nonce = nonce_pts[0] + b*nonce_pts[1] */ -static void secp256k1_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); -} - static void secp256k1_musig_nonce_process_internal(const secp256k1_context *ctx, int *fin_nonce_parity, unsigned char *fin_nonce, secp256k1_scalar *b, secp256k1_ge *aggnonce_pts, const unsigned char *agg_pk32, const unsigned char *msg) { unsigned char noncehash[32]; secp256k1_ge fin_nonce_pt; @@ -597,12 +537,6 @@ int secp256k1_musig_nonce_process(const secp256k1_context* ctx, secp256k1_musig_ return 1; } -static void secp256k1_musig_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) { - secp256k1_scalar_clear(sk); - secp256k1_scalar_clear(&k[0]); - secp256k1_scalar_clear(&k[1]); -} - int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_partial_sig *partial_sig, secp256k1_musig_secnonce *secnonce, const secp256k1_keypair *keypair, const secp256k1_musig_keyagg_cache *keyagg_cache, const secp256k1_musig_session *session) { secp256k1_scalar sk; secp256k1_ge pk, keypair_pk; @@ -621,7 +555,7 @@ int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_p * of this function to fail */ secp256k1_memzero_explicit(secnonce, sizeof(*secnonce)); if (!ret) { - secp256k1_musig_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -631,13 +565,13 @@ int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_p ARG_CHECK(session != NULL); if (!secp256k1_keypair_load(ctx, &sk, &keypair_pk, keypair)) { - secp256k1_musig_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } ARG_CHECK(secp256k1_fe_equal(&pk.x, &keypair_pk.x) && secp256k1_fe_equal(&pk.y, &keypair_pk.y)); if (!secp256k1_keyagg_cache_load(ctx, &cache_i, keyagg_cache)) { - secp256k1_musig_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -654,7 +588,7 @@ int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_p secp256k1_scalar_mul(&sk, &sk, &mu); if (!secp256k1_musig_session_load(ctx, &session_i, session)) { - secp256k1_musig_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -669,7 +603,7 @@ int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_p secp256k1_scalar_add(&k[0], &k[0], &k[1]); secp256k1_scalar_add(&s, &s, &k[0]); secp256k1_musig_partial_sig_save(partial_sig, &s); - secp256k1_musig_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 1; } diff --git a/src/modules/nonce_common.h b/src/modules/nonce_common.h new file mode 100644 index 0000000000..a5efc66e23 --- /dev/null +++ b/src/modules/nonce_common.h @@ -0,0 +1,52 @@ +/*********************************************************************** + * Copyright (c) The libsecp256k1 developers * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_NONCE_COMMON_H +#define SECP256K1_MODULE_NONCE_COMMON_H + +#include "../../include/secp256k1.h" + +#include "../group.h" +#include "../scalar.h" + +/* Helpers shared between the modules implementing interactive signing + * protocols with two-point nonces (musig and schnorrsig_fullagg). The magic + * argument distinguishes the modules' opaque objects from each other so that + * passing an object to the wrong module's API fails. */ + +/* Saves a secret nonce into a 132 byte buffer: magic (4 bytes) || k[0] (32 + * bytes) || k[1] (32 bytes) || pk (64 bytes). */ +static void secp256k1_secnonce_save(unsigned char *data, const unsigned char *magic, const secp256k1_scalar *k, const secp256k1_ge *pk); +static int secp256k1_secnonce_load(const secp256k1_context *ctx, secp256k1_scalar *k, secp256k1_ge *pk, const unsigned char *data, const unsigned char *magic); +/* If flag is 1, invalidate the secnonce; if flag is 0, leave it. + * Constant-time. Flag must be 0 or 1. */ +static void secp256k1_secnonce_invalidate(const secp256k1_context *ctx, unsigned char *data, int flag); + +/* Saves two group elements into a 132 byte buffer: magic (4 bytes) || ges[0] + * (64 bytes) || ges[1] (64 bytes). Requires that none of the provided group + * elements is infinity. */ +static void secp256k1_nonce_points_save(unsigned char *data, const unsigned char *magic, const secp256k1_ge *ges); +/* Loads two group elements. Returns 1 unless the buffer wasn't properly + * initialized. */ +static int secp256k1_nonce_points_load(const secp256k1_context *ctx, secp256k1_ge *ges, const unsigned char *data, const unsigned char *magic); +/* Parses two compressed points and verifies that they are in the correct + * subgroup. */ +static int secp256k1_nonce_points_parse(secp256k1_ge *ges, const unsigned char *in66); +/* Serializes two group elements as compressed points. Requires that none of + * the provided group elements is infinity. */ +static void secp256k1_nonce_points_serialize(unsigned char *out66, secp256k1_ge *ges); + +/* Saves a partial signature into a 36 byte buffer: magic (4 bytes) || s (32 + * bytes). */ +static void secp256k1_partial_sig_save(unsigned char *data, const unsigned char *magic, const secp256k1_scalar *s); +static int secp256k1_partial_sig_load(const secp256k1_context *ctx, secp256k1_scalar *s, const unsigned char *data, const unsigned char *magic); + +/* out_nonce = nonce_pts[0] + b*nonce_pts[1] */ +static void secp256k1_effective_nonce(secp256k1_gej *out_nonce, const secp256k1_ge *nonce_pts, const secp256k1_scalar *b); + +static void secp256k1_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k); + +#endif diff --git a/src/modules/nonce_common_impl.h b/src/modules/nonce_common_impl.h new file mode 100644 index 0000000000..73751dfe91 --- /dev/null +++ b/src/modules/nonce_common_impl.h @@ -0,0 +1,122 @@ +/*********************************************************************** + * Copyright (c) The libsecp256k1 developers * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_NONCE_COMMON_IMPL_H +#define SECP256K1_MODULE_NONCE_COMMON_IMPL_H + +#include + +#include "../../include/secp256k1.h" + +#include "nonce_common.h" +#include "../eckey.h" +#include "../ecmult.h" +#include "../group.h" +#include "../scalar.h" +#include "../util.h" + +static void secp256k1_secnonce_save(unsigned char *data, const unsigned char *magic, const secp256k1_scalar *k, const secp256k1_ge *pk) { + memcpy(&data[0], magic, 4); + secp256k1_scalar_get_b32(&data[4], &k[0]); + secp256k1_scalar_get_b32(&data[36], &k[1]); + secp256k1_ge_to_bytes(&data[68], pk); +} + +static int secp256k1_secnonce_load(const secp256k1_context *ctx, secp256k1_scalar *k, secp256k1_ge *pk, const unsigned char *data, const unsigned char *magic) { + int is_zero; + ARG_CHECK(secp256k1_memcmp_var(&data[0], 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(&data[4], 2 * 32); + secp256k1_declassify(ctx, &is_zero, sizeof(is_zero)); + ARG_CHECK(!is_zero); + + secp256k1_scalar_set_b32(&k[0], &data[4], NULL); + secp256k1_scalar_set_b32(&k[1], &data[36], NULL); + secp256k1_ge_from_bytes(pk, &data[68]); + return 1; +} + +static void secp256k1_secnonce_invalidate(const secp256k1_context *ctx, unsigned char *data, int flag) { + secp256k1_memczero(data, 132, 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, data, 4); + secp256k1_declassify(ctx, &data[68], 64); +} + +static void secp256k1_nonce_points_save(unsigned char *data, const unsigned char *magic, const secp256k1_ge *ges) { + int i; + memcpy(&data[0], magic, 4); + for (i = 0; i < 2; i++) { + secp256k1_ge_to_bytes(&data[4 + 64*i], &ges[i]); + } +} + +static int secp256k1_nonce_points_load(const secp256k1_context *ctx, secp256k1_ge *ges, const unsigned char *data, const unsigned char *magic) { + int i; + + ARG_CHECK(secp256k1_memcmp_var(&data[0], magic, 4) == 0); + for (i = 0; i < 2; i++) { + secp256k1_ge_from_bytes(&ges[i], &data[4 + 64*i]); + } + return 1; +} + +static int secp256k1_nonce_points_parse(secp256k1_ge *ges, const unsigned char *in66) { + int i; + + 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; + } + } + return 1; +} + +static void secp256k1_nonce_points_serialize(unsigned char *out66, secp256k1_ge *ges) { + int i; + + for (i = 0; i < 2; i++) { + secp256k1_eckey_pubkey_serialize33(&ges[i], &out66[33*i]); + } +} + +static void secp256k1_partial_sig_save(unsigned char *data, const unsigned char *magic, const secp256k1_scalar *s) { + memcpy(&data[0], magic, 4); + secp256k1_scalar_get_b32(&data[4], s); +} + +static int secp256k1_partial_sig_load(const secp256k1_context *ctx, secp256k1_scalar *s, const unsigned char *data, const unsigned char *magic) { + int overflow; + + ARG_CHECK(secp256k1_memcmp_var(&data[0], magic, 4) == 0); + secp256k1_scalar_set_b32(s, &data[4], &overflow); + /* Parsed signatures can not overflow */ + VERIFY_CHECK(!overflow); + return 1; +} + +static void secp256k1_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); +} + +static void secp256k1_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) { + secp256k1_scalar_clear(sk); + secp256k1_scalar_clear(&k[0]); + secp256k1_scalar_clear(&k[1]); +} + +#endif diff --git a/src/modules/schnorrsig_fullagg/main_impl.h b/src/modules/schnorrsig_fullagg/main_impl.h index 98deb2655a..70cca13def 100644 --- a/src/modules/schnorrsig_fullagg/main_impl.h +++ b/src/modules/schnorrsig_fullagg/main_impl.h @@ -13,6 +13,7 @@ #include "../../../include/secp256k1_extrakeys.h" #include "../../../include/secp256k1_schnorrsig_fullagg.h" +#include "../nonce_common.h" #include "../../eckey.h" #include "../../ecmult.h" #include "../../group.h" @@ -22,67 +23,26 @@ 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); + secp256k1_secnonce_save(secnonce->data, secp256k1_fullagg_secnonce_magic, k, 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; + return secp256k1_secnonce_load(ctx, k, pk, secnonce->data, secp256k1_fullagg_secnonce_magic); } -/* 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); + secp256k1_secnonce_invalidate(ctx, secnonce->data, flag); } 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]); - } + secp256k1_nonce_points_save(nonce->data, secp256k1_fullagg_pubnonce_magic, ges); } -/* 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; + return secp256k1_nonce_points_load(ctx, ges, nonce->data, secp256k1_fullagg_pubnonce_magic); } static const unsigned char secp256k1_fullagg_aggnonce_magic[4] = { 0xf1, 0x1a, 0x99, 0x03 }; @@ -90,72 +50,41 @@ static const unsigned char secp256k1_fullagg_aggnonce_magic[4] = { 0xf1, 0x1a, 0 /* Save aggregate nonce. The points are guaranteed not to be the point at * infinity because nonce aggregation fails in that case. */ 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(&nonce->data[4 + 64*i], &ges[i]); - } + secp256k1_nonce_points_save(nonce->data, secp256k1_fullagg_aggnonce_magic, ges); } /* 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(&ges[i], &nonce->data[4 + 64*i]); - } - return 1; + return secp256k1_nonce_points_load(ctx, ges, nonce->data, secp256k1_fullagg_aggnonce_magic); } 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); + secp256k1_partial_sig_save(sig->data, secp256k1_fullagg_partial_sig_magic, 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; + return secp256k1_partial_sig_load(ctx, s, sig->data, secp256k1_fullagg_partial_sig_magic); } -/* 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; - } + if (!secp256k1_nonce_points_parse(ges, in66)) { + 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); @@ -165,15 +94,12 @@ int secp256k1_fullagg_pubnonce_serialize(const secp256k1_context* ctx, unsigned if (!secp256k1_fullagg_pubnonce_load(ctx, ges, nonce)) { return 0; } - for (i = 0; i < 2; i++) { - secp256k1_eckey_pubkey_serialize33(&ges[i], &out66[33*i]); - } + secp256k1_nonce_points_serialize(out66, ges); return 1; } 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); @@ -182,13 +108,8 @@ int secp256k1_fullagg_aggnonce_parse(const secp256k1_context* ctx, secp256k1_ful /* The aggregate nonce consists of two compressed points. There is no * encoding for the point at infinity because nonce aggregation fails in * that case. */ - 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; - } + if (!secp256k1_nonce_points_parse(ges, in66)) { + return 0; } secp256k1_fullagg_aggnonce_save(nonce, ges); return 1; @@ -196,7 +117,6 @@ int secp256k1_fullagg_aggnonce_parse(const secp256k1_context* ctx, secp256k1_ful 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); @@ -206,13 +126,10 @@ int secp256k1_fullagg_aggnonce_serialize(const secp256k1_context* ctx, unsigned if (!secp256k1_fullagg_aggnonce_load(ctx, ges, nonce)) { return 0; } - for (i = 0; i < 2; i++) { - secp256k1_eckey_pubkey_serialize33(&ges[i], &out66[33*i]); - } + secp256k1_nonce_points_serialize(out66, ges); 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; @@ -230,7 +147,6 @@ int secp256k1_fullagg_partial_sig_parse(const secp256k1_context* ctx, secp256k1_ 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); @@ -630,16 +546,6 @@ static int secp256k1_fullagg_session_load(const secp256k1_context* ctx, } -/* TODO: Share with MuSig */ -/* out_nonce = nonce_pts[0] + b*nonce_pts[1] */ -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); -} - /* Initialize a FullAgg session. This implements GetSessionValues. */ int secp256k1_fullagg_session_init(const secp256k1_context* ctx, secp256k1_fullagg_session *session, const secp256k1_fullagg_aggnonce *aggnonce, @@ -685,7 +591,7 @@ int secp256k1_fullagg_session_init(const secp256k1_context* ctx, secp256k1_fulla /* Compute final nonce R = R1 + b*R2 and fail if it is the point at * infinity */ - secp256k1_fullagg_effective_nonce(&rj, aggnonce_pts, &noncecoef); + secp256k1_effective_nonce(&rj, aggnonce_pts, &noncecoef); secp256k1_ge_set_gej(&r, &rj); if (secp256k1_ge_is_infinity(&r)) { return 0; @@ -700,13 +606,6 @@ int secp256k1_fullagg_session_init(const secp256k1_context* ctx, secp256k1_fulla 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, @@ -751,19 +650,19 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla /* Always clear the secnonce to avoid nonce reuse */ secp256k1_memzero_explicit(secnonce, sizeof(*secnonce)); if (!ret) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } if (!secp256k1_keypair_load(ctx, &sk, &keypair_pk, keypair)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } /* Verify the keypair matches the secnonce. The secnonce stores the point * with even Y, so only the X coordinates are compared. */ if (!secp256k1_fe_equal(&pk.x, &keypair_pk.x)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -774,13 +673,13 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla } if (!secp256k1_fullagg_session_load(ctx, fin_nonce, &fin_nonce_parity, &noncecoef, aggnonce_ser, &n_session_signers, session)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } ARG_CHECK(n_signers == n_session_signers); if (signer_index >= n_signers) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -792,16 +691,16 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla { secp256k1_ge signer_pk; if (!secp256k1_xonly_pubkey_load(ctx, &signer_pk, pubkeys[signer_index])) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } if (!secp256k1_fe_equal(&keypair_pk.x, &signer_pk.x)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } } if (secp256k1_memcmp_var(messages[signer_index], msg32, 32) != 0) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -829,7 +728,7 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla * either. */ for (j = 0; j < n_signers; j++) { if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[j])) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -843,7 +742,7 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla /* R2 must appear exactly once and at the signer's index */ if (found_count != 1 || found_index != (int)signer_index) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } } @@ -856,12 +755,12 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla secp256k1_scalar noncecoef_check; if (!secp256k1_fullagg_compute_noncehash(ctx, noncehash, aggnonce_ser, pubkeys, messages, pubnonces, n_signers)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } secp256k1_scalar_set_b32(&noncecoef_check, noncehash, NULL); if (!secp256k1_scalar_eq(&noncecoef_check, &noncecoef)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } } @@ -876,7 +775,7 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla /* Compute signer's challenge c_i */ if (!secp256k1_fullagg_compute_sighash(ctx, &c_i, pubkeys, messages, n_signers, fin_nonce, signer_index)) { - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); return 0; } @@ -889,7 +788,7 @@ int secp256k1_fullagg_partial_sign(const secp256k1_context* ctx, secp256k1_fulla secp256k1_fullagg_partial_sig_save(partial_sig, &s); /* Clear sensitive data */ - secp256k1_fullagg_partial_sign_clear(&sk, k); + secp256k1_partial_sign_clear(&sk, k); secp256k1_scalar_clear(&s); return 1; @@ -954,7 +853,7 @@ int secp256k1_fullagg_partial_sig_verify(const secp256k1_context* ctx, const sec return 0; } secp256k1_scalar_set_b32(&noncecoef, noncehash, NULL); - secp256k1_fullagg_effective_nonce(&rj, aggnonce_pts, &noncecoef); + secp256k1_effective_nonce(&rj, aggnonce_pts, &noncecoef); secp256k1_ge_set_gej(&r, &rj); if (secp256k1_ge_is_infinity(&r)) { return 0; @@ -968,7 +867,7 @@ int secp256k1_fullagg_partial_sig_verify(const secp256k1_context* ctx, const sec if (!secp256k1_fullagg_pubnonce_load(ctx, nonce_pts, pubnonces[signer_index])) { return 0; } - secp256k1_fullagg_effective_nonce(&rj, nonce_pts, &noncecoef); + secp256k1_effective_nonce(&rj, nonce_pts, &noncecoef); /* Multiply by e: negate if R has odd Y */ if (fin_nonce_parity) { diff --git a/src/secp256k1.c b/src/secp256k1.c index b6c5ffadb2..47b64bfd37 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -842,6 +842,10 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, # include "modules/schnorrsig/main_impl.h" #endif +#if defined(ENABLE_MODULE_SCHNORRSIG_FULLAGG) || defined(ENABLE_MODULE_MUSIG) +# include "modules/nonce_common_impl.h" +#endif + #ifdef ENABLE_MODULE_SCHNORRSIG_FULLAGG # include "modules/schnorrsig_fullagg/main_impl.h" #endif