Skip to content

Commit a0c3fc1

Browse files
committed
schnorrsig: allow signing and verification of variable length msgs
Varlen message support for the default sign function comes from recommending tagged_sha256. sign_custom on the other hand gets the ability to directly sign message of any length. This also implies signing and verification support for the empty message (NULL) with msglen 0. Tests for variable lengths follow in a later commit.
1 parent 5a8e499 commit a0c3fc1

File tree

5 files changed

+111
-81
lines changed

5 files changed

+111
-81
lines changed

include/secp256k1_schnorrsig.h

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ extern "C" {
2424
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to
2525
* return an error.
2626
* Out: nonce32: pointer to a 32-byte array to be filled by the function
27-
* In: msg32: the 32-byte message hash being verified (will not be NULL)
27+
* In: msg: the message being verified. Is NULL if and only if msglen
28+
* is 0.
29+
* msglen: the length of the message
2830
* key32: pointer to a 32-byte secret key (will not be NULL)
2931
* xonly_pk32: the 32-byte serialized xonly pubkey corresponding to key32
3032
* (will not be NULL)
@@ -38,7 +40,8 @@ extern "C" {
3840
*/
3941
typedef int (*secp256k1_nonce_function_hardened)(
4042
unsigned char *nonce32,
41-
const unsigned char *msg32,
43+
const unsigned char *msg,
44+
size_t msglen,
4245
const unsigned char *key32,
4346
const unsigned char *xonly_pk32,
4447
const unsigned char *algo,
@@ -66,6 +69,13 @@ SECP256K1_API extern const secp256k1_nonce_function_hardened secp256k1_nonce_fun
6669
* signature. Instead, you can manually use secp256k1_schnorrsig_verify and
6770
* abort if it fails.
6871
*
72+
* This function only signs 32-byte messages. If you have messages of a
73+
* different size (or the same size but without a context-specific tag
74+
* prefix), it is recommended to create a 32-byte message hash with
75+
* secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows
76+
* providing an context-specific tag for domain separation. This prevents
77+
* signatures from being valid in multiple contexts by accident.
78+
*
6979
* Returns 1 on success, 0 on failure.
7080
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
7181
* Out: sig64: pointer to a 64-byte array to store the serialized signature (cannot be NULL)
@@ -86,12 +96,14 @@ SECP256K1_API int secp256k1_schnorrsig_sign(
8696

8797
/** Create a Schnorr signature with a more flexible API.
8898
*
89-
* Same arguments as secp256k1_schnorrsig_sign except that it misses aux_rand32
90-
* and instead allows allows providing a different nonce derivation function
91-
* with its own data argument.
99+
* Same arguments as secp256k1_schnorrsig_sign except that it allows signing
100+
* variable length messages and allows providing a different nonce derivation
101+
* function with its own data argument.
92102
*
93-
* In: noncefp: pointer to a nonce generation function. If NULL,
94-
* secp256k1_nonce_function_bip340 is used
103+
* In: msg: the message being signed. Can only be NULL if msglen is 0.
104+
* msglen: length of the message
105+
* noncefp: pointer to a nonce generation function. If NULL,
106+
* secp256k1_nonce_function_bip340 is used.
95107
* ndata: pointer to arbitrary data used by the nonce generation function
96108
* (can be NULL). If it is non-NULL and
97109
* secp256k1_nonce_function_bip340 is used, then ndata must be a
@@ -100,27 +112,30 @@ SECP256K1_API int secp256k1_schnorrsig_sign(
100112
SECP256K1_API int secp256k1_schnorrsig_sign_custom(
101113
const secp256k1_context* ctx,
102114
unsigned char *sig64,
103-
const unsigned char *msg32,
115+
const unsigned char *msg,
116+
size_t msglen,
104117
const secp256k1_keypair *keypair,
105118
secp256k1_nonce_function_hardened noncefp,
106119
void *ndata
107-
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
120+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
108121

109122
/** Verify a Schnorr signature.
110123
*
111124
* Returns: 1: correct signature
112125
* 0: incorrect signature
113126
* Args: ctx: a secp256k1 context object, initialized for verification.
114127
* In: sig64: pointer to the 64-byte signature to verify (cannot be NULL)
115-
* msg32: the 32-byte message being verified (cannot be NULL)
128+
* msg: the message being verified. Can only be NULL if msglen is 0.
129+
* msglen: length of the message
116130
* pubkey: pointer to an x-only public key to verify with (cannot be NULL)
117131
*/
118132
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
119133
const secp256k1_context* ctx,
120134
const unsigned char *sig64,
121-
const unsigned char *msg32,
135+
const unsigned char *msg,
136+
size_t msglen,
122137
const secp256k1_xonly_pubkey *pubkey
123-
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
138+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
124139

125140
#ifdef __cplusplus
126141
}

src/bench_schnorrsig.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "util.h"
1414
#include "bench.h"
1515

16+
#define MSGLEN 32
17+
1618
typedef struct {
1719
secp256k1_context *ctx;
1820
int n;
@@ -26,7 +28,7 @@ typedef struct {
2628
void bench_schnorrsig_sign(void* arg, int iters) {
2729
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
2830
int i;
29-
unsigned char msg[32] = "benchmarkexamplemessagetemplate";
31+
unsigned char msg[MSGLEN] = "benchmarkexamplemessagetemplate";
3032
unsigned char sig[64];
3133

3234
for (i = 0; i < iters; i++) {
@@ -43,7 +45,7 @@ void bench_schnorrsig_verify(void* arg, int iters) {
4345
for (i = 0; i < iters; i++) {
4446
secp256k1_xonly_pubkey pk;
4547
CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &pk, data->pk[i]) == 1);
46-
CHECK(secp256k1_schnorrsig_verify(data->ctx, data->sigs[i], data->msgs[i], &pk));
48+
CHECK(secp256k1_schnorrsig_verify(data->ctx, data->sigs[i], data->msgs[i], MSGLEN, &pk));
4749
}
4850
}
4951

@@ -60,7 +62,7 @@ int main(void) {
6062

6163
for (i = 0; i < iters; i++) {
6264
unsigned char sk[32];
63-
unsigned char *msg = (unsigned char *)malloc(32);
65+
unsigned char *msg = (unsigned char *)malloc(MSGLEN);
6466
unsigned char *sig = (unsigned char *)malloc(64);
6567
secp256k1_keypair *keypair = (secp256k1_keypair *)malloc(sizeof(*keypair));
6668
unsigned char *pk_char = (unsigned char *)malloc(32);

src/modules/schnorrsig/main_impl.h

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static void secp256k1_nonce_function_bip340_sha256_tagged_aux(secp256k1_sha256 *
4747
* by using the correct tagged hash function. */
4848
static const unsigned char bip340_algo[13] = "BIP0340/nonce";
4949

50-
static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *xonly_pk32, const unsigned char *algo, size_t algolen, void *data) {
50+
static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *msg, size_t msglen, const unsigned char *key32, const unsigned char *xonly_pk32, const unsigned char *algo, size_t algolen, void *data) {
5151
secp256k1_sha256 sha;
5252
unsigned char masked_key[32];
5353
int i;
@@ -82,7 +82,7 @@ static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *ms
8282
secp256k1_sha256_write(&sha, key32, 32);
8383
}
8484
secp256k1_sha256_write(&sha, xonly_pk32, 32);
85-
secp256k1_sha256_write(&sha, msg32, 32);
85+
secp256k1_sha256_write(&sha, msg, msglen);
8686
secp256k1_sha256_finalize(&sha, nonce32);
8787
return 1;
8888
}
@@ -104,28 +104,27 @@ static void secp256k1_schnorrsig_sha256_tagged(secp256k1_sha256 *sha) {
104104
sha->bytes = 64;
105105
}
106106

107-
static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned char *r32, const unsigned char *msg32, const unsigned char *pubkey32)
107+
static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned char *r32, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32)
108108
{
109109
unsigned char buf[32];
110110
secp256k1_sha256 sha;
111111

112-
/* tagged hash(r.x, pk.x, msg32) */
112+
/* tagged hash(r.x, pk.x, msg) */
113113
secp256k1_schnorrsig_sha256_tagged(&sha);
114114
secp256k1_sha256_write(&sha, r32, 32);
115115
secp256k1_sha256_write(&sha, pubkey32, 32);
116-
secp256k1_sha256_write(&sha, msg32, 32);
116+
secp256k1_sha256_write(&sha, msg, msglen);
117117
secp256k1_sha256_finalize(&sha, buf);
118118
/* Set scalar e to the challenge hash modulo the curve order as per
119119
* BIP340. */
120120
secp256k1_scalar_set_b32(e, buf, NULL);
121121
}
122122

123-
124123
int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, unsigned char *aux_rand32) {
125-
return secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, keypair, NULL, aux_rand32);
124+
return secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, 32, keypair, NULL, aux_rand32);
126125
}
127126

128-
int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, secp256k1_nonce_function_hardened noncefp, void *ndata) {
127+
int secp256k1_schnorrsig_sign_custom(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) {
129128
secp256k1_scalar sk;
130129
secp256k1_scalar e;
131130
secp256k1_scalar k;
@@ -140,7 +139,7 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
140139
VERIFY_CHECK(ctx != NULL);
141140
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
142141
ARG_CHECK(sig64 != NULL);
143-
ARG_CHECK(msg32 != NULL);
142+
ARG_CHECK(msg != NULL || msglen == 0);
144143
ARG_CHECK(keypair != NULL);
145144

146145
if (noncefp == NULL) {
@@ -157,7 +156,7 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
157156

158157
secp256k1_scalar_get_b32(seckey, &sk);
159158
secp256k1_fe_get_b32(pk_buf, &pk.x);
160-
ret &= !!noncefp(buf, msg32, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), ndata);
159+
ret &= !!noncefp(buf, msg, msglen, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), ndata);
161160
secp256k1_scalar_set_b32(&k, buf, NULL);
162161
ret &= !secp256k1_scalar_is_zero(&k);
163162
secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
@@ -175,7 +174,7 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
175174
secp256k1_fe_normalize_var(&r.x);
176175
secp256k1_fe_get_b32(&sig64[0], &r.x);
177176

178-
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg32, pk_buf);
177+
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg, msglen, pk_buf);
179178
secp256k1_scalar_mul(&e, &e, &sk);
180179
secp256k1_scalar_add(&e, &e, &k);
181180
secp256k1_scalar_get_b32(&sig64[32], &e);
@@ -188,7 +187,7 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
188187
return ret;
189188
}
190189

191-
int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg32, const secp256k1_xonly_pubkey *pubkey) {
190+
int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey) {
192191
secp256k1_scalar s;
193192
secp256k1_scalar e;
194193
secp256k1_gej rj;
@@ -202,7 +201,7 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
202201
VERIFY_CHECK(ctx != NULL);
203202
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
204203
ARG_CHECK(sig64 != NULL);
205-
ARG_CHECK(msg32 != NULL);
204+
ARG_CHECK(msg != NULL || msglen == 0);
206205
ARG_CHECK(pubkey != NULL);
207206

208207
if (!secp256k1_fe_set_b32(&rx, &sig64[0])) {
@@ -220,7 +219,7 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
220219

221220
/* Compute e. */
222221
secp256k1_fe_get_b32(buf, &pk.x);
223-
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg32, buf);
222+
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg, msglen, buf);
224223

225224
/* Compute rj = s*G + (-e)*pkj */
226225
secp256k1_scalar_negate(&e, &e);

src/modules/schnorrsig/tests_exhaustive_impl.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ static const unsigned char invalid_pubkey_bytes[][32] = {
5858

5959
#define NUM_INVALID_KEYS (sizeof(invalid_pubkey_bytes) / sizeof(invalid_pubkey_bytes[0]))
6060

61-
static int secp256k1_hardened_nonce_function_smallint(unsigned char *nonce32, const unsigned char *msg32,
61+
static int secp256k1_hardened_nonce_function_smallint(unsigned char *nonce32, const unsigned char *msg,
62+
size_t msglen,
6263
const unsigned char *key32, const unsigned char *xonly_pk32,
6364
const unsigned char *algo, size_t algolen,
6465
void* data) {
6566
secp256k1_scalar s;
6667
int *idata = data;
67-
(void)msg32;
68+
(void)msg;
69+
(void)msglen;
6870
(void)key32;
6971
(void)xonly_pk32;
7072
(void)algo;
@@ -103,7 +105,7 @@ static void test_exhaustive_schnorrsig_verify(const secp256k1_context *ctx, cons
103105
secp256k1_scalar e;
104106
unsigned char msg32[32];
105107
secp256k1_testrand256(msg32);
106-
secp256k1_schnorrsig_challenge(&e, sig64, msg32, pk32);
108+
secp256k1_schnorrsig_challenge(&e, sig64, msg32, sizeof(msg32), pk32);
107109
/* Only do work if we hit a challenge we haven't tried before. */
108110
if (!e_done[e]) {
109111
/* Iterate over the possible valid last 32 bytes in the signature.
@@ -121,7 +123,7 @@ static void test_exhaustive_schnorrsig_verify(const secp256k1_context *ctx, cons
121123
secp256k1_testrand256(sig64 + 32);
122124
expect_valid = 0;
123125
}
124-
valid = secp256k1_schnorrsig_verify(ctx, sig64, msg32, &pubkeys[d - 1]);
126+
valid = secp256k1_schnorrsig_verify(ctx, sig64, msg32, sizeof(msg32), &pubkeys[d - 1]);
125127
CHECK(valid == expect_valid);
126128
count_valid += valid;
127129
}
@@ -156,14 +158,14 @@ static void test_exhaustive_schnorrsig_sign(const secp256k1_context *ctx, unsign
156158
while (e_count_done < EXHAUSTIVE_TEST_ORDER) {
157159
secp256k1_scalar e;
158160
secp256k1_testrand256(msg32);
159-
secp256k1_schnorrsig_challenge(&e, xonly_pubkey_bytes[k - 1], msg32, xonly_pubkey_bytes[d - 1]);
161+
secp256k1_schnorrsig_challenge(&e, xonly_pubkey_bytes[k - 1], msg32, sizeof(msg32), xonly_pubkey_bytes[d - 1]);
160162
/* Only do work if we hit a challenge we haven't tried before. */
161163
if (!e_done[e]) {
162164
secp256k1_scalar expected_s = (actual_k + e * actual_d) % EXHAUSTIVE_TEST_ORDER;
163165
unsigned char expected_s_bytes[32];
164166
secp256k1_scalar_get_b32(expected_s_bytes, &expected_s);
165167
/* Invoke the real function to construct a signature. */
166-
CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, &keypairs[d - 1], secp256k1_hardened_nonce_function_smallint, &k));
168+
CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, sizeof(msg32), &keypairs[d - 1], secp256k1_hardened_nonce_function_smallint, &k));
167169
/* The first 32 bytes must match the xonly pubkey for the specified k. */
168170
CHECK(secp256k1_memcmp_var(sig64, xonly_pubkey_bytes[k - 1], 32) == 0);
169171
/* The last 32 bytes must match the expected s value. */

0 commit comments

Comments
 (0)