Skip to content

Commit f818fc0

Browse files
committed
batch: Add batch_add_* APIs
This commit adds the batch APIs: 1. batch_add_schnorrsig Adds a Schnorr signature to the batch. 2. batch_add_xonlypub_tweak_check Adds a tweaked x-only pubkey check to the batch. 3. batch_usable Checks if a batch can be used by _batch_add_* APIs. **Side Note:** Exposing batch_add_schnorrsig in the secp256k1_schnorrsig.h header file (with batch module header guards) will force the user to define ENABLE_MODULE_BATCH during their code compilation. Hence, it is in a standalone secp256k1_schnorrsig_batch.h header file. A similar argument could be made for batch_add_xonlypub_tweak_check.
1 parent b47d8f3 commit f818fc0

File tree

10 files changed

+465
-0
lines changed

10 files changed

+465
-0
lines changed

include/secp256k1_batch.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@ SECP256K1_API void secp256k1_batch_destroy(
6363
secp256k1_batch* batch
6464
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
6565

66+
/** Checks if a batch can be used by the `secp256k1_batch_add_*` APIs.
67+
*
68+
* Returns: 1: batch can be used by `secp256k1_batch_add_*` APIs.
69+
* 0: batch cannot be used by `secp256k1_batch_add_*` APIs.
70+
*
71+
* Args: ctx: a secp256k1 context object (can be initialized for none).
72+
* batch: a secp256k1 batch object that contains a set of schnorrsigs/tweaks.
73+
*
74+
* You are advised to check if `secp256k1_batch_usable` returns 1 before calling
75+
* any `secp256k1_batch_add_*` API. We recommend this because `secp256k1_batch_add_*`
76+
* will fail in two cases:
77+
* - case 1: unparsable input (schnorrsig or tweak check)
78+
* - case 2: unusable (or invalid) batch
79+
* Calling `secp256k1_batch_usable` beforehand helps eliminate case 2 if
80+
* `secp256k1_batch_add_*` fails.
81+
*
82+
* If you ignore the above advice, all the secp256k1_batch APIs will still
83+
* work correctly. It simply makes it hard to understand the reason behind
84+
* `secp256k1_batch_add_*` failure (if occurs).
85+
*/
86+
SECP256K1_API int secp256k1_batch_usable(
87+
const secp256k1_context *ctx,
88+
const secp256k1_batch *batch
89+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
90+
6691
/** Verify the set of schnorr signatures or tweaked pubkeys present in the secp256k1_batch.
6792
*
6893
* Returns: 1: every schnorrsig/tweak (in batch) is valid

include/secp256k1_schnorrsig_batch.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#ifndef SECP256K1_SCHNORRSIG_BATCH_H
2+
#define SECP256K1_SCHNORRSIG_BATCH_H
3+
4+
#include "secp256k1.h"
5+
#include "secp256k1_schnorrsig.h"
6+
#include "secp256k1_batch.h"
7+
8+
#ifdef __cplusplus
9+
extern "C" {
10+
#endif
11+
12+
/** This header file implements batch verification functionality for Schnorr
13+
* signature (see include/secp256k1_schnorrsig.h).
14+
*/
15+
16+
/** Adds a Schnorr signature to the batch object (secp256k1_batch)
17+
* defined in the Batch module (see include/secp256k1_batch.h).
18+
*
19+
* Returns: 1: successfully added the signature to the batch
20+
* 0: unparseable signature or unusable batch (according to
21+
* secp256k1_batch_usable).
22+
* Args: ctx: a secp256k1 context object (can be initialized for none).
23+
* batch: a secp256k1 batch object created using `secp256k1_batch_create`.
24+
* In: sig64: pointer to the 64-byte signature to verify.
25+
* msg: the message being verified. Can only be NULL if msglen is 0.
26+
* msglen: length of the message.
27+
* pubkey: pointer to an x-only public key to verify with (cannot be NULL).
28+
*/
29+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_batch_add_schnorrsig(
30+
const secp256k1_context* ctx,
31+
secp256k1_batch *batch,
32+
const unsigned char *sig64,
33+
const unsigned char *msg,
34+
size_t msglen,
35+
const secp256k1_xonly_pubkey *pubkey
36+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(6);
37+
38+
#ifdef __cplusplus
39+
}
40+
#endif
41+
42+
#endif /* SECP256K1_SCHNORRSIG_BATCH_H */

include/secp256k1_tweak_check_batch.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#ifndef SECP256K1_TWEAK_CHECK_BATCH_H
2+
#define SECP256K1_TWEAK_CHECK_BATCH_H
3+
4+
#include "secp256k1.h"
5+
#include "secp256k1_extrakeys.h"
6+
#include "secp256k1_batch.h"
7+
8+
#ifdef __cplusplus
9+
extern "C" {
10+
#endif
11+
12+
/** This header file implements batch verification functionality for
13+
* x-only tweaked public key check (see include/secp256k1_extrakeys.h).
14+
*/
15+
16+
/** Adds a x-only tweaked pubkey check to the batch object (secp256k1_batch)
17+
* defined in the Batch module (see include/secp256k1_batch.h).
18+
*
19+
* The tweaked pubkey is represented by its 32-byte x-only serialization and
20+
* its pk_parity, which can both be obtained by converting the result of
21+
* tweak_add to a secp256k1_xonly_pubkey.
22+
*
23+
* Returns: 1: successfully added the tweaked pubkey check to the batch
24+
* 0: unparseable tweaked pubkey check or unusable batch (according to
25+
* secp256k1_batch_usable).
26+
* Args: ctx: pointer to a context object initialized for verification.
27+
* batch: a secp256k1 batch object created using `secp256k1_batch_create`.
28+
* In: tweaked_pubkey32: pointer to a serialized xonly_pubkey.
29+
* tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization
30+
* is passed in as tweaked_pubkey32). This must match the
31+
* pk_parity value that is returned when calling
32+
* secp256k1_xonly_pubkey_from_pubkey with the tweaked pubkey, or
33+
* the final secp256k1_batch_verify on this batch will fail.
34+
* internal_pubkey: pointer to an x-only public key object to apply the tweak to.
35+
* tweak32: pointer to a 32-byte tweak.
36+
*/
37+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_batch_add_xonlypub_tweak_check(
38+
const secp256k1_context* ctx,
39+
secp256k1_batch *batch,
40+
const unsigned char *tweaked_pubkey32,
41+
int tweaked_pk_parity,
42+
const secp256k1_xonly_pubkey *internal_pubkey,
43+
const unsigned char *tweak32
44+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
45+
46+
#ifdef __cplusplus
47+
}
48+
#endif
49+
50+
#endif /* SECP256K1_TWEAK_CHECK_BATCH_H */

src/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,17 @@ if(SECP256K1_ENABLE_MODULE_SCHNORRSIG)
3333
set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON)
3434
add_compile_definitions(ENABLE_MODULE_SCHNORRSIG=1)
3535
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig.h)
36+
if(SECP256K1_ENABLE_MODULE_BATCH)
37+
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig_batch.h)
38+
endif()
3639
endif()
3740

3841
if(SECP256K1_ENABLE_MODULE_EXTRAKEYS)
3942
add_compile_definitions(ENABLE_MODULE_EXTRAKEYS=1)
4043
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_extrakeys.h)
44+
if(SECP256K1_ENABLE_MODULE_BATCH)
45+
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_tweak_check_batch.h)
46+
endif()
4147
endif()
4248

4349
if(SECP256K1_ENABLE_MODULE_RECOVERY)

src/modules/batch/main_impl.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
#include "../../../include/secp256k1_batch.h"
55

6+
/* Assume two batch objects (batch1 and batch2) and we call
7+
* `batch_add_tweak_check` on batch1 and `batch_add_schnorrsig` on batch2.
8+
* In this case, the same randomizer will be generated if the input bytes to
9+
* batch1 and batch2 are the same (even though we use different `batch_add_` funcs).
10+
* Including this tag during randomizer generation (to differentiate btw
11+
* `batch_add_` funcs) will prevent such mishaps. */
12+
enum batch_add_type {schnorrsig = 1, tweak_check = 2};
13+
614
/** Opaque data structure that holds information required for the batch verification.
715
*
816
* Members:
@@ -146,6 +154,13 @@ void secp256k1_batch_destroy(const secp256k1_context *ctx, secp256k1_batch *batc
146154
}
147155
}
148156

157+
int secp256k1_batch_usable(const secp256k1_context *ctx, const secp256k1_batch *batch) {
158+
VERIFY_CHECK(ctx != NULL);
159+
ARG_CHECK(batch != NULL);
160+
161+
return batch->result;
162+
}
163+
149164
/** verifies the inputs (schnorrsig or tweak_check) by performing multi-scalar point
150165
* multiplication on the scalars (`batch->scalars`) and points (`batch->points`)
151166
* present in the batch. Uses `secp256k1_ecmult_strauss_batch_internal` to perform
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
include_HEADERS += include/secp256k1_extrakeys.h
2+
if ENABLE_MODULE_BATCH
3+
include_HEADERS += include/secp256k1_tweak_check_batch.h
4+
endif
25
noinst_HEADERS += src/modules/extrakeys/tests_impl.h
36
noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h
47
noinst_HEADERS += src/modules/extrakeys/main_impl.h
8+
if ENABLE_MODULE_BATCH
9+
noinst_HEADERS += src/modules/extrakeys/batch_add_impl.h
10+
endif
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#ifndef SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_IMPL_H
2+
#define SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_IMPL_H
3+
4+
#include "../../../include/secp256k1_extrakeys.h"
5+
#include "../../../include/secp256k1_tweak_check_batch.h"
6+
#include "../batch/main_impl.h"
7+
8+
/* The number of scalar-point pairs allocated on the scratch space
9+
* by `secp256k1_batch_add_xonlypub_tweak_check` */
10+
#define BATCH_TWEAK_CHECK_SCRATCH_OBJS 1
11+
12+
/** Computes a 16-byte deterministic randomizer by
13+
* SHA256(batch_add_tag || tweaked pubkey || parity || tweak || internal pubkey) */
14+
static void secp256k1_batch_xonlypub_tweak_randomizer_gen(unsigned char *randomizer32, secp256k1_sha256 *sha256, const unsigned char *tweaked_pubkey32, const unsigned char *tweaked_pk_parity, const unsigned char *internal_pk33, const unsigned char *tweak32) {
15+
secp256k1_sha256 sha256_cpy;
16+
unsigned char batch_add_type = (unsigned char) tweak_check;
17+
18+
secp256k1_sha256_write(sha256, &batch_add_type, sizeof(batch_add_type));
19+
/* add tweaked pubkey check data to sha object */
20+
secp256k1_sha256_write(sha256, tweaked_pubkey32, 32);
21+
secp256k1_sha256_write(sha256, tweaked_pk_parity, 1);
22+
secp256k1_sha256_write(sha256, tweak32, 32);
23+
secp256k1_sha256_write(sha256, internal_pk33, 33);
24+
25+
/* generate randomizer */
26+
sha256_cpy = *sha256;
27+
secp256k1_sha256_finalize(&sha256_cpy, randomizer32);
28+
/* 16 byte randomizer is sufficient */
29+
memset(randomizer32, 0, 16);
30+
}
31+
32+
static int secp256k1_batch_xonlypub_tweak_randomizer_set(const secp256k1_context* ctx, secp256k1_batch *batch, secp256k1_scalar *r, const unsigned char *tweaked_pubkey32, int tweaked_pk_parity, const secp256k1_xonly_pubkey *internal_pubkey,const unsigned char *tweak32) {
33+
unsigned char randomizer[32];
34+
unsigned char internal_buf[33];
35+
size_t internal_buflen = sizeof(internal_buf);
36+
unsigned char parity = (unsigned char) tweaked_pk_parity;
37+
int overflow;
38+
/* t = 2^127 */
39+
secp256k1_scalar t = SECP256K1_SCALAR_CONST(0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000);
40+
41+
/* We use compressed serialization here. If we would use
42+
* xonly_pubkey serialization and a user would wrongly memcpy
43+
* normal secp256k1_pubkeys into xonly_pubkeys then the randomizer
44+
* would be the same for two different pubkeys. */
45+
if (!secp256k1_ec_pubkey_serialize(ctx, internal_buf, &internal_buflen, (const secp256k1_pubkey *) internal_pubkey, SECP256K1_EC_COMPRESSED)) {
46+
return 0;
47+
}
48+
49+
secp256k1_batch_xonlypub_tweak_randomizer_gen(randomizer, &batch->sha256, tweaked_pubkey32, &parity, internal_buf, tweak32);
50+
secp256k1_scalar_set_b32(r, randomizer, &overflow);
51+
/* Shift scalar to range [-2^127, 2^127-1] */
52+
secp256k1_scalar_negate(&t, &t);
53+
secp256k1_scalar_add(r, r, &t);
54+
VERIFY_CHECK(overflow == 0);
55+
56+
return 1;
57+
}
58+
59+
/** Adds the given x-only tweaked public key check to the batch.
60+
*
61+
* Updates the batch object by:
62+
* 1. adding the point P-Q to the scratch space
63+
* -> the point is of type `secp256k1_gej`
64+
* 2. adding the scalar ai to the scratch space
65+
* -> ai is the scalar coefficient of P-Q (in multi multiplication)
66+
* 3. incrementing sc_g (scalar of G) by ai.tweak
67+
*
68+
* Conventions used above:
69+
* -> Q (tweaked pubkey) = EC point where parity(y) = tweaked_pk_parity
70+
* and x = tweaked_pubkey32
71+
* -> P (internal pubkey) = internal pubkey
72+
* -> ai (randomizer) = sha256_tagged(batch_add_tag || tweaked_pubkey32 ||
73+
* tweaked_pk_parity || tweak32 || pubkey)
74+
* -> tweak (challenge) = tweak32
75+
*
76+
* This function is based on `secp256k1_xonly_pubkey_tweak_add_check`.
77+
*/
78+
int secp256k1_batch_add_xonlypub_tweak_check(const secp256k1_context* ctx, secp256k1_batch *batch, const unsigned char *tweaked_pubkey32, int tweaked_pk_parity, const secp256k1_xonly_pubkey *internal_pubkey,const unsigned char *tweak32) {
79+
secp256k1_scalar tweak;
80+
secp256k1_scalar ai;
81+
secp256k1_ge pk;
82+
secp256k1_ge q;
83+
secp256k1_gej tmpj;
84+
secp256k1_fe qx;
85+
int overflow;
86+
size_t i;
87+
88+
VERIFY_CHECK(ctx != NULL);
89+
ARG_CHECK(batch != NULL);
90+
ARG_CHECK(internal_pubkey != NULL);
91+
ARG_CHECK(tweaked_pubkey32 != NULL);
92+
ARG_CHECK(tweak32 != NULL);
93+
94+
if(batch->result == 0) {
95+
return 0;
96+
}
97+
98+
if (!secp256k1_fe_set_b32_limit(&qx, tweaked_pubkey32)) {
99+
return 0;
100+
}
101+
102+
secp256k1_scalar_set_b32(&tweak, tweak32, &overflow);
103+
if (overflow) {
104+
return 0;
105+
}
106+
107+
if (!secp256k1_xonly_pubkey_load(ctx, &pk, internal_pubkey)) {
108+
return 0;
109+
}
110+
111+
/* if insufficient space in batch, verify the inputs (stored in curr batch) and
112+
* save the result. This extends the batch capacity since `secp256k1_batch_verify`
113+
* clears the batch after verification. */
114+
if (batch->capacity - batch->len < BATCH_TWEAK_CHECK_SCRATCH_OBJS) {
115+
secp256k1_batch_verify(ctx, batch);
116+
}
117+
118+
i = batch->len;
119+
/* append point P-Q to the scratch space */
120+
if (!secp256k1_ge_set_xo_var(&q, &qx, tweaked_pk_parity)) {
121+
return 0;
122+
}
123+
if (!secp256k1_ge_is_in_correct_subgroup(&q)) {
124+
return 0;
125+
}
126+
secp256k1_ge_neg(&q, &q);
127+
secp256k1_gej_set_ge(&tmpj, &q);
128+
secp256k1_gej_add_ge_var(&tmpj, &tmpj, &pk, NULL);
129+
batch->points[i] = tmpj;
130+
131+
/* Compute ai (randomizer) */
132+
if (batch->len == 0) {
133+
/* set randomizer as 1 for the first term in batch */
134+
ai = secp256k1_scalar_one;
135+
} else if(!secp256k1_batch_xonlypub_tweak_randomizer_set(ctx, batch, &ai, tweaked_pubkey32, tweaked_pk_parity, internal_pubkey, tweak32)) {
136+
return 0;
137+
}
138+
139+
/* append scalar ai to scratch space */
140+
batch->scalars[i] = ai;
141+
142+
/* increment scalar of G by ai.tweak */
143+
secp256k1_scalar_mul(&tweak, &tweak, &ai);
144+
secp256k1_scalar_add(&batch->sc_g, &batch->sc_g, &tweak);
145+
146+
batch->len += 1;
147+
148+
return 1;
149+
}
150+
151+
#endif /* SECP256K1_MODULE_EXTRAKEYS_BATCH_ADD_IMPL_H */
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
include_HEADERS += include/secp256k1_schnorrsig.h
2+
if ENABLE_MODULE_BATCH
3+
include_HEADERS += include/secp256k1_schnorrsig_batch.h
4+
endif
25
noinst_HEADERS += src/modules/schnorrsig/main_impl.h
36
noinst_HEADERS += src/modules/schnorrsig/tests_impl.h
47
noinst_HEADERS += src/modules/schnorrsig/tests_exhaustive_impl.h
58
noinst_HEADERS += src/modules/schnorrsig/bench_impl.h
9+
if ENABLE_MODULE_BATCH
10+
noinst_HEADERS += src/modules/schnorrsig/batch_add_impl.h
11+
endif

0 commit comments

Comments
 (0)