Skip to content

Commit 03a61e3

Browse files
committed
schnorrsig: add batch_verify
1 parent 7936de8 commit 03a61e3

File tree

4 files changed

+316
-24
lines changed

4 files changed

+316
-24
lines changed

include/secp256k1_schnorrsig.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,27 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
104104
const secp256k1_xonly_pubkey *pubkey
105105
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
106106

107+
/** Verifies a set of Schnorr signatures.
108+
*
109+
* Returns 1 if all succeeded, 0 otherwise. In particular, returns 1 if n_sigs is 0.
110+
*
111+
* Args: ctx: a secp256k1 context object, initialized for verification.
112+
* scratch: scratch space used for the multiexponentiation
113+
* In: sig: array of pointers to signatures, or NULL if there are no signatures
114+
* msg32: array of pointers to messages, or NULL if there are no signatures
115+
* pk: array of pointers to x-only public keys, or NULL if there are no signatures
116+
* n_sigs: number of signatures in above arrays. Must be below the
117+
* minimum of 2^31 and SIZE_MAX/2. Must be 0 if above arrays are NULL.
118+
*/
119+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify_batch(
120+
const secp256k1_context* ctx,
121+
secp256k1_scratch_space *scratch,
122+
const unsigned char *const *sig,
123+
const unsigned char *const *msg32,
124+
const secp256k1_xonly_pubkey *const *pk,
125+
size_t n_sigs
126+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
127+
107128
#ifdef __cplusplus
108129
}
109130
#endif

src/bench_schnorrsig.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
typedef struct {
1717
secp256k1_context *ctx;
18+
secp256k1_scratch_space *scratch;
1819
int n;
1920

2021
const secp256k1_keypair **keypairs;
@@ -47,12 +48,35 @@ void bench_schnorrsig_verify(void* arg, int iters) {
4748
}
4849
}
4950

51+
void bench_schnorrsig_verify_n(void* arg, int iters) {
52+
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
53+
int i, j;
54+
const secp256k1_xonly_pubkey **pk = (const secp256k1_xonly_pubkey **)malloc(data->n * sizeof(*pk));
55+
56+
CHECK(pk != NULL);
57+
for (j = 0; j < iters/data->n; j++) {
58+
for (i = 0; i < data->n; i++) {
59+
secp256k1_xonly_pubkey *pk_nonconst = (secp256k1_xonly_pubkey *)malloc(sizeof(*pk_nonconst));
60+
CHECK(secp256k1_xonly_pubkey_parse(data->ctx, pk_nonconst, data->pk[i]) == 1);
61+
pk[i] = pk_nonconst;
62+
}
63+
CHECK(secp256k1_schnorrsig_verify_batch(data->ctx, data->scratch, data->sigs, data->msgs, pk, data->n));
64+
for (i = 0; i < data->n; i++) {
65+
free((void *)pk[i]);
66+
}
67+
}
68+
free(pk);
69+
}
70+
5071
int main(void) {
5172
int i;
5273
bench_schnorrsig_data data;
5374
int iters = get_iters(10000);
5475

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

86110
run_benchmark("schnorrsig_sign", bench_schnorrsig_sign, NULL, NULL, (void *) &data, 10, iters);
87111
run_benchmark("schnorrsig_verify", bench_schnorrsig_verify, NULL, NULL, (void *) &data, 10, iters);
112+
for (i = 1; i <= iters; i *= 2) {
113+
char name[64];
114+
int divisible_iters;
115+
sprintf(name, "schnorrsig_batch_verify_%d", (int) i);
116+
117+
data.n = i;
118+
divisible_iters = iters - (iters % data.n);
119+
run_benchmark(name, bench_schnorrsig_verify_n, NULL, NULL, (void *) &data, 3, divisible_iters);
120+
}
88121

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

133+
secp256k1_scratch_space_destroy(data.ctx, data.scratch);
100134
secp256k1_context_destroy(data.ctx);
101135
return 0;
102136
}

src/modules/schnorrsig/main_impl.h

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,188 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
235235
secp256k1_fe_equal_var(&rx, &r.x);
236236
}
237237

238+
/* Data that is used by the batch verification ecmult callback */
239+
typedef struct {
240+
const secp256k1_context *ctx;
241+
/* Seed for the random number generator */
242+
unsigned char chacha_seed[32];
243+
/* Caches randomizers generated by the PRNG which returns two randomizers per call. Caching
244+
* avoids having to call the PRNG twice as often. The very first randomizer will be set to 1 and
245+
* the PRNG is called at every odd indexed schnorrsig to fill the cache. */
246+
secp256k1_scalar randomizer_cache[2];
247+
/* Signature, message, public key tuples to verify */
248+
const unsigned char *const *sig;
249+
const unsigned char *const *msg32;
250+
const secp256k1_xonly_pubkey *const *pk;
251+
size_t n_sigs;
252+
} secp256k1_schnorrsig_verify_ecmult_context;
253+
254+
/* Callback function which is called by ecmult_multi in order to convert the ecmult_context
255+
* consisting of signature, message and public key tuples into scalars and points. */
256+
static int secp256k1_schnorrsig_verify_batch_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) {
257+
secp256k1_schnorrsig_verify_ecmult_context *ecmult_context = (secp256k1_schnorrsig_verify_ecmult_context *) data;
258+
259+
if (idx % 4 == 2) {
260+
/* Every idx corresponds to a (scalar,point)-tuple. So this callback is called with 4
261+
* consecutive tuples before we need to call the RNG for new randomizers:
262+
* (-randomizer_cache[0], R1)
263+
* (-randomizer_cache[0]*e1, P1)
264+
* (-randomizer_cache[1], R2)
265+
* (-randomizer_cache[1]*e2, P2) */
266+
secp256k1_scalar_chacha20(&ecmult_context->randomizer_cache[0], &ecmult_context->randomizer_cache[1], ecmult_context->chacha_seed, idx / 4);
267+
}
268+
269+
/* R */
270+
if (idx % 2 == 0) {
271+
secp256k1_fe rx;
272+
*sc = ecmult_context->randomizer_cache[(idx / 2) % 2];
273+
if (!secp256k1_fe_set_b32(&rx, &ecmult_context->sig[idx / 2][0])) {
274+
return 0;
275+
}
276+
if (!secp256k1_ge_set_xo_var(pt, &rx, 0)) {
277+
return 0;
278+
}
279+
/* eP */
280+
} else {
281+
unsigned char buf[32];
282+
secp256k1_sha256 sha;
283+
284+
/* xonly_pubkey_load is guaranteed not to fail because
285+
* verify_batch_init_randomizer calls secp256k1_ec_pubkey_serialize
286+
* which only works if loading the pubkey into a group element
287+
* succeeds.*/
288+
VERIFY_CHECK(secp256k1_xonly_pubkey_load(ecmult_context->ctx, pt, ecmult_context->pk[idx / 2]));
289+
290+
secp256k1_schnorrsig_sha256_tagged(&sha);
291+
secp256k1_sha256_write(&sha, &ecmult_context->sig[idx / 2][0], 32);
292+
secp256k1_fe_get_b32(buf, &pt->x);
293+
secp256k1_sha256_write(&sha, buf, sizeof(buf));
294+
secp256k1_sha256_write(&sha, ecmult_context->msg32[idx / 2], 32);
295+
secp256k1_sha256_finalize(&sha, buf);
296+
297+
secp256k1_scalar_set_b32(sc, buf, NULL);
298+
secp256k1_scalar_mul(sc, sc, &ecmult_context->randomizer_cache[(idx / 2) % 2]);
299+
}
300+
return 1;
301+
}
302+
303+
/** Helper function for batch verification. Hashes signature verification data into the
304+
* randomization seed and initializes ecmult_context.
305+
*
306+
* Returns 1 if the randomizer was successfully initialized.
307+
*
308+
* Args: ctx: a secp256k1 context object
309+
* Out: ecmult_context: context for batch_ecmult_callback
310+
* In/Out sha: an initialized sha256 object which hashes the schnorrsig input in order to get a
311+
* seed for the randomizer PRNG
312+
* In: sig: array of signatures, or NULL if there are no signatures
313+
* msg32: array of messages, or NULL if there are no signatures
314+
* pk: array of public keys, or NULL if there are no signatures
315+
* n_sigs: number of signatures in above arrays (must be 0 if they are NULL)
316+
*/
317+
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) {
318+
size_t i;
319+
320+
if (n_sigs > 0) {
321+
ARG_CHECK(sig != NULL);
322+
ARG_CHECK(msg32 != NULL);
323+
ARG_CHECK(pk != NULL);
324+
}
325+
326+
for (i = 0; i < n_sigs; i++) {
327+
unsigned char buf[33];
328+
size_t buflen = sizeof(buf);
329+
secp256k1_sha256_write(sha, sig[i], 64);
330+
secp256k1_sha256_write(sha, msg32[i], 32);
331+
/* We use compressed serialization here. If we would use
332+
* xonly_pubkey serialization and a user would wrongly memcpy
333+
* normal secp256k1_pubkeys into xonly_pubkeys then the randomizer
334+
* would be the same for two different pubkeys. */
335+
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &buflen, (const secp256k1_pubkey *) pk[i], SECP256K1_EC_COMPRESSED)) {
336+
return 0;
337+
}
338+
secp256k1_sha256_write(sha, buf, buflen);
339+
}
340+
ecmult_context->ctx = ctx;
341+
ecmult_context->sig = sig;
342+
ecmult_context->msg32 = msg32;
343+
ecmult_context->pk = pk;
344+
ecmult_context->n_sigs = n_sigs;
345+
346+
return 1;
347+
}
348+
349+
/** Helper function for batch verification. Sums the s part of all signatures multiplied by their
350+
* randomizer.
351+
*
352+
* Returns 1 if s is successfully summed.
353+
*
354+
* In/Out: s: the s part of the input sigs is added to this s argument
355+
* In: chacha_seed: PRNG seed for computing randomizers
356+
* sig: array of signatures, or NULL if there are no signatures
357+
* n_sigs: number of signatures in above array (must be 0 if they are NULL)
358+
*/
359+
static int secp256k1_schnorrsig_verify_batch_sum_s(secp256k1_scalar *s, unsigned char *chacha_seed, const unsigned char *const *sig, size_t n_sigs) {
360+
secp256k1_scalar randomizer_cache[2];
361+
size_t i;
362+
363+
secp256k1_scalar_set_int(&randomizer_cache[0], 1);
364+
for (i = 0; i < n_sigs; i++) {
365+
int overflow;
366+
secp256k1_scalar term;
367+
if (i % 2 == 1) {
368+
secp256k1_scalar_chacha20(&randomizer_cache[0], &randomizer_cache[1], chacha_seed, i / 2);
369+
}
370+
371+
secp256k1_scalar_set_b32(&term, &sig[i][32], &overflow);
372+
if (overflow) {
373+
return 0;
374+
}
375+
secp256k1_scalar_mul(&term, &term, &randomizer_cache[i % 2]);
376+
secp256k1_scalar_add(s, s, &term);
377+
}
378+
return 1;
379+
}
380+
381+
/* schnorrsig batch verification.
382+
*
383+
* Seeds a random number generator with the inputs and derives a random number
384+
* ai for every signature i. Fails if
385+
*
386+
* 0 != -(s1 + a2*s2 + ... + au*su)G
387+
* + R1 + a2*R2 + ... + au*Ru + e1*P1 + (a2*e2)P2 + ... + (au*eu)Pu.
388+
*/
389+
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) {
390+
secp256k1_schnorrsig_verify_ecmult_context ecmult_context;
391+
secp256k1_sha256 sha;
392+
secp256k1_scalar s;
393+
secp256k1_gej rj;
394+
395+
VERIFY_CHECK(ctx != NULL);
396+
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
397+
ARG_CHECK(scratch != NULL);
398+
/* Check that n_sigs is less than half of the maximum size_t value. This is necessary because
399+
* the number of points given to ecmult_multi is 2*n_sigs. */
400+
ARG_CHECK(n_sigs <= SIZE_MAX / 2);
401+
/* Check that n_sigs is less than 2^31 to ensure the same behavior of this function on 32-bit
402+
* and 64-bit platforms. */
403+
ARG_CHECK(n_sigs < ((uint32_t)1 << 31));
404+
405+
secp256k1_sha256_initialize(&sha);
406+
if (!secp256k1_schnorrsig_verify_batch_init_randomizer(ctx, &ecmult_context, &sha, sig, msg32, pk, n_sigs)) {
407+
return 0;
408+
}
409+
secp256k1_sha256_finalize(&sha, ecmult_context.chacha_seed);
410+
secp256k1_scalar_set_int(&ecmult_context.randomizer_cache[0], 1);
411+
412+
secp256k1_scalar_clear(&s);
413+
if (!secp256k1_schnorrsig_verify_batch_sum_s(&s, ecmult_context.chacha_seed, sig, n_sigs)) {
414+
return 0;
415+
}
416+
secp256k1_scalar_negate(&s, &s);
417+
418+
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)
419+
&& secp256k1_gej_is_infinity(&rj);
420+
}
421+
238422
#endif

0 commit comments

Comments
 (0)