From 0495479257a07fad0539022b0959a29bb7d777f5 Mon Sep 17 00:00:00 2001 From: Benedikt Date: Thu, 27 Jul 2023 13:46:25 +0200 Subject: [PATCH] Incremental Half-Aggregation for Schnorr Signatures. --- .gitignore | 2 + include/secp256k1_schnorrsig.h | 75 ++++++++ src/modules/schnorrsig/main_impl.h | 288 ++++++++++++++++++++++++++++ src/modules/schnorrsig/tests_impl.h | 59 ++++++ 4 files changed, 424 insertions(+) diff --git a/.gitignore b/.gitignore index 9be37772b..92609973c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ schnorr_example *.log *.trs +.vscode/ + Makefile configure .libs/ diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 5fedcb07b..028f21412 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -175,6 +175,81 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify( const secp256k1_xonly_pubkey *pubkey ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5); + +/** Incrementally (Half-)Aggregate a sequence of Schnorr signatures to an existing half-aggregate signature. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: a secp256k1 context object. + * In/Out: aggsig: pointer to the serialized aggregate signature that is input. Will be overwritten by the new serialized aggregate signature. + * aggsig_size: size of the memory allocated in aggsig. Should be large enough to hold the new serialized aggregate signature. + * In: all_pubkeys: Array of x-only public keys, including both the ones for the already aggregated signature + * and the ones for the signatures that should be added. + * Assumed to contain n_sigs=n_sigs_before+n_sigs_new many public keys. + * all_msgs32: Array of 32-byte messages, including both the ones for the already aggregated signature + * and the ones for the signatures that should be added. + * Assumed to contain n_sigs=n_sigs_before+n_sigs_new many messages. + * new_sigs64: Array of 64-byte signatures, containing the new signatures that should be added. + * Assumed to contain n_sigs_new many signatures. + * n_sigs_before: Number of signatures that are already "contained" in the aggregate signature + * n_sigs_new: Number of signatures that should now be added to the aggregate signature + */ +SECP256K1_API int secp256k1_schnorrsig_inc_aggregate( + const secp256k1_context* ctx, + unsigned char* aggsig, + size_t* aggsig_size, + const secp256k1_xonly_pubkey* all_pubkeys, + const unsigned char* all_msgs32, + const unsigned char* new_sigs64, + size_t n_sigs_before, + size_t n_sigs_new +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + + + +/** (Half-)Aggregate a sequence of Schnorr signatures. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: a secp256k1 context object. + * Out: aggsig: pointer to an array of aggsig_size many bytes to store the serialized aggregate signature + * In/Out: aggsig_size: size of the aggsig array that is passed; will be overwritten to be the exact size of aggsig. + * In: pubkeys: Array of x-only public keys. Assumed to contain n_sigs many public keys. + * msgs32: Array of 32-byte messages. Assumed to contain n_sigs many messages. + * sigs64: Array of 64-byte signatures. Assumed to contain n_sigs many signatures. + * n_sigs: number of signatures to be aggregated. + */ +SECP256K1_API int secp256k1_schnorrsig_aggregate( + const secp256k1_context* ctx, + unsigned char* aggsig, + size_t* aggsig_size, + const secp256k1_xonly_pubkey* pubkeys, + const unsigned char* msgs32, + const unsigned char* sigs64, + size_t n_sigs +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + + + +/** Verify a (Half-)aggregate Schnorr signature. + * + * Returns: 1: correct signature + * 2: incorrect signature. + * Args: ctx: a secp256k1 context object. + * In: pubkeys: Array of x-only public keys. Assume to contain n_sigs many public keys. + * msgs32: Array of 32-byte messages. Assumed to contain n_sigs many messages. + * n_sigs: number of signatures to that have been aggregated. + * aggsig: Pointer to an array of aggsig_size many bytes containing the serialized aggregate signatur to be verified + * aggsig_size: Size of the aggregate signature + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_aggverify( + const secp256k1_context* ctx, + const secp256k1_xonly_pubkey* pubkeys, + const unsigned char* msgs32, + size_t n_sigs, + const unsigned char* aggsig, + size_t aggsig_size +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); + + #ifdef __cplusplus } #endif diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index cd651591c..0ef01dc32 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -264,4 +264,292 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha secp256k1_fe_equal_var(&rx, &r.x); } + + + + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("HalfAgg/randomizer")||SHA256("HalfAgg/randomizer"). */ +void secp256k1_schnorrsig_sha256_tagged_aggregation(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xd11f5532ul; + sha->s[1] = 0xfa57f70ful; + sha->s[2] = 0x5db0d728ul; + sha->s[3] = 0xf806ffe1ul; + sha->s[4] = 0x1d4db069ul; + sha->s[5] = 0xb4d587e1ul; + sha->s[6] = 0x50451c2aul; + sha->s[7] = 0x10fb63e9ul; + + sha->bytes = 64; +} + +/** + * Internal helper function for half-aggregation: + * Computes the coefficients zi for i=starting_from to i=n_sigs-1 + * used for aggregation for a given sequence of messages, keys and and r's. + * zs should have space for n_sigs-starting_from many scalars. + * Note: We pass pointers to the rs to avoid having to copy them twice +*/ +static int compute_aggregation_coefficients( + secp256k1_scalar* zs, + const unsigned char* pubkeys_ser, + const unsigned char** rs, + const unsigned char* msgs32, + size_t starting_from, + size_t n_sigs) { + + + uint32_t i; + secp256k1_sha256 hash; + + if (n_sigs <= starting_from) { + return 0; + } + + /* Write common prefix pre into tagged hash */ + /* Let n' = starting_from */ + /* pre = (r_0 || pk_0 || mes_0 || .... || r_{n'-1} || pk_{n'-1} || mes_{n'-1}) */ + secp256k1_schnorrsig_sha256_tagged_aggregation(&hash); + for (i = 0; i < starting_from; ++i) { + /* write r_i */ + secp256k1_sha256_write(&hash, rs[i], 32); + /* write pk_i */ + secp256k1_sha256_write(&hash, &pubkeys_ser[i*32], 32); + /* write mes_i*/ + secp256k1_sha256_write(&hash, &msgs32[i*32], 32); + } + + /* zi = Hash( pre || r_{n'} || pk_{n'} || mes_{n'} || ... || r_{i} || pk_{i} || mes_{i} ) */ + for (i = starting_from; i < n_sigs; ++i) { + unsigned char hashoutput[32]; + secp256k1_sha256 hashcopy; + /* Write into hash r_i, pk_i, mes_i */ + secp256k1_sha256_write(&hash, rs[i], 32); + secp256k1_sha256_write(&hash, &pubkeys_ser[i*32], 32); + secp256k1_sha256_write(&hash, &msgs32[i*32], 32); + /* Copy the hash */ + memcpy(&hashcopy,&hash,sizeof(hash)); + /* Finalize the copy to get zi*/ + secp256k1_sha256_finalize(&hashcopy, hashoutput); + secp256k1_scalar_set_b32(&zs[i-starting_from], hashoutput, NULL); + } + return 1; +} + + +int secp256k1_schnorrsig_inc_aggregate(const secp256k1_context* ctx, unsigned char* aggsig, size_t* aggsig_size, const secp256k1_xonly_pubkey* all_pubkeys, const unsigned char* all_msgs32, const unsigned char* new_sigs64, size_t n_sigs_before, size_t n_sigs_new) { + uint32_t i; + uint32_t n_sigs; + int overflow; + secp256k1_scalar s; + unsigned char* pubkeys_ser; /* this will store serialized pubkeys */ + secp256k1_scalar* zs; /* this will store the aggregation coefficients */ + const unsigned char** rs; /* this will store pointers to the r's in the aggsig */ + + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(aggsig != NULL); + ARG_CHECK(aggsig_size != NULL); + ARG_CHECK(all_pubkeys != NULL); + ARG_CHECK(all_msgs32 != NULL); + ARG_CHECK(new_sigs64 != NULL); + + /* Check that aggsig_size is large enough, i.e. aggsig_size >= 32*(n_sigs+1) */ + n_sigs = n_sigs_before + n_sigs_new; + if ((*aggsig_size >> 5) <= 0 || ((*aggsig_size >> 5)-1) < n_sigs) { + return 0; + } + + /* Serialize public keys. */ + /* We need that for the aggregation coefficients */ + pubkeys_ser = (unsigned char*) checked_malloc(&ctx->error_callback, 32*n_sigs); + for (i = 0; i < n_sigs; ++i) { + if (!secp256k1_xonly_pubkey_serialize(ctx, &pubkeys_ser[i*32], &all_pubkeys[i])) { + free(pubkeys_ser); + return 0; + } + } + + /* Compute new aggregation coefficients zi*/ + zs = (secp256k1_scalar*) checked_malloc(&ctx->error_callback, n_sigs_new*sizeof(secp256k1_scalar)); + rs = (const unsigned char**) checked_malloc(&ctx->error_callback, n_sigs*sizeof(unsigned char*)); + for (i = 0; i < n_sigs_before; ++i) { + rs[i] = &aggsig[i*32]; + } + for (i = 0; i < n_sigs_new; ++i) { + rs[i+n_sigs_before] = &new_sigs64[i*64]; + } + if (!compute_aggregation_coefficients(zs, pubkeys_ser, rs, all_msgs32, n_sigs_before, n_sigs)) { + free(pubkeys_ser); + free(zs); + free(rs); + return 0; + } + + /* Compute s = s_old + sum_{i = n_sigs_before}^{n_sigs} z_i*s_i */ + if (n_sigs_before == 0) { + secp256k1_scalar_set_int(&s, 0); + } else { + secp256k1_scalar_set_b32(&s, &aggsig[n_sigs_before*32], &overflow); + if (overflow) { + free(pubkeys_ser); + free(zs); + free(rs); + return 0; + } + } + for (i = n_sigs_before; i < n_sigs; ++i) { + secp256k1_scalar si; + int pos; + pos = i-n_sigs_before; + secp256k1_scalar_set_b32(&si, &new_sigs64[pos*64+32], NULL); + secp256k1_scalar_mul(&si, &si, &zs[pos]); + secp256k1_scalar_add(&s, &s, &si); + } + + /* copy new rs into aggsig */ + for (i = n_sigs_before; i < n_sigs; ++i) { + int pos; + pos = i-n_sigs_before; + memcpy(&aggsig[i*32], &new_sigs64[pos*64], 32); + } + + /* copy new s into aggsig */ + secp256k1_scalar_get_b32(&aggsig[n_sigs*32], &s); + *aggsig_size = 32 * (1 + n_sigs); + + free(pubkeys_ser); + free(zs); + free(rs); + + return 1; +} + +int secp256k1_schnorrsig_aggregate(const secp256k1_context* ctx, unsigned char* aggsig, size_t* aggsig_size, const secp256k1_xonly_pubkey* pubkeys, const unsigned char* msgs32, const unsigned char* sigs64, size_t n_sigs) { + if (!secp256k1_schnorrsig_inc_aggregate(ctx, aggsig, aggsig_size, pubkeys, msgs32, sigs64, 0, n_sigs)) { + return 0; + } + return 1; +} + +int secp256k1_schnorrsig_aggverify(const secp256k1_context* ctx, const secp256k1_xonly_pubkey* pubkeys, const unsigned char* msgs32, size_t n_sigs, const unsigned char* aggsig, size_t aggsig_size) { + uint32_t i; + secp256k1_gej lhs, rhs; + secp256k1_scalar s; + int overflow, result; + unsigned char* pubkeys_ser; /* this will store serialized pubkeys */ + secp256k1_scalar* zs; /* this will store the aggregation coefficients */ + const unsigned char** rs; /* this will store pointers to the r's in the aggsig */ + secp256k1_ge* ts; /* this will store values R_i + e_i*P_i */ + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubkeys != NULL); + ARG_CHECK(msgs32 != NULL); + ARG_CHECK(aggsig != NULL); + + /* Check that aggsig_size is correct, i.e. aggsig_size = 32*(n_sigs+1) */ + if ((aggsig_size >> 5) <= 0 || ((aggsig_size >> 5)-1) != n_sigs) { + return 0; + } + /* Serialize public keys. */ + /* We need that for the aggregation coefficients and for the Schnorr challenges */ + pubkeys_ser = (unsigned char*) checked_malloc(&ctx->error_callback, 32*n_sigs); + for (i = 0; i< n_sigs; ++i) { + if (!secp256k1_xonly_pubkey_serialize(ctx, &pubkeys_ser[i*32], &pubkeys[i])) { + free(pubkeys_ser); + return 0; + } + } + + /* Compute aggregation coefficients zi */ + zs = (secp256k1_scalar*) checked_malloc(&ctx->error_callback,n_sigs*sizeof(secp256k1_scalar)); + rs = (const unsigned char**) checked_malloc(&ctx->error_callback,n_sigs*sizeof(unsigned char*)); + for (i = 0; i< n_sigs; ++i) { + rs[i] = &aggsig[i*32]; + } + if (!compute_aggregation_coefficients(zs,pubkeys_ser,rs,msgs32,0,n_sigs)) { + free(pubkeys_ser); + free(zs); + free(rs); + return 0; + } + + /* For each i in 0,.., n_sigs-1, we compute T_i = R_i+e_i*P_i */ + ts = (secp256k1_ge*) checked_malloc(&ctx->error_callback, n_sigs*sizeof(secp256k1_ge)); + + for (i = 0; i < n_sigs; ++i) { + secp256k1_fe rx; + secp256k1_ge rp, pp; + secp256k1_scalar ei; + secp256k1_gej ppj, acc; + + /* R_i = lift_x(int(r_i)); fail if that fails */ + if (!secp256k1_fe_set_b32(&rx, &aggsig[i*32])) { + free(pubkeys_ser); + free(zs); + free(rs); + free(ts); + return 0; + } + if (!secp256k1_ge_set_xo_var(&rp, &rx, 0)) { + free(pubkeys_ser); + free(zs); + free(rs); + free(ts); + return 0; + } + /* P_i = lift_x(int(pk_i)); fail if that fails */ + if (!secp256k1_xonly_pubkey_load(ctx, &pp, &pubkeys[i])) { + free(pubkeys_ser); + free(zs); + free(rs); + free(ts); + return 0; + } + /* e_i = int(hash_{BIP0340/challenge}(bytes(r_i) || pk_i || m_i)) mod n */ + secp256k1_schnorrsig_challenge(&ei, &aggsig[i*32], &msgs32[i*32], 32, &pubkeys_ser[i*32]); + secp256k1_gej_set_ge(&ppj, &pp); + /* e_i⋅P_i */ + secp256k1_ecmult(&acc, &ppj, &ei, NULL); + /* R_i + e_i⋅P_i */ + secp256k1_gej_add_ge_var(&acc, &acc, &rp, NULL); + /* Write into T_i */ + secp256k1_ge_set_gej_var(&ts[i], &acc); + } + + /* Compute the rhs as rhs = sum_{i} z_i*T_i */ + /* Later, this should be a multi-exp */ + secp256k1_gej_set_infinity(&rhs); + for (i = 0; i < n_sigs; ++i) { + secp256k1_gej acc; + secp256k1_gej_set_ge(&acc, &ts[i]); + secp256k1_ecmult(&acc, &acc, &zs[i], NULL); + secp256k1_gej_add_var(&rhs, &rhs, &acc, NULL); + } + + /* Compute the lhs as lhs = s*G */ + secp256k1_scalar_set_b32(&s, &aggsig[n_sigs*32], &overflow); + if (overflow) { + free(pubkeys_ser); + free(zs); + free(rs); + free(ts); + return 0; + } + secp256k1_ecmult(&lhs, NULL, &secp256k1_scalar_zero, &s); + + /* Check that lhs == rhs */ + secp256k1_gej_neg(&lhs, &lhs); + secp256k1_gej_add_var(&lhs, &lhs, &rhs, NULL); + result = secp256k1_gej_is_infinity(&lhs); + + free(pubkeys_ser); + free(zs); + free(rs); + free(ts); + + return result; +} + #endif diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 25840b8fa..457b537c0 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -888,6 +888,62 @@ void test_schnorrsig_taproot(void) { CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); } + + +/* We test that hash initialized by secp256k1_schnorrsig_sha256_tagged_aggregate has the + * expected state. */ +void test_schnorrsig_sha256_tagged_aggregate(void) { + unsigned char tag[18] = "HalfAgg/randomizer"; + secp256k1_sha256 sha; + secp256k1_sha256 sha_optimized; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char *) tag, sizeof(tag)); + secp256k1_schnorrsig_sha256_tagged_aggregation(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); +} + + + +/* In this test we create a bunch of Schnorr signatures, +* aggregate N_SIGS_INITIAL of them in one shot, and then +* aggregate N_SIGS_NEW of them incrementally to the already aggregated ones +* the aggregate signature should verify after both steps +*/ +#define N_SIGS_INITIAL 3 +#define N_SIGS_NEW 5 +#define N_SIGS (N_SIGS_INITIAL+N_SIGS_NEW) +void test_schnorrsig_aggregate(void) { + int i; + secp256k1_xonly_pubkey pubkeys[N_SIGS]; + unsigned char msgs32[N_SIGS*32]; + unsigned char sigs64[N_SIGS*64]; + unsigned char aggsig[32*(N_SIGS + 1)]; + size_t aggsig_size = 32*(N_SIGS + 1); + + /* create N_SIGS many Schnorr keys and sigs for random messages */ + for (i = 0; i < N_SIGS; ++i) { + unsigned char sk[32]; + secp256k1_keypair keypair; + secp256k1_testrand256(sk); + secp256k1_testrand256(&msgs32[i*32]); + + CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(ctx, &pubkeys[i], NULL, &keypair)); + CHECK(secp256k1_schnorrsig_sign(ctx, &sigs64[i*64], &msgs32[i*32], &keypair, NULL)); + } + + /* aggregate the first N_SIGS_INITIAL of them */ + CHECK(secp256k1_schnorrsig_aggregate(ctx, aggsig, &aggsig_size, pubkeys, msgs32, sigs64, N_SIGS_INITIAL)); + /* make sure that the aggregate signature verifies */ + CHECK(secp256k1_schnorrsig_aggverify(ctx, pubkeys, msgs32, N_SIGS_INITIAL, aggsig, aggsig_size)); + /* aggregate the remaining N_SIGS_NEW many signatures to the already existing ones */ + aggsig_size = 32*(N_SIGS + 1); + secp256k1_schnorrsig_inc_aggregate(ctx, aggsig, &aggsig_size, pubkeys, msgs32, &sigs64[N_SIGS_INITIAL*64], N_SIGS_INITIAL, N_SIGS_NEW); + /* make sure that the aggregate signature verifies */ + CHECK(secp256k1_schnorrsig_aggverify(ctx, pubkeys, msgs32, N_SIGS, aggsig, aggsig_size)); +} + + void run_schnorrsig_tests(void) { int i; run_nonce_function_bip340_tests(); @@ -900,6 +956,9 @@ void run_schnorrsig_tests(void) { test_schnorrsig_sign_verify(); } test_schnorrsig_taproot(); + + test_schnorrsig_sha256_tagged_aggregate(); + test_schnorrsig_aggregate(); } #endif