Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Schnorrsig half aggregation #130

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions include/secp256k1_schnorrsig.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SECP256K1_SCHNORRSIG_H
#define SECP256K1_SCHNORRSIG_H

#include <stdint.h>

#include "secp256k1.h"
#include "secp256k1_extrakeys.h"

Expand Down Expand Up @@ -104,6 +106,25 @@ 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(3) SECP256K1_ARG_NONNULL(4);

SECP256K1_API int secp256k1_schnorrsig_aggregate(
const secp256k1_context* ctx,
unsigned char* aggsig,
size_t* aggsig_size,
unsigned char **sig64,
unsigned char **msg32,
secp256k1_xonly_pubkey *pubkey,
uint32_t n_sigs
Comment on lines +112 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know C is sloppy with this, but const unsigned char **sig64 would be nice. And msg32.

Also, n_sigs here and n_msgs below?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And sig64 adn msg32 are arrays of ptrys, pubkey is a direct array of objects. That's a bit weird? Also, please pubkeys to give the poor user a hint?

) 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_API int secp256k1_schnorrsig_aggverify(
const secp256k1_context* ctx,
unsigned char **msg32,
uint32_t n_msgs,
secp256k1_xonly_pubkey *pubkey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const here too?

unsigned char *aggsig,
size_t aggsig_size
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);

#ifdef __cplusplus
}
#endif
Expand Down
159 changes: 159 additions & 0 deletions src/modules/schnorrsig/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,163 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
secp256k1_fe_equal_var(&rx, &r.x);
}

static int compute_midhash(const secp256k1_context* ctx, unsigned char *midhash, unsigned char **sig64, unsigned char *aggsig, unsigned char **msg32, secp256k1_xonly_pubkey *pubkey, size_t n_sigs) {
secp256k1_sha256 hash;
uint32_t i;

VERIFY_CHECK((aggsig != NULL) != (sig64 != NULL));

/* TODO: use actual tagged hash */
secp256k1_sha256_initialize(&hash);
/* z_i = int(hash_{HalfAggregation}(r_1 || pk_1 || m_1 || ... || r_n || pk_n || m_n || i)) mod n */
for (i = 0; i < n_sigs; i++) {
unsigned char pk_ser[32];

/* r_i = sig_i[0:32] */
if (sig64 != NULL) {
secp256k1_sha256_write(&hash, sig64[i], 32);
} else {
/* aggsig != NULL */
secp256k1_sha256_write(&hash, &aggsig[i*32], 32);
}
if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, &pubkey[i])) {
return 0;
}
secp256k1_sha256_write(&hash, pk_ser, sizeof(pk_ser));
secp256k1_sha256_write(&hash, msg32[i], 32);
}
/* TODO: copy midstate instead of computing intermediate "midhash" */
secp256k1_sha256_finalize(&hash, midhash);
return 1;
}

int secp256k1_schnorrsig_aggregate(const secp256k1_context* ctx, unsigned char* aggsig, size_t* aggsig_size, unsigned char **sig64, unsigned char **msg32, secp256k1_xonly_pubkey *pubkey, uint32_t n_sigs) {
uint32_t i;
secp256k1_sha256 hash;
unsigned char midhash[32];
secp256k1_scalar s;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(aggsig != NULL);
ARG_CHECK(aggsig_size != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pubkey != NULL);

if (*aggsig_size < 32*(1 + n_sigs)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overflow protection needed here, AFAICT. You probably want to divide aggsig_size, check it's > 0, then sub 1 and compare with n_sigs.

return 0;
}

if (!compute_midhash(ctx, midhash, sig64, NULL, msg32, pubkey, n_sigs)) {
return 0;
}

/* s = z_1⋅s_1 + ... + z_n⋅s_n */
secp256k1_scalar_set_int(&s, 0);
for (i = 0; i < n_sigs; i++) {
unsigned char randomizer[32];
secp256k1_scalar zi;
secp256k1_scalar si;

secp256k1_sha256_initialize(&hash);
secp256k1_sha256_write(&hash, midhash, sizeof(midhash));
/* TODO: fix endianness issue */
secp256k1_sha256_write(&hash, (unsigned char *)&i, sizeof(i));
Comment on lines +299 to +300
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Please do :)

secp256k1_sha256_finalize(&hash, randomizer);
secp256k1_scalar_set_b32(&zi, randomizer, NULL);

secp256k1_scalar_set_b32(&si, &sig64[i][32], NULL);
secp256k1_scalar_mul(&si, &si, &zi);
secp256k1_scalar_add(&s, &s, &si);

memcpy(&aggsig[i*32], sig64[i], 32);
}

/* Return sig = r_1 || ... || r_n || bytes(s) */
secp256k1_scalar_get_b32(&aggsig[n_sigs*32], &s);
*aggsig_size = 32 * (1 + n_sigs);

return 1;
}

int secp256k1_schnorrsig_aggverify(const secp256k1_context* ctx, unsigned char **msg32, uint32_t n_msgs, secp256k1_xonly_pubkey *pubkey, unsigned char *aggsig, size_t aggsig_size) {
unsigned char midhash[32];
uint32_t i;
secp256k1_gej lhs, rhs;
secp256k1_scalar s;
int overflow;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pubkey != NULL);
ARG_CHECK(aggsig != NULL);

secp256k1_gej_set_infinity(&rhs);
if (aggsig_size != 32*(1 + n_msgs)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overflow

return 0;
}

if (!compute_midhash(ctx, midhash, NULL, aggsig, msg32, pubkey, n_msgs)) {
return 0;
}

for (i = 0; i < n_msgs; i++) {
secp256k1_sha256 hash;
unsigned char randomizer[32];
secp256k1_scalar zi;
secp256k1_fe rx;
secp256k1_ge rp, pp;
secp256k1_scalar ei;
unsigned char pk_ser[32];
secp256k1_gej ppj, acc;

secp256k1_sha256_initialize(&hash);
secp256k1_sha256_write(&hash, midhash, sizeof(midhash));
/* TODO: fix endianness issue */
secp256k1_sha256_write(&hash, (unsigned char *)&i, sizeof(i));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted.

secp256k1_sha256_finalize(&hash, randomizer);
secp256k1_scalar_set_b32(&zi, randomizer, NULL);

/* R_i = lift_x(int(r_i)); fail if that fails */
if (!secp256k1_fe_set_b32(&rx, &aggsig[i*32])) {
return 0;
}
if (!secp256k1_ge_set_xo_var(&rp, &rx, 0)) {
return 0;
}

/* P_i = lift_x(int(pk_i)); fail if that fails */
if (!secp256k1_xonly_pubkey_load(ctx, &pp, &pubkey[i])) {
return 0;
}

/* e_i = int(hash_{BIP0340/challenge}(bytes(r_i) || pk_i || m_i)) mod n */
/* TODO: compute pubkey serialization again after compute_midhash? */
if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, &pubkey[i])) {
return 0;
}
secp256k1_schnorrsig_challenge(&ei, &aggsig[i*32], msg32[i], pk_ser);
secp256k1_gej_set_ge(&ppj, &pp);
/* e_i⋅P_i */
secp256k1_ecmult(&ctx->ecmult_ctx, &acc, &ppj, &ei, NULL);
/* R_i + e_i⋅P_i */
secp256k1_gej_add_ge_var(&acc, &acc, &rp, NULL);
/* z_i⋅(R_i + e_i⋅P_i) */
secp256k1_ecmult(&ctx->ecmult_ctx, &acc, &acc, &zi, NULL);
secp256k1_gej_add_var(&rhs, &rhs, &acc, NULL);
}
/* s = int(sig[n⋅32:(n+1)⋅32]) */
secp256k1_scalar_set_b32(&s, &aggsig[n_msgs*32], &overflow);
if (overflow) {
return 0;
}
/* s⋅G */
secp256k1_ecmult(&ctx->ecmult_ctx, &lhs, NULL, &secp256k1_scalar_zero, &s);

/* lhs ?= rhs */
secp256k1_gej_neg(&lhs, &lhs);
secp256k1_gej_add_var(&lhs, &lhs, &rhs, NULL);
return secp256k1_gej_is_infinity(&lhs);
}

#endif
31 changes: 31 additions & 0 deletions src/modules/schnorrsig/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,36 @@ void test_schnorrsig_taproot(void) {
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1);
}


#define N_SIGS 3
void test_schnorrsig_aggregate(void) {
unsigned char aggsig[32*(N_SIGS + 1)];
size_t aggsig_size = sizeof(aggsig);
unsigned char sig64[N_SIGS][64];
unsigned char *sig64_ptr[N_SIGS];
unsigned char msg32[N_SIGS][32];
unsigned char *msg32_ptr[N_SIGS];
secp256k1_xonly_pubkey pubkey[N_SIGS];
int i;

for (i = 0; i < N_SIGS; i++) {
unsigned char sk[32];
secp256k1_keypair keypair;

msg32_ptr[i] = &msg32[i][0];
sig64_ptr[i] = &sig64[i][0];
secp256k1_testrand256(sk);
secp256k1_testrand256(msg32[i]);

CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1);
CHECK(secp256k1_keypair_xonly_pub(ctx, &pubkey[i], NULL, &keypair));
CHECK(secp256k1_schnorrsig_sign(ctx, sig64[i], msg32[i], &keypair, NULL, NULL));
}

CHECK(secp256k1_schnorrsig_aggregate(ctx, aggsig, &aggsig_size, sig64_ptr, msg32_ptr, pubkey, N_SIGS));
CHECK(secp256k1_schnorrsig_aggverify(ctx, msg32_ptr, N_SIGS, pubkey, aggsig, aggsig_size));
}

void run_schnorrsig_tests(void) {
int i;
run_nonce_function_bip340_tests();
Expand All @@ -801,6 +831,7 @@ void run_schnorrsig_tests(void) {
test_schnorrsig_sign_verify();
}
test_schnorrsig_taproot();
test_schnorrsig_aggregate();
}

#endif