Skip to content

Commit 896f6db

Browse files
benmajonasnick
andcommitted
allow creating and verifying Schnorr sign-to-contract commitments
Adapted from #589. The data is hashed using a tagged hash with the "s2c/schnorr/data" tag, which is consistent with the data hashing done in the ECDSA version of sign-to-contract (in ElementsProject/secp256k1-zkp), where the "s2c/ecdsa/data" tag is used. Similarly, the tweak hash tag is "s2c/schnorr/point". Co-authored-by: Jonas Nick <[email protected]>
1 parent 579366d commit 896f6db

File tree

3 files changed

+264
-60
lines changed

3 files changed

+264
-60
lines changed

include/secp256k1_schnorrsig.h

+35-9
Original file line numberDiff line numberDiff line change
@@ -125,26 +125,36 @@ SECP256K1_API const secp256k1_nonce_function_hardened secp256k1_nonce_function_b
125125
* setting it to SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT.
126126
*
127127
* Members:
128-
* magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization
129-
* and has no other function than making sure the object is
130-
* initialized.
131-
* noncefp: pointer to a nonce generation function. If NULL,
132-
* secp256k1_nonce_function_bip340 is used
133-
* ndata: pointer to arbitrary data used by the nonce generation function
134-
* (can be NULL). If it is non-NULL and
135-
* secp256k1_nonce_function_bip340 is used, then ndata must be a
136-
* pointer to 32-byte auxiliary randomness as per BIP-340.
128+
* magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization
129+
* and has no other function than making sure the object is
130+
* initialized.
131+
* noncefp: pointer to a nonce generation function. If NULL,
132+
* secp256k1_nonce_function_bip340 is used
133+
* ndata: pointer to arbitrary data used by the nonce generation function
134+
* (can be NULL). If it is non-NULL and
135+
* secp256k1_nonce_function_bip340 is used, then ndata must be a
136+
* pointer to 32-byte auxiliary randomness as per BIP-340.
137+
* s2c_opening: pointer to an secp256k1_schnorrsig_s2c_opening structure which can be
138+
* NULL but is required to be not NULL if this signature creates
139+
* a sign-to-contract commitment (i.e. the `s2c_data32` argument
140+
* is not NULL).
141+
* s2c_data32: pointer to a 32-byte data to create an optional
142+
* sign-to-contract commitment to if not NULL (can be NULL).
137143
*/
138144
typedef struct {
139145
unsigned char magic[4];
140146
secp256k1_nonce_function_hardened noncefp;
141147
void *ndata;
148+
secp256k1_schnorrsig_s2c_opening* s2c_opening;
149+
const unsigned char* s2c_data32;
142150
} secp256k1_schnorrsig_extraparams;
143151

144152
#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC { 0xda, 0x6f, 0xb3, 0x8c }
145153
#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT {\
146154
SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC,\
147155
NULL,\
156+
NULL,\
157+
NULL,\
148158
NULL\
149159
}
150160

@@ -239,6 +249,22 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
239249
const secp256k1_xonly_pubkey *pubkey
240250
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
241251

252+
/** Verify a sign-to-contract commitment.
253+
*
254+
* Returns: 1: the signature contains a commitment to data32
255+
* 0: incorrect opening
256+
* Args: ctx: a secp256k1 context object, initialized for verification.
257+
* In: sig64: the signature containing the sign-to-contract commitment (cannot be NULL)
258+
* data32: the 32-byte data that was committed to (cannot be NULL)
259+
* opening: pointer to the opening created during signing (cannot be NULL)
260+
*/
261+
SECP256K1_API int secp256k1_schnorrsig_verify_s2c_commit(
262+
const secp256k1_context* ctx,
263+
const unsigned char *sig64,
264+
const unsigned char *data32,
265+
const secp256k1_schnorrsig_s2c_opening *opening
266+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
267+
242268
#ifdef __cplusplus
243269
}
244270
#endif

src/modules/schnorrsig/main_impl.h

+86-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313

1414
static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL;
1515

16+
static const unsigned char s2c_data_tag[16] = {
17+
's', '2', 'c', '/', 's', 'c', 'h', 'n', 'o', 'r', 'r', '/', 'd', 'a', 't', 'a'
18+
};
19+
static const unsigned char s2c_point_tag[17] = {
20+
's', '2', 'c', '/', 's', 'c', 'h', 'n', 'o', 'r', 'r', '/', 'p', 'o', 'i', 'n', 't'
21+
};
22+
1623
static void secp256k1_schnorrsig_s2c_opening_init(secp256k1_schnorrsig_s2c_opening *opening) {
1724
opening->magic = s2c_opening_magic;
1825
opening->nonce_is_negated = 0;
@@ -183,23 +190,31 @@ static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned c
183190
secp256k1_scalar_set_b32(e, buf, NULL);
184191
}
185192

186-
static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, secp256k1_nonce_function_hardened noncefp, void *ndata) {
193+
static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, secp256k1_nonce_function_hardened noncefp, void *ndata, secp256k1_schnorrsig_s2c_opening *s2c_opening, const unsigned char *s2c_data32) {
187194
secp256k1_scalar sk;
188195
secp256k1_scalar e;
189196
secp256k1_scalar k;
190197
secp256k1_gej rj;
191198
secp256k1_ge pk;
192199
secp256k1_ge r;
200+
secp256k1_sha256 sha;
193201
unsigned char buf[32] = { 0 };
194202
unsigned char pk_buf[32];
195203
unsigned char seckey[32];
204+
unsigned char noncedata[32];
196205
int ret = 1;
197206

198207
VERIFY_CHECK(ctx != NULL);
199208
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
200209
ARG_CHECK(sig64 != NULL);
201210
ARG_CHECK(msg != NULL || msglen == 0);
202211
ARG_CHECK(keypair != NULL);
212+
/* sign-to-contract commitments only work with the default nonce function,
213+
* because we need to ensure that s2c_data is actually hashed into the nonce and
214+
* not just ignored because otherwise this could result in nonce reuse. */
215+
ARG_CHECK(s2c_data32 == NULL || (noncefp == NULL || noncefp == secp256k1_nonce_function_bip340));
216+
/* s2c_opening and s2c_data32 should be either both non-NULL or both NULL. */
217+
ARG_CHECK((s2c_opening != NULL) == (s2c_data32 != NULL));
203218

204219
if (noncefp == NULL) {
205220
noncefp = secp256k1_nonce_function_bip340;
@@ -215,6 +230,25 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
215230

216231
secp256k1_scalar_get_b32(seckey, &sk);
217232
secp256k1_fe_get_b32(pk_buf, &pk.x);
233+
234+
if (s2c_data32 != NULL) {
235+
/* Provide s2c_data32 and ndata (if not NULL) to the the nonce function
236+
* as additional data to derive the nonce from. If both pointers are
237+
* not NULL, they need to be hashed to get the nonce data 32 bytes.
238+
* Even if only s2c_data32 is not NULL, it's hashed because it should
239+
* be possible to derive nonces even if only a SHA256 commitment to the
240+
* data is known. This is for example important in the anti nonce
241+
* sidechannel protocol.
242+
*/
243+
secp256k1_sha256_initialize_tagged(&sha, s2c_data_tag, sizeof(s2c_data_tag));
244+
secp256k1_sha256_write(&sha, s2c_data32, 32);
245+
if (ndata != NULL) {
246+
secp256k1_sha256_write(&sha, ndata, 32);
247+
}
248+
secp256k1_sha256_finalize(&sha, noncedata);
249+
ndata = &noncedata;
250+
}
251+
218252
ret &= !!noncefp(buf, msg, msglen, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), ndata);
219253
secp256k1_scalar_set_b32(&k, buf, NULL);
220254
ret &= !secp256k1_scalar_is_zero(&k);
@@ -223,12 +257,27 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
223257
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k);
224258
secp256k1_ge_set_gej(&r, &rj);
225259

260+
if (s2c_opening != NULL) {
261+
secp256k1_schnorrsig_s2c_opening_init(s2c_opening);
262+
if (s2c_data32 != NULL) {
263+
secp256k1_sha256_initialize_tagged(&sha, s2c_point_tag, sizeof(s2c_point_tag));
264+
/* Create sign-to-contract commitment */
265+
secp256k1_pubkey_save(&s2c_opening->original_pubnonce, &r);
266+
secp256k1_ec_commit_seckey(&k, &r, &sha, s2c_data32, 32);
267+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k);
268+
secp256k1_ge_set_gej(&r, &rj);
269+
}
270+
}
271+
226272
/* We declassify r to allow using it as a branch point. This is fine
227273
* because r is not a secret. */
228274
secp256k1_declassify(ctx, &r, sizeof(r));
229275
secp256k1_fe_normalize_var(&r.y);
230276
if (secp256k1_fe_is_odd(&r.y)) {
231277
secp256k1_scalar_negate(&k, &k);
278+
if (s2c_opening != NULL) {
279+
s2c_opening->nonce_is_negated = 1;
280+
}
232281
}
233282
secp256k1_fe_normalize_var(&r.x);
234283
secp256k1_fe_get_b32(&sig64[0], &r.x);
@@ -248,7 +297,7 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
248297

249298
int secp256k1_schnorrsig_sign32(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
250299
/* We cast away const from the passed aux_rand32 argument since we know the default nonce function does not modify it. */
251-
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32);
300+
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32, NULL, NULL);
252301
}
253302

254303
int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
@@ -258,6 +307,8 @@ int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64
258307
int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, secp256k1_schnorrsig_extraparams *extraparams) {
259308
secp256k1_nonce_function_hardened noncefp = NULL;
260309
void *ndata = NULL;
310+
secp256k1_schnorrsig_s2c_opening *s2c_opening = NULL;
311+
const unsigned char *s2c_data32 = NULL;
261312
VERIFY_CHECK(ctx != NULL);
262313

263314
if (extraparams != NULL) {
@@ -266,8 +317,10 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
266317
sizeof(extraparams->magic)) == 0);
267318
noncefp = extraparams->noncefp;
268319
ndata = extraparams->ndata;
320+
s2c_opening = extraparams->s2c_opening;
321+
s2c_data32 = extraparams->s2c_data32;
269322
}
270-
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata);
323+
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata, s2c_opening, s2c_data32);
271324
}
272325

273326
int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey) {
@@ -318,4 +371,34 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
318371
secp256k1_fe_equal(&rx, &r.x);
319372
}
320373

374+
int secp256k1_schnorrsig_verify_s2c_commit(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *data32, const secp256k1_schnorrsig_s2c_opening *opening) {
375+
secp256k1_fe rx;
376+
secp256k1_ge original_r;
377+
secp256k1_ge r;
378+
secp256k1_sha256 sha;
379+
380+
VERIFY_CHECK(ctx != NULL);
381+
ARG_CHECK(sig64 != NULL);
382+
ARG_CHECK(data32 != NULL);
383+
ARG_CHECK(opening != NULL);
384+
ARG_CHECK(secp256k1_schnorrsig_s2c_commit_is_init(opening));
385+
386+
if (!secp256k1_fe_set_b32_limit(&rx, &sig64[0])) {
387+
return 0;
388+
}
389+
if (!secp256k1_ge_set_xo_var(&r, &rx, 0)) {
390+
return 0;
391+
}
392+
if (opening->nonce_is_negated) {
393+
secp256k1_ge_neg(&r, &r);
394+
}
395+
396+
if (!secp256k1_pubkey_load(ctx, &original_r, &opening->original_pubnonce)) {
397+
return 0;
398+
}
399+
400+
secp256k1_sha256_initialize_tagged(&sha, s2c_point_tag, sizeof(s2c_point_tag));
401+
return secp256k1_ec_commit_verify(&r, &original_r, &sha, data32, 32);
402+
}
403+
321404
#endif

0 commit comments

Comments
 (0)