Skip to content

Commit

Permalink
schnorrsig: allow signing and verification of variable length msgs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jonasnick committed Jun 27, 2021
1 parent 5a8e499 commit a0c3fc1
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 81 deletions.
39 changes: 27 additions & 12 deletions include/secp256k1_schnorrsig.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ extern "C" {
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to
* return an error.
* Out: nonce32: pointer to a 32-byte array to be filled by the function
* In: msg32: the 32-byte message hash being verified (will not be NULL)
* In: msg: the message being verified. Is NULL if and only if msglen
* is 0.
* msglen: the length of the message
* key32: pointer to a 32-byte secret key (will not be NULL)
* xonly_pk32: the 32-byte serialized xonly pubkey corresponding to key32
* (will not be NULL)
Expand All @@ -38,7 +40,8 @@ extern "C" {
*/
typedef int (*secp256k1_nonce_function_hardened)(
unsigned char *nonce32,
const unsigned char *msg32,
const unsigned char *msg,
size_t msglen,
const unsigned char *key32,
const unsigned char *xonly_pk32,
const unsigned char *algo,
Expand Down Expand Up @@ -66,6 +69,13 @@ SECP256K1_API extern const secp256k1_nonce_function_hardened secp256k1_nonce_fun
* signature. Instead, you can manually use secp256k1_schnorrsig_verify and
* abort if it fails.
*
* This function only signs 32-byte messages. If you have messages of a
* different size (or the same size but without a context-specific tag
* prefix), it is recommended to create a 32-byte message hash with
* secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows
* providing an context-specific tag for domain separation. This prevents
* signatures from being valid in multiple contexts by accident.
*
* Returns 1 on success, 0 on failure.
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
* Out: sig64: pointer to a 64-byte array to store the serialized signature (cannot be NULL)
Expand All @@ -86,12 +96,14 @@ SECP256K1_API int secp256k1_schnorrsig_sign(

/** Create a Schnorr signature with a more flexible API.
*
* Same arguments as secp256k1_schnorrsig_sign except that it misses aux_rand32
* and instead allows allows providing a different nonce derivation function
* with its own data argument.
* Same arguments as secp256k1_schnorrsig_sign except that it allows signing
* variable length messages and allows providing a different nonce derivation
* function with its own data argument.
*
* In: noncefp: pointer to a nonce generation function. If NULL,
* secp256k1_nonce_function_bip340 is used
* In: msg: the message being signed. Can only be NULL if msglen is 0.
* msglen: length of the message
* noncefp: pointer to a nonce generation function. If NULL,
* secp256k1_nonce_function_bip340 is used.
* ndata: pointer to arbitrary data used by the nonce generation function
* (can be NULL). If it is non-NULL and
* secp256k1_nonce_function_bip340 is used, then ndata must be a
Expand All @@ -100,27 +112,30 @@ SECP256K1_API int secp256k1_schnorrsig_sign(
SECP256K1_API int secp256k1_schnorrsig_sign_custom(
const secp256k1_context* ctx,
unsigned char *sig64,
const unsigned char *msg32,
const unsigned char *msg,
size_t msglen,
const secp256k1_keypair *keypair,
secp256k1_nonce_function_hardened noncefp,
void *ndata
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);

/** Verify a Schnorr signature.
*
* Returns: 1: correct signature
* 0: incorrect signature
* Args: ctx: a secp256k1 context object, initialized for verification.
* In: sig64: pointer to the 64-byte signature to verify (cannot be NULL)
* msg32: the 32-byte message being verified (cannot be NULL)
* msg: the message being verified. Can only be NULL if msglen is 0.
* msglen: length of the message
* pubkey: pointer to an x-only public key to verify with (cannot be NULL)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
const secp256k1_context* ctx,
const unsigned char *sig64,
const unsigned char *msg32,
const unsigned char *msg,
size_t msglen,
const secp256k1_xonly_pubkey *pubkey
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);

#ifdef __cplusplus
}
Expand Down
8 changes: 5 additions & 3 deletions src/bench_schnorrsig.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "util.h"
#include "bench.h"

#define MSGLEN 32

typedef struct {
secp256k1_context *ctx;
int n;
Expand All @@ -26,7 +28,7 @@ typedef struct {
void bench_schnorrsig_sign(void* arg, int iters) {
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
int i;
unsigned char msg[32] = "benchmarkexamplemessagetemplate";
unsigned char msg[MSGLEN] = "benchmarkexamplemessagetemplate";
unsigned char sig[64];

for (i = 0; i < iters; i++) {
Expand All @@ -43,7 +45,7 @@ void bench_schnorrsig_verify(void* arg, int iters) {
for (i = 0; i < iters; i++) {
secp256k1_xonly_pubkey pk;
CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &pk, data->pk[i]) == 1);
CHECK(secp256k1_schnorrsig_verify(data->ctx, data->sigs[i], data->msgs[i], &pk));
CHECK(secp256k1_schnorrsig_verify(data->ctx, data->sigs[i], data->msgs[i], MSGLEN, &pk));
}
}

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

for (i = 0; i < iters; i++) {
unsigned char sk[32];
unsigned char *msg = (unsigned char *)malloc(32);
unsigned char *msg = (unsigned char *)malloc(MSGLEN);
unsigned char *sig = (unsigned char *)malloc(64);
secp256k1_keypair *keypair = (secp256k1_keypair *)malloc(sizeof(*keypair));
unsigned char *pk_char = (unsigned char *)malloc(32);
Expand Down
27 changes: 13 additions & 14 deletions src/modules/schnorrsig/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static void secp256k1_nonce_function_bip340_sha256_tagged_aux(secp256k1_sha256 *
* by using the correct tagged hash function. */
static const unsigned char bip340_algo[13] = "BIP0340/nonce";

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) {
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) {
secp256k1_sha256 sha;
unsigned char masked_key[32];
int i;
Expand Down Expand Up @@ -82,7 +82,7 @@ static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *ms
secp256k1_sha256_write(&sha, key32, 32);
}
secp256k1_sha256_write(&sha, xonly_pk32, 32);
secp256k1_sha256_write(&sha, msg32, 32);
secp256k1_sha256_write(&sha, msg, msglen);
secp256k1_sha256_finalize(&sha, nonce32);
return 1;
}
Expand All @@ -104,28 +104,27 @@ static void secp256k1_schnorrsig_sha256_tagged(secp256k1_sha256 *sha) {
sha->bytes = 64;
}

static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned char *r32, const unsigned char *msg32, const unsigned char *pubkey32)
static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned char *r32, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32)
{
unsigned char buf[32];
secp256k1_sha256 sha;

/* tagged hash(r.x, pk.x, msg32) */
/* tagged hash(r.x, pk.x, msg) */
secp256k1_schnorrsig_sha256_tagged(&sha);
secp256k1_sha256_write(&sha, r32, 32);
secp256k1_sha256_write(&sha, pubkey32, 32);
secp256k1_sha256_write(&sha, msg32, 32);
secp256k1_sha256_write(&sha, msg, msglen);
secp256k1_sha256_finalize(&sha, buf);
/* Set scalar e to the challenge hash modulo the curve order as per
* BIP340. */
secp256k1_scalar_set_b32(e, buf, NULL);
}


int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, unsigned char *aux_rand32) {
return secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, keypair, NULL, aux_rand32);
return secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, 32, keypair, NULL, aux_rand32);
}

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) {
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) {
secp256k1_scalar sk;
secp256k1_scalar e;
secp256k1_scalar k;
Expand All @@ -140,7 +139,7 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(sig64 != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(msg != NULL || msglen == 0);
ARG_CHECK(keypair != NULL);

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

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

secp256k1_schnorrsig_challenge(&e, &sig64[0], msg32, pk_buf);
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg, msglen, pk_buf);
secp256k1_scalar_mul(&e, &e, &sk);
secp256k1_scalar_add(&e, &e, &k);
secp256k1_scalar_get_b32(&sig64[32], &e);
Expand All @@ -188,7 +187,7 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char
return ret;
}

int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg32, const secp256k1_xonly_pubkey *pubkey) {
int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey) {
secp256k1_scalar s;
secp256k1_scalar e;
secp256k1_gej rj;
Expand All @@ -202,7 +201,7 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
ARG_CHECK(sig64 != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(msg != NULL || msglen == 0);
ARG_CHECK(pubkey != NULL);

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

/* Compute e. */
secp256k1_fe_get_b32(buf, &pk.x);
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg32, buf);
secp256k1_schnorrsig_challenge(&e, &sig64[0], msg, msglen, buf);

/* Compute rj = s*G + (-e)*pkj */
secp256k1_scalar_negate(&e, &e);
Expand Down
14 changes: 8 additions & 6 deletions src/modules/schnorrsig/tests_exhaustive_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ static const unsigned char invalid_pubkey_bytes[][32] = {

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

static int secp256k1_hardened_nonce_function_smallint(unsigned char *nonce32, const unsigned char *msg32,
static int secp256k1_hardened_nonce_function_smallint(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) {
secp256k1_scalar s;
int *idata = data;
(void)msg32;
(void)msg;
(void)msglen;
(void)key32;
(void)xonly_pk32;
(void)algo;
Expand Down Expand Up @@ -103,7 +105,7 @@ static void test_exhaustive_schnorrsig_verify(const secp256k1_context *ctx, cons
secp256k1_scalar e;
unsigned char msg32[32];
secp256k1_testrand256(msg32);
secp256k1_schnorrsig_challenge(&e, sig64, msg32, pk32);
secp256k1_schnorrsig_challenge(&e, sig64, msg32, sizeof(msg32), pk32);
/* Only do work if we hit a challenge we haven't tried before. */
if (!e_done[e]) {
/* Iterate over the possible valid last 32 bytes in the signature.
Expand All @@ -121,7 +123,7 @@ static void test_exhaustive_schnorrsig_verify(const secp256k1_context *ctx, cons
secp256k1_testrand256(sig64 + 32);
expect_valid = 0;
}
valid = secp256k1_schnorrsig_verify(ctx, sig64, msg32, &pubkeys[d - 1]);
valid = secp256k1_schnorrsig_verify(ctx, sig64, msg32, sizeof(msg32), &pubkeys[d - 1]);
CHECK(valid == expect_valid);
count_valid += valid;
}
Expand Down Expand Up @@ -156,14 +158,14 @@ static void test_exhaustive_schnorrsig_sign(const secp256k1_context *ctx, unsign
while (e_count_done < EXHAUSTIVE_TEST_ORDER) {
secp256k1_scalar e;
secp256k1_testrand256(msg32);
secp256k1_schnorrsig_challenge(&e, xonly_pubkey_bytes[k - 1], msg32, xonly_pubkey_bytes[d - 1]);
secp256k1_schnorrsig_challenge(&e, xonly_pubkey_bytes[k - 1], msg32, sizeof(msg32), xonly_pubkey_bytes[d - 1]);
/* Only do work if we hit a challenge we haven't tried before. */
if (!e_done[e]) {
secp256k1_scalar expected_s = (actual_k + e * actual_d) % EXHAUSTIVE_TEST_ORDER;
unsigned char expected_s_bytes[32];
secp256k1_scalar_get_b32(expected_s_bytes, &expected_s);
/* Invoke the real function to construct a signature. */
CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, &keypairs[d - 1], secp256k1_hardened_nonce_function_smallint, &k));
CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig64, msg32, sizeof(msg32), &keypairs[d - 1], secp256k1_hardened_nonce_function_smallint, &k));
/* The first 32 bytes must match the xonly pubkey for the specified k. */
CHECK(secp256k1_memcmp_var(sig64, xonly_pubkey_bytes[k - 1], 32) == 0);
/* The last 32 bytes must match the expected s value. */
Expand Down
Loading

0 comments on commit a0c3fc1

Please sign in to comment.