Skip to content

Commit

Permalink
schnorrsig: add batch_verify
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasnick committed Sep 11, 2020
1 parent 7936de8 commit 03a61e3
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 24 deletions.
21 changes: 21 additions & 0 deletions include/secp256k1_schnorrsig.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ 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);

/** Verifies a set of Schnorr signatures.
*
* Returns 1 if all succeeded, 0 otherwise. In particular, returns 1 if n_sigs is 0.
*
* Args: ctx: a secp256k1 context object, initialized for verification.
* scratch: scratch space used for the multiexponentiation
* In: sig: array of pointers to signatures, or NULL if there are no signatures
* msg32: array of pointers to messages, or NULL if there are no signatures
* pk: array of pointers to x-only public keys, or NULL if there are no signatures
* n_sigs: number of signatures in above arrays. Must be below the
* minimum of 2^31 and SIZE_MAX/2. Must be 0 if above arrays are NULL.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify_batch(
const secp256k1_context* ctx,
secp256k1_scratch_space *scratch,
const unsigned char *const *sig,
const unsigned char *const *msg32,
const secp256k1_xonly_pubkey *const *pk,
size_t n_sigs
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);

#ifdef __cplusplus
}
#endif
Expand Down
34 changes: 34 additions & 0 deletions src/bench_schnorrsig.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

typedef struct {
secp256k1_context *ctx;
secp256k1_scratch_space *scratch;
int n;

const secp256k1_keypair **keypairs;
Expand Down Expand Up @@ -47,12 +48,35 @@ void bench_schnorrsig_verify(void* arg, int iters) {
}
}

void bench_schnorrsig_verify_n(void* arg, int iters) {
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
int i, j;
const secp256k1_xonly_pubkey **pk = (const secp256k1_xonly_pubkey **)malloc(data->n * sizeof(*pk));

CHECK(pk != NULL);
for (j = 0; j < iters/data->n; j++) {
for (i = 0; i < data->n; i++) {
secp256k1_xonly_pubkey *pk_nonconst = (secp256k1_xonly_pubkey *)malloc(sizeof(*pk_nonconst));
CHECK(secp256k1_xonly_pubkey_parse(data->ctx, pk_nonconst, data->pk[i]) == 1);
pk[i] = pk_nonconst;
}
CHECK(secp256k1_schnorrsig_verify_batch(data->ctx, data->scratch, data->sigs, data->msgs, pk, data->n));
for (i = 0; i < data->n; i++) {
free((void *)pk[i]);
}
}
free(pk);
}

int main(void) {
int i;
bench_schnorrsig_data data;
int iters = get_iters(10000);

data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
/* Scratch space size was selected to allow fitting the maximum number of
* points for the default iters value into a single ecmult_multi batch. */
data.scratch = secp256k1_scratch_space_create(data.ctx, 5 * 1024 * 1024);
data.keypairs = (const secp256k1_keypair **)malloc(iters * sizeof(secp256k1_keypair *));
data.pk = (const unsigned char **)malloc(iters * sizeof(unsigned char *));
data.msgs = (const unsigned char **)malloc(iters * sizeof(unsigned char *));
Expand Down Expand Up @@ -85,6 +109,15 @@ int main(void) {

run_benchmark("schnorrsig_sign", bench_schnorrsig_sign, NULL, NULL, (void *) &data, 10, iters);
run_benchmark("schnorrsig_verify", bench_schnorrsig_verify, NULL, NULL, (void *) &data, 10, iters);
for (i = 1; i <= iters; i *= 2) {
char name[64];
int divisible_iters;
sprintf(name, "schnorrsig_batch_verify_%d", (int) i);

data.n = i;
divisible_iters = iters - (iters % data.n);
run_benchmark(name, bench_schnorrsig_verify_n, NULL, NULL, (void *) &data, 3, divisible_iters);
}

for (i = 0; i < iters; i++) {
free((void *)data.keypairs[i]);
Expand All @@ -97,6 +130,7 @@ int main(void) {
free(data.msgs);
free(data.sigs);

secp256k1_scratch_space_destroy(data.ctx, data.scratch);
secp256k1_context_destroy(data.ctx);
return 0;
}
184 changes: 184 additions & 0 deletions src/modules/schnorrsig/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,188 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
secp256k1_fe_equal_var(&rx, &r.x);
}

/* Data that is used by the batch verification ecmult callback */
typedef struct {
const secp256k1_context *ctx;
/* Seed for the random number generator */
unsigned char chacha_seed[32];
/* Caches randomizers generated by the PRNG which returns two randomizers per call. Caching
* avoids having to call the PRNG twice as often. The very first randomizer will be set to 1 and
* the PRNG is called at every odd indexed schnorrsig to fill the cache. */
secp256k1_scalar randomizer_cache[2];
/* Signature, message, public key tuples to verify */
const unsigned char *const *sig;
const unsigned char *const *msg32;
const secp256k1_xonly_pubkey *const *pk;
size_t n_sigs;
} secp256k1_schnorrsig_verify_ecmult_context;

/* Callback function which is called by ecmult_multi in order to convert the ecmult_context
* consisting of signature, message and public key tuples into scalars and points. */
static int secp256k1_schnorrsig_verify_batch_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) {
secp256k1_schnorrsig_verify_ecmult_context *ecmult_context = (secp256k1_schnorrsig_verify_ecmult_context *) data;

if (idx % 4 == 2) {
/* Every idx corresponds to a (scalar,point)-tuple. So this callback is called with 4
* consecutive tuples before we need to call the RNG for new randomizers:
* (-randomizer_cache[0], R1)
* (-randomizer_cache[0]*e1, P1)
* (-randomizer_cache[1], R2)
* (-randomizer_cache[1]*e2, P2) */
secp256k1_scalar_chacha20(&ecmult_context->randomizer_cache[0], &ecmult_context->randomizer_cache[1], ecmult_context->chacha_seed, idx / 4);
}

/* R */
if (idx % 2 == 0) {
secp256k1_fe rx;
*sc = ecmult_context->randomizer_cache[(idx / 2) % 2];
if (!secp256k1_fe_set_b32(&rx, &ecmult_context->sig[idx / 2][0])) {
return 0;
}
if (!secp256k1_ge_set_xo_var(pt, &rx, 0)) {
return 0;
}
/* eP */
} else {
unsigned char buf[32];
secp256k1_sha256 sha;

/* xonly_pubkey_load is guaranteed not to fail because
* verify_batch_init_randomizer calls secp256k1_ec_pubkey_serialize
* which only works if loading the pubkey into a group element
* succeeds.*/
VERIFY_CHECK(secp256k1_xonly_pubkey_load(ecmult_context->ctx, pt, ecmult_context->pk[idx / 2]));

secp256k1_schnorrsig_sha256_tagged(&sha);
secp256k1_sha256_write(&sha, &ecmult_context->sig[idx / 2][0], 32);
secp256k1_fe_get_b32(buf, &pt->x);
secp256k1_sha256_write(&sha, buf, sizeof(buf));
secp256k1_sha256_write(&sha, ecmult_context->msg32[idx / 2], 32);
secp256k1_sha256_finalize(&sha, buf);

secp256k1_scalar_set_b32(sc, buf, NULL);
secp256k1_scalar_mul(sc, sc, &ecmult_context->randomizer_cache[(idx / 2) % 2]);
}
return 1;
}

/** Helper function for batch verification. Hashes signature verification data into the
* randomization seed and initializes ecmult_context.
*
* Returns 1 if the randomizer was successfully initialized.
*
* Args: ctx: a secp256k1 context object
* Out: ecmult_context: context for batch_ecmult_callback
* In/Out sha: an initialized sha256 object which hashes the schnorrsig input in order to get a
* seed for the randomizer PRNG
* In: sig: array of signatures, or NULL if there are no signatures
* msg32: array of messages, or NULL if there are no signatures
* pk: array of public keys, or NULL if there are no signatures
* n_sigs: number of signatures in above arrays (must be 0 if they are NULL)
*/
static int secp256k1_schnorrsig_verify_batch_init_randomizer(const secp256k1_context *ctx, secp256k1_schnorrsig_verify_ecmult_context *ecmult_context, secp256k1_sha256 *sha, const unsigned char *const *sig, const unsigned char *const *msg32, const secp256k1_xonly_pubkey *const *pk, size_t n_sigs) {
size_t i;

if (n_sigs > 0) {
ARG_CHECK(sig != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pk != NULL);
}

for (i = 0; i < n_sigs; i++) {
unsigned char buf[33];
size_t buflen = sizeof(buf);
secp256k1_sha256_write(sha, sig[i], 64);
secp256k1_sha256_write(sha, msg32[i], 32);
/* We use compressed serialization here. If we would use
* xonly_pubkey serialization and a user would wrongly memcpy
* normal secp256k1_pubkeys into xonly_pubkeys then the randomizer
* would be the same for two different pubkeys. */
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &buflen, (const secp256k1_pubkey *) pk[i], SECP256K1_EC_COMPRESSED)) {
return 0;
}
secp256k1_sha256_write(sha, buf, buflen);
}
ecmult_context->ctx = ctx;
ecmult_context->sig = sig;
ecmult_context->msg32 = msg32;
ecmult_context->pk = pk;
ecmult_context->n_sigs = n_sigs;

return 1;
}

/** Helper function for batch verification. Sums the s part of all signatures multiplied by their
* randomizer.
*
* Returns 1 if s is successfully summed.
*
* In/Out: s: the s part of the input sigs is added to this s argument
* In: chacha_seed: PRNG seed for computing randomizers
* sig: array of signatures, or NULL if there are no signatures
* n_sigs: number of signatures in above array (must be 0 if they are NULL)
*/
static int secp256k1_schnorrsig_verify_batch_sum_s(secp256k1_scalar *s, unsigned char *chacha_seed, const unsigned char *const *sig, size_t n_sigs) {
secp256k1_scalar randomizer_cache[2];
size_t i;

secp256k1_scalar_set_int(&randomizer_cache[0], 1);
for (i = 0; i < n_sigs; i++) {
int overflow;
secp256k1_scalar term;
if (i % 2 == 1) {
secp256k1_scalar_chacha20(&randomizer_cache[0], &randomizer_cache[1], chacha_seed, i / 2);
}

secp256k1_scalar_set_b32(&term, &sig[i][32], &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_mul(&term, &term, &randomizer_cache[i % 2]);
secp256k1_scalar_add(s, s, &term);
}
return 1;
}

/* schnorrsig batch verification.
*
* Seeds a random number generator with the inputs and derives a random number
* ai for every signature i. Fails if
*
* 0 != -(s1 + a2*s2 + ... + au*su)G
* + R1 + a2*R2 + ... + au*Ru + e1*P1 + (a2*e2)P2 + ... + (au*eu)Pu.
*/
int secp256k1_schnorrsig_verify_batch(const secp256k1_context *ctx, secp256k1_scratch *scratch, const unsigned char *const *sig, const unsigned char *const *msg32, const secp256k1_xonly_pubkey *const *pk, size_t n_sigs) {
secp256k1_schnorrsig_verify_ecmult_context ecmult_context;
secp256k1_sha256 sha;
secp256k1_scalar s;
secp256k1_gej rj;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
ARG_CHECK(scratch != NULL);
/* Check that n_sigs is less than half of the maximum size_t value. This is necessary because
* the number of points given to ecmult_multi is 2*n_sigs. */
ARG_CHECK(n_sigs <= SIZE_MAX / 2);
/* Check that n_sigs is less than 2^31 to ensure the same behavior of this function on 32-bit
* and 64-bit platforms. */
ARG_CHECK(n_sigs < ((uint32_t)1 << 31));

secp256k1_sha256_initialize(&sha);
if (!secp256k1_schnorrsig_verify_batch_init_randomizer(ctx, &ecmult_context, &sha, sig, msg32, pk, n_sigs)) {
return 0;
}
secp256k1_sha256_finalize(&sha, ecmult_context.chacha_seed);
secp256k1_scalar_set_int(&ecmult_context.randomizer_cache[0], 1);

secp256k1_scalar_clear(&s);
if (!secp256k1_schnorrsig_verify_batch_sum_s(&s, ecmult_context.chacha_seed, sig, n_sigs)) {
return 0;
}
secp256k1_scalar_negate(&s, &s);

return secp256k1_ecmult_multi_var(&ctx->error_callback, &ctx->ecmult_ctx, scratch, &rj, &s, secp256k1_schnorrsig_verify_batch_ecmult_callback, (void *) &ecmult_context, 2 * n_sigs)
&& secp256k1_gej_is_infinity(&rj);
}

#endif
Loading

0 comments on commit 03a61e3

Please sign in to comment.