Skip to content

Commit 15753e7

Browse files
committed
schnorrsig: add batch_verify
1 parent 9bc43c3 commit 15753e7

File tree

4 files changed

+312
-25
lines changed

4 files changed

+312
-25
lines changed

include/secp256k1_schnorrsig.h

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

93+
/** Verifies a set of Schnorr signatures.
94+
*
95+
* Returns 1 if all succeeded, 0 otherwise. In particular, returns 1 if n_sigs is 0.
96+
*
97+
* Args: ctx: a secp256k1 context object, initialized for verification.
98+
* scratch: scratch space used for the multiexponentiation
99+
* In: sig: array of pointers to signatures, or NULL if there are no signatures
100+
* msg32: array of pointers to messages, or NULL if there are no signatures
101+
* pk: array of pointers to x-only public keys, or NULL if there are no signatures
102+
* n_sigs: number of signatures in above arrays. Must be below the
103+
* minimum of 2^31 and SIZE_MAX/2. Must be 0 if above arrays are NULL.
104+
*/
105+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify_batch(
106+
const secp256k1_context* ctx,
107+
secp256k1_scratch_space *scratch,
108+
const unsigned char *const *sig,
109+
const unsigned char *const *msg32,
110+
const secp256k1_xonly_pubkey *const *pk,
111+
size_t n_sigs
112+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
113+
93114
#ifdef __cplusplus
94115
}
95116
#endif

src/bench_schnorrsig.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ void bench_schnorrsig_verify(void* arg, int iters) {
4848
}
4949
}
5050

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+
5171
int main(void) {
5272
int i;
5373
bench_schnorrsig_data data;
@@ -87,6 +107,15 @@ int main(void) {
87107

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

91120
for (i = 0; i < iters; i++) {
92121
free((void *)data.keypairs[i]);

src/modules/schnorrsig/main_impl.h

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,184 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
231231
&& secp256k1_gej_eq_x_var(&rx, &rj);
232232
}
233233

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

0 commit comments

Comments
 (0)