Skip to content

Commit ec50168

Browse files
committed
schnorrsig: add batch_verify
1 parent cb02d22 commit ec50168

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
@@ -103,6 +103,27 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
103103
const secp256k1_xonly_pubkey *pubkey
104104
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
105105

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

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

0 commit comments

Comments
 (0)