Skip to content

Commit 5d768de

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 81a745c commit 5d768de

File tree

3 files changed

+270
-56
lines changed

3 files changed

+270
-56
lines changed

include/secp256k1_schnorrsig.h

+35-9
Original file line numberDiff line numberDiff line change
@@ -125,26 +125,36 @@ SECP256K1_API_VAR const secp256k1_nonce_function_hardened secp256k1_nonce_functi
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

@@ -231,6 +241,22 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
231241
const secp256k1_xonly_pubkey *pubkey
232242
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
233243

244+
/** Verify a sign-to-contract commitment.
245+
*
246+
* Returns: 1: the signature contains a commitment to data32
247+
* 0: incorrect opening
248+
* Args: ctx: a secp256k1 context object, initialized for verification.
249+
* In: sig64: the signature containing the sign-to-contract commitment (cannot be NULL)
250+
* data32: the 32-byte data that was committed to (cannot be NULL)
251+
* opening: pointer to the opening created during signing (cannot be NULL)
252+
*/
253+
SECP256K1_API int secp256k1_schnorrsig_verify_s2c_commit(
254+
const secp256k1_context* ctx,
255+
const unsigned char *sig64,
256+
const unsigned char *data32,
257+
const secp256k1_schnorrsig_s2c_opening *opening
258+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
259+
234260
#ifdef __cplusplus
235261
}
236262
#endif

src/modules/schnorrsig/main_impl.h

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

1414
static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL;
1515

16+
static const unsigned char s2c_data_tag[16] = "s2c/schnorr/data";
17+
static const unsigned char s2c_point_tag[17] = "s2c/schnorr/point";
18+
1619
static void secp256k1_schnorrsig_s2c_opening_init(secp256k1_schnorrsig_s2c_opening *opening) {
1720
opening->magic = s2c_opening_magic;
1821
opening->nonce_is_negated = 0;
@@ -183,23 +186,31 @@ static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned c
183186
secp256k1_scalar_set_b32(e, buf, NULL);
184187
}
185188

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) {
189+
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) {
187190
secp256k1_scalar sk;
188191
secp256k1_scalar e;
189192
secp256k1_scalar k;
190193
secp256k1_gej rj;
191194
secp256k1_ge pk;
192195
secp256k1_ge r;
196+
secp256k1_sha256 sha;
193197
unsigned char buf[32] = { 0 };
194198
unsigned char pk_buf[32];
195199
unsigned char seckey[32];
200+
unsigned char noncedata[32];
196201
int ret = 1;
197202

198203
VERIFY_CHECK(ctx != NULL);
199204
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
200205
ARG_CHECK(sig64 != NULL);
201206
ARG_CHECK(msg != NULL || msglen == 0);
202207
ARG_CHECK(keypair != NULL);
208+
/* sign-to-contract commitments only work with the default nonce function,
209+
* because we need to ensure that s2c_data is actually hashed into the nonce and
210+
* not just ignored because otherwise this could result in nonce reuse. */
211+
ARG_CHECK(s2c_data32 == NULL || (noncefp == NULL || noncefp == secp256k1_nonce_function_bip340));
212+
/* s2c_opening and s2c_data32 should be either both non-NULL or both NULL. */
213+
ARG_CHECK((s2c_opening != NULL) == (s2c_data32 != NULL));
203214

204215
if (noncefp == NULL) {
205216
noncefp = secp256k1_nonce_function_bip340;
@@ -215,6 +226,25 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
215226

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

256+
if (s2c_opening != NULL) {
257+
secp256k1_schnorrsig_s2c_opening_init(s2c_opening);
258+
if (s2c_data32 != NULL) {
259+
secp256k1_sha256_initialize_tagged(&sha, s2c_point_tag, sizeof(s2c_point_tag));
260+
/* Create sign-to-contract commitment */
261+
secp256k1_pubkey_save(&s2c_opening->original_pubnonce, &r);
262+
secp256k1_ec_commit_seckey(&k, &r, &sha, s2c_data32, 32);
263+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k);
264+
secp256k1_ge_set_gej(&r, &rj);
265+
}
266+
}
267+
226268
/* We declassify r to allow using it as a branch point. This is fine
227269
* because r is not a secret. */
228270
secp256k1_declassify(ctx, &r, sizeof(r));
229271
secp256k1_fe_normalize_var(&r.y);
230272
if (secp256k1_fe_is_odd(&r.y)) {
231273
secp256k1_scalar_negate(&k, &k);
274+
if (s2c_opening != NULL) {
275+
s2c_opening->nonce_is_negated = 1;
276+
}
232277
}
233278
secp256k1_fe_normalize_var(&r.x);
234279
secp256k1_fe_get_b32(&sig64[0], &r.x);
@@ -248,7 +293,7 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
248293

249294
int secp256k1_schnorrsig_sign32(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
250295
/* 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);
296+
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32, NULL, NULL);
252297
}
253298

254299
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 +303,8 @@ int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64
258303
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) {
259304
secp256k1_nonce_function_hardened noncefp = NULL;
260305
void *ndata = NULL;
306+
secp256k1_schnorrsig_s2c_opening *s2c_opening = NULL;
307+
const unsigned char *s2c_data32 = NULL;
261308
VERIFY_CHECK(ctx != NULL);
262309

263310
if (extraparams != NULL) {
@@ -266,8 +313,10 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
266313
sizeof(extraparams->magic)) == 0);
267314
noncefp = extraparams->noncefp;
268315
ndata = extraparams->ndata;
316+
s2c_opening = extraparams->s2c_opening;
317+
s2c_data32 = extraparams->s2c_data32;
269318
}
270-
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata);
319+
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata, s2c_opening, s2c_data32);
271320
}
272321

273322
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 +367,34 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
318367
secp256k1_fe_equal_var(&rx, &r.x);
319368
}
320369

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

0 commit comments

Comments
 (0)