From 4ff856741cc17828fbde6780970c8355384d8683 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 3 Feb 2015 11:26:30 -0800 Subject: [PATCH] Add support for Schnorr signatures. --- Makefile.am | 7 +- include/secp256k1.h | 188 +++++++++++++++++++++++++++++++++ src/bench_schnorr_verify.c | 64 ++++++++++++ src/schnorr.h | 19 ++++ src/schnorr_impl.h | 207 +++++++++++++++++++++++++++++++++++++ src/secp256k1.c | 195 ++++++++++++++++++++++++++++++++++ src/tests.c | 197 +++++++++++++++++++++++++++++++++++ 7 files changed, 876 insertions(+), 1 deletion(-) create mode 100644 src/bench_schnorr_verify.c create mode 100644 src/schnorr.h create mode 100644 src/schnorr_impl.h diff --git a/Makefile.am b/Makefile.am index cc15338b7e..3ef756c2dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,8 @@ noinst_HEADERS += src/hash_impl.h noinst_HEADERS += src/field.h noinst_HEADERS += src/field_impl.h noinst_HEADERS += src/bench.h +noinst_HEADERS += src/schnorr.h +noinst_HEADERS += src/schnorr_impl.h pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libsecp256k1.pc @@ -49,10 +51,13 @@ libsecp256k1_la_LIBADD = $(SECP_LIBS) noinst_PROGRAMS = if USE_BENCHMARK -noinst_PROGRAMS += bench_verify bench_recover bench_sign bench_internal +noinst_PROGRAMS += bench_verify bench_recover bench_sign bench_internal bench_schnorr_verify bench_verify_SOURCES = src/bench_verify.c bench_verify_LDADD = libsecp256k1.la $(SECP_LIBS) bench_verify_LDFLAGS = -static +bench_schnorr_verify_SOURCES = src/bench_schnorr_verify.c +bench_schnorr_verify_LDADD = libsecp256k1.la $(SECP_LIBS) +bench_schnorr_verify_LDFLAGS = -static bench_recover_SOURCES = src/bench_recover.c bench_recover_LDADD = libsecp256k1.la $(SECP_LIBS) bench_recover_LDFLAGS = -static diff --git a/include/secp256k1.h b/include/secp256k1.h index beda1e7b5d..dbf4ed6f74 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -341,6 +341,194 @@ SECP256K1_WARN_UNUSED_RESULT int secp256k1_context_randomize( const unsigned char *seed32 ) SECP256K1_ARG_NONNULL(1); +/** Create a signature using a custom EC-Schnorr-SHA256 construction. It + * produces non-malleable 64-byte signatures which support public key recovery + * batch validation, and multiparty signing. + * Returns: 1: signature created + * 0: the nonce generation function failed, or the private key was + * invalid. + * In: ctx: pointer to a context object, initialized for signing + * (cannot be NULL) + * msg32: the 32-byte message hash being signed (cannot be NULL) + * seckey: pointer to a 32-byte secret key (cannot be NULL) + * noncefp:pointer to a nonce generation function. If NULL, + * secp256k1_nonce_function_default is used + * ndata: pointer to arbitrary data used by the nonce generation + * function (can be NULL) + * Out: sig64: pointer to a 64-byte array where the signature will be + * placed (cannot be NULL) + */ +int secp256k1_schnorr_sign( + const secp256k1_context_t* ctx, + const unsigned char *msg32, + unsigned char *sig64, + const unsigned char *seckey, + secp256k1_nonce_function_t noncefp, + const void *ndata +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Verify a signature created by secp256k1_schnorr_sign. + * Returns: 1: public key and signature correct + * 0: incorrect signature + * -1: invalid public key + * In: ctx: a secp256k1 context object, initialized for verification. + * msg32: the 32-byte message hash being verified (cannot be NULL) + * sig64: the 64-byte signature being verified (cannot be NULL) + * pubkey: the public key to verify with (cannot be NULL) + * pubkeylen: the length of pubkey + */ +SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorr_verify( + const secp256k1_context_t* ctx, + const unsigned char *msg32, + const unsigned char *sig64, + const unsigned char *pubkey, + int pubkeylen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Recover an EC public key from a Schnorr signature created using + * secp256k1_schnorr_sign. + * Returns: 1: public key successfully recovered (which guarantees a correct + * signature). + * 0: otherwise. + * In: ctx: pointer to a context object, initialized for + * verification (cannot be NULL) + * msg32: the 32-byte message hash assumed to be signed (cannot + * be NULL) + * sig64: signature as 64 byte array (cannot be NULL) + * compressed: whether to recover a compressed or uncompressed pubkey + * Out: pubkey: pointer to a 33 or 65 byte array to put the pubkey + * (cannot be NULL) + * pubkeylen: pointer to an int that will contain the pubkey length + * (cannot be NULL) + */ +int secp256k1_schnorr_recover( + const secp256k1_context_t* ctx, + const unsigned char *msg32, + const unsigned char *sig64, + unsigned char *pubkey, + int *pubkeylen, + int compressed +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Generate a nonce pair deterministically for use with + * secp256k1_schnorr_partial_sign. + * Returns: 1: valid nonce pair was generated. + * 0: otherwise (nonce generation function failed) + * In: ctx: pointer to a context object, initialized for signing + * (cannot be NULL) + * msg32: the 32-byte message hash assumed to be signed (cannot + * be NULL) + * sec32: the 32-byte private key (cannot be NULL) + * noncefp: pointer to a nonce generation function. If NULL, + * secp256k1_nonce_function_default is used + * noncedata: pointer to arbitrary data used by the nonce generation + * function (can be NULL) + * Out: pubnonce33: public side of the nonce (represented as a 33-byte + * compressed public key) (cannot be NULL) + * privnonce32:private side of the nonce (32 byte) (cannot be NULL) + * + * Do not use the output as a private/public key pair for signing/validation. + */ +int secp256k1_schnorr_generate_nonce_pair( + const secp256k1_context_t* ctx, + const unsigned char *msg32, + const unsigned char *sec32, + secp256k1_nonce_function_t noncefp, + const void* noncedata, + unsigned char *pubnonce33, + unsigned char *privnonce32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7); + +/** Add a number of public keys together. + * Returns: 1: the sum of the public keys is valid. + * 0: the sum of the public keys is not valid. + * -1: one or more of the inputs were invalid. + * In: ctx: pointer to a context object + * out: pointer to 33-byte array for placing the resulting + * public key (in compressed format) (cannot be NULL) + * n: the number of public keys to add together (must be at least 1) + * ins: pointer to array of pointers to public keys in + * compressed format (cannot be NULL) + * Use secp256k1_ec_pubkey_compress and secp256k1_ec_pubkey_decompress if the + * uncompressed format is needed. + */ +SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine( + const secp256k1_context_t* ctx, + unsigned char *out, + int n, + const unsigned char **ins +) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4); + +/** Produce a partial Schnorr signature, which can be combined using + * secp256k1_schnorr_partial_combine, to end up with a full signature that is + * verifiable using secp256k1_schnorr_verify. + * Returns: 1: signature created succesfully. + * 0: no valid signature exists with this combination of keys, nonces + * and message (chance around 1 in 2^128) + * -1: invalid private key, nonce, or public nonces. + * In: ctx: pointer to context object, initialized for signing (cannot + * be NULL) + * msg32: pointer to 32-byte message to sign + * sec32: pointer to 32-byte private key + * secnonce32: pointer to 32-byte array containing our nonce + * pubnonce33: pointer to 33-byte array containing the sum of the other's + * nonces (see secp256k1_ec_pubkey_combine) + * Out: sig64: pointer to 64-byte array to put partial signature in + * + * The intended procedure for creating a multiparty signature is: + * - Each signer S[i] with private key x[i] and public key Q[i] runs + * secp256k1_schnorr_generate_nonce_pair to produce a pair (k[i],R[i]) of + * private/public nonces. + * - All signers communicate their public nonces to each other (revealing your + * private nonce can lead to discovery of your private key, so it should be + * considered secret). + * - All signers combine all the public nonces they received (excluding their + * own) using secp256k1_ec_pubkey_combine to obtain an + * Rall[i] = sum(R[0..i-1,i+1..n]). + * - All signers produce a partial signature using + * secp256k1_schnorr_partial_sign, passing in their own private key x[i], + * their own private nonce k[i], and the sum of the others' public nonces + * Rall[i]. + * - All signers communicate their partial signatures to each other. + * - Someone combines all partial signatures using + * secp256k1_schnorr_partial_combine, to obtain a full signature. + * - The resulting signature is validatable using secp256k1_schnorr_verify, with + * public key equal to the result of secp256k1_ec_pubkey_combine of the + * signers' public keys (sum(Q[0..n])). + * + * Note that secp256k1_schnorr_partial_combine and secp256k1_ec_pubkey_combine + * function take their arguments in any order, and it is possible to + * pre-combine several inputs already with one call, and add more inputs later + * by calling the function again. + */ +SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorr_partial_sign( + const secp256k1_context_t* ctx, + const unsigned char *msg32, + unsigned char *sig64, + const unsigned char *sec32, + const unsigned char *secnonce32, + const unsigned char *pubnonce33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Combine multiple Schnorr partial signatures. + * Returns: 1: the passed signatures were succesfully combined. + * 0: some signatures cancel eachother out (chance of 1 in 2^128 per + * signature) + * -1: some inputs were invalid, or the signatures were not created + * using the same set of nonces + * In: ctx: pointer to a context object + * sig64: pointer to a 64-byte array to place the combined signature + * (cannot be NULL) + * n: the number of signatures to combine (at least 1) + * Out: sig64sin: pointer to an array of n pointers to 64-byte input + * signatures + */ +SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorr_partial_combine( + const secp256k1_context_t* ctx, + unsigned char *sig64, + int n, +const unsigned char **sig64sin +) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4); # ifdef __cplusplus } diff --git a/src/bench_schnorr_verify.c b/src/bench_schnorr_verify.c new file mode 100644 index 0000000000..a3b85067a5 --- /dev/null +++ b/src/bench_schnorr_verify.c @@ -0,0 +1,64 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include +#include + +#include "include/secp256k1.h" +#include "util.h" +#include "bench.h" + +typedef struct { + unsigned char key[32]; + unsigned char sig[64]; + unsigned char pubkey[33]; + int pubkeylen; +} benchmark_schnorr_sig_t; + +typedef struct { + secp256k1_context_t *ctx; + unsigned char msg[32]; + benchmark_schnorr_sig_t sigs[64]; + int numsigs; +} benchmark_schnorr_verify_t; + +static void benchmark_schnorr_init(void* arg) { + int i, k; + benchmark_schnorr_verify_t* data = (benchmark_schnorr_verify_t*)arg; + + for (i = 0; i < 32; i++) data->msg[i] = 1 + i; + for (k = 0; k < data->numsigs; k++) { + for (i = 0; i < 32; i++) data->sigs[k].key[i] = 33 + i + k; + secp256k1_schnorr_sign(data->ctx, data->msg, data->sigs[k].sig, data->sigs[k].key, NULL, NULL); + data->sigs[k].pubkeylen = 33; + CHECK(secp256k1_ec_pubkey_create(data->ctx, data->sigs[k].pubkey, &data->sigs[k].pubkeylen, data->sigs[k].key, 1)); + } +} + +static void benchmark_schnorr_verify(void* arg) { + int i; + benchmark_schnorr_verify_t* data = (benchmark_schnorr_verify_t*)arg; + + for (i = 0; i < 20000 / data->numsigs; i++) { + data->sigs[0].sig[(i >> 8) % 64] ^= (i & 0xFF); + CHECK(secp256k1_schnorr_verify(data->ctx, data->msg, data->sigs[0].sig, data->sigs[0].pubkey, data->sigs[0].pubkeylen) == ((i & 0xFF) == 0)); + data->sigs[0].sig[(i >> 8) % 64] ^= (i & 0xFF); + } +} + + + +int main(void) { + benchmark_schnorr_verify_t data; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + data.numsigs = 1; + run_benchmark("schnorr_verify", benchmark_schnorr_verify, benchmark_schnorr_init, NULL, &data, 10, 20000); + + secp256k1_context_destroy(data.ctx); + return 0; +} diff --git a/src/schnorr.h b/src/schnorr.h new file mode 100644 index 0000000000..939a426e75 --- /dev/null +++ b/src/schnorr.h @@ -0,0 +1,19 @@ +/*********************************************************************** + * Copyright (c) 2014-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php. * + ***********************************************************************/ + +#ifndef _SECP256K1_SCHNORR_ +#define _SECP256K1_SCHNORR_ + +#include "scalar.h" +#include "group.h" + +typedef void (*secp256k1_schnorr_msghash_t)(unsigned char *h32, const unsigned char *r32, const unsigned char *msg32); + +static int secp256k1_schnorr_sig_sign(const secp256k1_ecmult_gen_context_t* ctx, unsigned char *sig64, const secp256k1_scalar_t *key, const secp256k1_scalar_t *nonce, const secp256k1_ge_t *pubnonce, secp256k1_schnorr_msghash_t hash, const unsigned char *msg32); +static int secp256k1_schnorr_sig_verify(const secp256k1_ecmult_context_t* ctx, const unsigned char *sig64, const secp256k1_ge_t *pubkey, secp256k1_schnorr_msghash_t hash, const unsigned char *msg32); +static int secp256k1_schnorr_sig_recover(const secp256k1_ecmult_context_t* ctx, const unsigned char *sig64, secp256k1_ge_t *pubkey, secp256k1_schnorr_msghash_t hash, const unsigned char *msg32); + +#endif diff --git a/src/schnorr_impl.h b/src/schnorr_impl.h new file mode 100644 index 0000000000..8b49d4e3ae --- /dev/null +++ b/src/schnorr_impl.h @@ -0,0 +1,207 @@ +/*********************************************************************** + * Copyright (c) 2014-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php. * + ***********************************************************************/ + +#ifndef _SECP256K1_SCHNORR_IMPL_H_ +#define _SECP256K1_SCHNORR_IMPL_H_ + +#include + +#include "schnorr.h" +#include "num.h" +#include "field.h" +#include "group.h" +#include "ecmult.h" +#include "ecmult_gen.h" + +/** + * Custom Schnorr-based signature scheme. They support multiparty signing, public key + * recovery and batch validation. + * + * Rationale for verifying R's y coordinate: + * In order to support batch validation and public key recovery, the full R point must + * be known to verifiers, rather than just its x coordinate. In order to not risk + * being more strict in batch validation than normal validation, validators must be + * required to reject signatures with incorrect y coordinate. This is only possible + * by including a (relatively slow) field inverse, or a field square root. However, + * batch validation offers potentially much higher benefits than this cost. + * + * Rationale for having an implicit y coordinate oddness: + * If we commit to having the full R point known to verifiers, there are two mechanism. + * Either include its oddness in the signature, or give it an implicit fixed value. + * As the R y coordinate can be flipped by a simple negation of the nonce, we choose the + * latter, as it comes with nearly zero impact on signing or validation performance, and + * saves a byte in the signature. + * + * Signing: + * Inputs: 32-byte message m, 32-byte scalar key x (!=0), 32-byte scalar nonce k (!=0) + * + * Compute point R = k * G. Reject nonce if R's y coordinate is odd (or negate nonce). + * Compute 32-byte r, the serialization of R's x coordinate. + * Compute scalar h = Hash(r || m). Reject nonce if h == 0 or h >= order. + * Compute scalar s = k - h * x. + * The signature is (r, s). + * + * + * Verification: + * Inputs: 32-byte message m, public key point Q, signature: (32-byte r, scalar s) + * + * Signature is invalid if s >= order. + * Signature is invalid if r >= p. + * Compute scalar h = Hash(r || m). Signature is invalid if h == 0 or h >= order. + * Option 1 (faster for single verification): + * Compute point R = h * Q + s * G. Signature is invalid if R is infinity or R's y coordinate is odd. + * Signature is valid if the serialization of R's x coordinate equals r. + * Option 2 (allows batch validation and pubkey recovery): + * Decompress x coordinate r into point R, with odd y coordinate. Fail if R is not on the curve. + * Signature is valid if R + h * Q + s * G == 0. + */ + +static int secp256k1_schnorr_sig_sign(const secp256k1_ecmult_gen_context_t* ctx, unsigned char *sig64, const secp256k1_scalar_t *key, const secp256k1_scalar_t *nonce, const secp256k1_ge_t *pubnonce, secp256k1_schnorr_msghash_t hash, const unsigned char *msg32) { + secp256k1_gej_t Rj; + secp256k1_ge_t Ra; + unsigned char h32[32]; + secp256k1_scalar_t h, s; + int overflow; + secp256k1_scalar_t n; + + if (secp256k1_scalar_is_zero(key) || secp256k1_scalar_is_zero(nonce)) { + return 0; + } + n = *nonce; + + secp256k1_ecmult_gen(ctx, &Rj, &n); + if (pubnonce) { + secp256k1_gej_add_ge(&Rj, &Rj, pubnonce); + } + secp256k1_ge_set_gej(&Ra, &Rj); + secp256k1_fe_normalize(&Ra.y); + if (secp256k1_fe_is_odd(&Ra.y)) { + /* R's y coordinate is odd, which is not allowed (see rationale above). + Force it to be even by negating the nonce. Note that this even works + for multiparty signing, as the R point is known to all participants, + which can all decide to flip the sign in unison, resulting in the + overall R point to be negated too. */ + secp256k1_scalar_negate(&n, &n); + } + secp256k1_fe_normalize(&Ra.x); + secp256k1_fe_get_b32(sig64, &Ra.x); + hash(h32, sig64, msg32); + overflow = 0; + secp256k1_scalar_set_b32(&h, h32, &overflow); + if (overflow || secp256k1_scalar_is_zero(&h)) { + secp256k1_scalar_clear(&n); + return 0; + } + secp256k1_scalar_mul(&s, &h, key); + secp256k1_scalar_negate(&s, &s); + secp256k1_scalar_add(&s, &s, &n); + secp256k1_scalar_clear(&n); + secp256k1_scalar_get_b32(sig64 + 32, &s); + return 1; +} + +static int secp256k1_schnorr_sig_verify(const secp256k1_ecmult_context_t* ctx, const unsigned char *sig64, const secp256k1_ge_t *pubkey, secp256k1_schnorr_msghash_t hash, const unsigned char *msg32) { + secp256k1_gej_t Qj, Rj; + secp256k1_ge_t Ra; + secp256k1_fe_t Rx; + secp256k1_scalar_t h, s; + unsigned char hh[32]; + int overflow; + + if (secp256k1_ge_is_infinity(pubkey)) { + return 0; + } + hash(hh, sig64, msg32); + overflow = 0; + secp256k1_scalar_set_b32(&h, hh, &overflow); + if (overflow || secp256k1_scalar_is_zero(&h)) { + return 0; + } + overflow = 0; + secp256k1_scalar_set_b32(&s, sig64 + 32, &overflow); + if (overflow) { + return 0; + } + if (!secp256k1_fe_set_b32(&Rx, sig64)) { + return 0; + } + secp256k1_gej_set_ge(&Qj, pubkey); + secp256k1_ecmult(ctx, &Rj, &Qj, &h, &s); + if (secp256k1_gej_is_infinity(&Rj)) { + return 0; + } + secp256k1_ge_set_gej_var(&Ra, &Rj); + secp256k1_fe_normalize_var(&Ra.y); + if (secp256k1_fe_is_odd(&Ra.y)) { + return 0; + } + return secp256k1_fe_equal_var(&Rx, &Ra.x); +} + +static int secp256k1_schnorr_sig_recover(const secp256k1_ecmult_context_t* ctx, const unsigned char *sig64, secp256k1_ge_t *pubkey, secp256k1_schnorr_msghash_t hash, const unsigned char *msg32) { + secp256k1_gej_t Qj, Rj; + secp256k1_ge_t Ra; + secp256k1_fe_t Rx; + secp256k1_scalar_t h, s; + unsigned char hh[32]; + int overflow; + + hash(hh, sig64, msg32); + overflow = 0; + secp256k1_scalar_set_b32(&h, hh, &overflow); + if (overflow || secp256k1_scalar_is_zero(&h)) { + return 0; + } + overflow = 0; + secp256k1_scalar_set_b32(&s, sig64 + 32, &overflow); + if (overflow) { + return 0; + } + if (!secp256k1_fe_set_b32(&Rx, sig64)) { + return 0; + } + if (!secp256k1_ge_set_xo_var(&Ra, &Rx, 0)) { + return 0; + } + secp256k1_gej_set_ge(&Rj, &Ra); + secp256k1_scalar_inverse_var(&h, &h); + secp256k1_scalar_negate(&s, &s); + secp256k1_scalar_mul(&s, &s, &h); + secp256k1_ecmult(ctx, &Qj, &Rj, &h, &s); + if (secp256k1_gej_is_infinity(&Qj)) { + return 0; + } + secp256k1_ge_set_gej(pubkey, &Qj); + return 1; +} + +static int secp256k1_schnorr_sig_combine(unsigned char *sig64, int n, const unsigned char **sig64ins) { + secp256k1_scalar_t s = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + int i; + for (i = 0; i < n; i++) { + secp256k1_scalar_t si; + int overflow; + secp256k1_scalar_set_b32(&si, sig64ins[i] + 32, &overflow); + if (overflow) { + return -1; + } + if (i) { + if (memcmp(sig64ins[i - 1], sig64ins[i], 32) != 0) { + return -1; + } + } + secp256k1_scalar_add(&s, &s, &si); + if (secp256k1_scalar_is_zero(&s)) { + return 0; + } + } + memcpy(sig64, sig64ins[0], 32); + secp256k1_scalar_get_b32(sig64 + 32, &s); + secp256k1_scalar_clear(&s); + return 1; +} + +#endif diff --git a/src/secp256k1.c b/src/secp256k1.c index 552c196aae..2cfa7f3f5b 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -16,6 +16,7 @@ #include "ecmult_impl.h" #include "ecmult_gen_impl.h" #include "ecdsa_impl.h" +#include "schnorr_impl.h" #include "eckey_impl.h" #include "hash_impl.h" @@ -418,3 +419,197 @@ int secp256k1_context_randomize(secp256k1_context_t* ctx, const unsigned char *s secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, seed32); return 1; } + +void secp256k1_schnorr_msghash_sha256(unsigned char *h32, const unsigned char *r32, const unsigned char *msg32) { + secp256k1_sha256_t sha; + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, r32, 32); + secp256k1_sha256_write(&sha, msg32, 32); + secp256k1_sha256_finalize(&sha, h32); +} + +static unsigned char secp256k1_schnorr_message_tweak[32] = "secp256k1-Schnorr-SHA256"; + +int secp256k1_schnorr_sign(const secp256k1_context_t* ctx, const unsigned char *msg32, unsigned char *sig64, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void* noncedata) { + secp256k1_scalar_t sec, non; + int ret = 0; + int overflow = 0; + unsigned int count = 0; + unsigned char msgt32[32]; + int i; + DEBUG_CHECK(ctx != NULL); + DEBUG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + DEBUG_CHECK(msg32 != NULL); + DEBUG_CHECK(sig64 != NULL); + DEBUG_CHECK(seckey != NULL); + if (noncefp == NULL) { + noncefp = secp256k1_nonce_function_default; + } + + for (i = 0; i < 32; i++) { + /* Tweak the message to prevent a key leak from signing the same message + using different algorithms. */ + msgt32[i] = msg32[i] ^ secp256k1_schnorr_message_tweak[i]; + } + + secp256k1_scalar_set_b32(&sec, seckey, NULL); + while (1) { + unsigned char nonce32[32]; + ret = noncefp(nonce32, msgt32, seckey, count, noncedata); + if (!ret) { + break; + } + secp256k1_scalar_set_b32(&non, nonce32, &overflow); + memset(nonce32, 0, 32); + if (!secp256k1_scalar_is_zero(&non) && !overflow) { + if (secp256k1_schnorr_sig_sign(&ctx->ecmult_gen_ctx, sig64, &sec, &non, NULL, secp256k1_schnorr_msghash_sha256, msg32)) { + break; + } + } + count++; + } + memset(msgt32, 0, 32); + secp256k1_scalar_clear(&non); + secp256k1_scalar_clear(&sec); + return ret; +} + +int secp256k1_schnorr_verify(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig64, const unsigned char *pubkey, int pubkeylen) { + secp256k1_ge_t q; + DEBUG_CHECK(ctx != NULL); + DEBUG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + DEBUG_CHECK(msg32 != NULL); + DEBUG_CHECK(sig64 != NULL); + DEBUG_CHECK(pubkey != NULL); + + if (!secp256k1_eckey_pubkey_parse(&q, pubkey, pubkeylen)) { + return -1; + } + return secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64, &q, secp256k1_schnorr_msghash_sha256, msg32); +} + +int secp256k1_schnorr_recover(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig64, unsigned char *pubkey, int *pubkeylen, int compressed) { + secp256k1_ge_t q; + int ret; + + DEBUG_CHECK(ctx != NULL); + DEBUG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + DEBUG_CHECK(msg32 != NULL); + DEBUG_CHECK(sig64 != NULL); + DEBUG_CHECK(pubkey != NULL); + + if (!secp256k1_schnorr_sig_recover(&ctx->ecmult_ctx, sig64, &q, secp256k1_schnorr_msghash_sha256, msg32)) { + return 0; + } + ret = secp256k1_eckey_pubkey_serialize(&q, pubkey, pubkeylen, compressed); + VERIFY_CHECK(ret == 1); + return ret; +} + +int secp256k1_schnorr_generate_nonce_pair(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sec32, secp256k1_nonce_function_t noncefp, const void* noncedata, unsigned char *pubnonce33, unsigned char *privnonce32) { + int count = 0; + int ret = 1; + unsigned char msgt32[32]; + int i; + secp256k1_gej_t Qj; + secp256k1_ge_t Q; + secp256k1_scalar_t sec; + + DEBUG_CHECK(ctx != NULL); + DEBUG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + DEBUG_CHECK(msg32 != NULL); + DEBUG_CHECK(sec32 != NULL); + DEBUG_CHECK(pubnonce33 != NULL); + DEBUG_CHECK(privnonce32 != NULL); + + for (i = 0; i < 32; i++) { + /* Tweak the message to prevent a key leak from signing the same message + using different algorithms. */ + msgt32[i] = msg32[i] ^ secp256k1_schnorr_message_tweak[i]; + } + + if (noncefp == NULL) { + noncefp = secp256k1_nonce_function_default; + } + + do { + int overflow; + int pubkeylen = 33; + ret = noncefp(privnonce32, msgt32, sec32, count++, noncedata); + if (!ret) { + break; + } + secp256k1_scalar_set_b32(&sec, privnonce32, &overflow); + if (overflow || secp256k1_scalar_is_zero(&sec)) { + continue; + } + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &sec); + secp256k1_ge_set_gej(&Q, &Qj); + + ret = secp256k1_eckey_pubkey_serialize(&Q, pubnonce33, &pubkeylen, 1); + VERIFY_CHECK(ret); /* This cannot fail, as sec cannot be 0, so Q cannot be infinity. */ + VERIFY_CHECK(pubkeylen == 33); + break; + } while(1); + + memset(msgt32, 0, 32); + secp256k1_scalar_clear(&sec); + return ret; +} + +int secp256k1_ec_pubkey_combine(const secp256k1_context_t* ctx, unsigned char *pubnonce33, int n, const unsigned char **pubnonce33s) { + int i; + secp256k1_gej_t Qj; + secp256k1_ge_t Q; + int pubkeylen = 33; + + (void)ctx; + DEBUG_CHECK(pubnonce33 != NULL); + DEBUG_CHECK(n >= 1); + DEBUG_CHECK(pubnonce33s != NULL); + + secp256k1_gej_set_infinity(&Qj); + + for (i = 0; i < n; i++) { + if (!secp256k1_eckey_pubkey_parse(&Q, pubnonce33s[i], 33)) { + return -1; + } + secp256k1_gej_add_ge(&Qj, &Qj, &Q); + } + secp256k1_ge_set_gej(&Q, &Qj); + return secp256k1_eckey_pubkey_serialize(&Q, pubnonce33, &pubkeylen, 1); +} + +int secp256k1_schnorr_partial_sign(const secp256k1_context_t* ctx, const unsigned char *msg32, unsigned char *sig64, const unsigned char *sec32, const unsigned char *secnonce32, const unsigned char *pubnonce33) { + int overflow = 0; + secp256k1_scalar_t sec, non; + secp256k1_ge_t pubnon; + DEBUG_CHECK(ctx != NULL); + DEBUG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + DEBUG_CHECK(msg32 != NULL); + DEBUG_CHECK(sig64 != NULL); + DEBUG_CHECK(sec32 != NULL); + DEBUG_CHECK(secnonce32 != NULL); + DEBUG_CHECK(pubnonce33 != NULL); + + secp256k1_scalar_set_b32(&sec, sec32, &overflow); + if (overflow || secp256k1_scalar_is_zero(&sec)) { + return -1; + } + secp256k1_scalar_set_b32(&non, secnonce32, &overflow); + if (overflow || secp256k1_scalar_is_zero(&non)) { + return -1; + } + if (!secp256k1_eckey_pubkey_parse(&pubnon, pubnonce33, 33)) { + return -1; + } + return secp256k1_schnorr_sig_sign(&ctx->ecmult_gen_ctx, sig64, &sec, &non, &pubnon, secp256k1_schnorr_msghash_sha256, msg32); +} + +int secp256k1_schnorr_partial_combine(const secp256k1_context_t* ctx, unsigned char *sig64, int n, const unsigned char **sig64sin) { + (void)ctx; + DEBUG_CHECK(sig64 != NULL); + DEBUG_CHECK(n >= 1); + DEBUG_CHECK(sig64sin != NULL); + return secp256k1_schnorr_sig_combine(sig64, n, sig64sin); +} diff --git a/src/tests.c b/src/tests.c index bcd2d62a4a..a41ca5689f 100644 --- a/src/tests.c +++ b/src/tests.c @@ -1235,6 +1235,39 @@ void run_ge(void) { test_add_neg_y_diff_x(); } +void test_ec_combine(void) { + secp256k1_scalar_t sum = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + unsigned char data[6][33]; + const unsigned char* d[6]; + unsigned char sd[33]; + unsigned char sd2[33]; + secp256k1_gej_t Qj; + secp256k1_ge_t Q; + int pubkeylen = 33; + int i; + for (i = 1; i <= 6; i++) { + secp256k1_scalar_t s; + random_scalar_order_test(&s); + secp256k1_scalar_add(&sum, &sum, &s); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &s); + secp256k1_ge_set_gej(&Q, &Qj); + secp256k1_eckey_pubkey_serialize(&Q, data[i - 1], &pubkeylen, 1); + d[i - 1] = data[i - 1]; + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &sum); + secp256k1_ge_set_gej(&Q, &Qj); + secp256k1_eckey_pubkey_serialize(&Q, sd, &pubkeylen, 1); + CHECK(secp256k1_ec_pubkey_combine(ctx, sd2, i, d) == 1); + CHECK(memcmp(sd, sd2, 33) == 0); + } +} + +void run_ec_combine(void) { + int i; + for (i = 0; i < count * 8; i++) { + test_ec_combine(); + } +} + /***** ECMULT TESTS *****/ void run_ecmult_chain(void) { @@ -1564,6 +1597,7 @@ void test_ecdsa_end_to_end(void) { unsigned char message[32]; unsigned char privkey2[32]; unsigned char csignature[64]; + unsigned char schnorr_signature[64]; unsigned char signature[72]; unsigned char signature2[72]; unsigned char signature3[72]; @@ -1579,6 +1613,7 @@ void test_ecdsa_end_to_end(void) { int recpubkeylen = 0; int pubkeylen = 65; int seckeylen = 300; + int ret; /* Generate a random key and message. */ { @@ -1690,6 +1725,20 @@ void test_ecdsa_end_to_end(void) { memcmp(pubkey, recpubkey, pubkeylen) != 0); CHECK(recpubkeylen == pubkeylen); + /* Schnorr sign. */ + CHECK(secp256k1_schnorr_sign(ctx, message, schnorr_signature, privkey, NULL, NULL) == 1); + CHECK(secp256k1_schnorr_verify(ctx, message, schnorr_signature, pubkey, pubkeylen) == 1); + CHECK(secp256k1_schnorr_recover(ctx, message, schnorr_signature, recpubkey, &recpubkeylen, pubkeylen == 33) == 1); + CHECK(recpubkeylen == pubkeylen); + CHECK(memcmp(pubkey, recpubkey, pubkeylen) == 0); + /* Destroy signature and verify again. */ + schnorr_signature[secp256k1_rand32() % 64] += 1 + (secp256k1_rand32() % 255); + CHECK(secp256k1_schnorr_verify(ctx, message, schnorr_signature, pubkey, pubkeylen) == 0); + ret = secp256k1_schnorr_recover(ctx, message, schnorr_signature, recpubkey, &recpubkeylen, pubkeylen == 33); + if (ret) { + CHECK(recpubkeylen == pubkeylen); + CHECK(memcmp(pubkey, recpubkey, pubkeylen) != 0); + } } void test_random_pubkeys(void) { @@ -2092,6 +2141,148 @@ void run_ecdsa_edge_cases(void) { test_ecdsa_edge_cases(); } +/** Horribly broken hash function. Do not use for anything but tests. */ +void test_schnorr_hash(unsigned char *h32, const unsigned char *r32, const unsigned char *msg32) { + int i; + for (i = 0; i < 32; i++) { + h32[i] = r32[i] ^ msg32[i]; + } +} + +void test_schnorr_sign_verify(void) { + unsigned char msg32[32]; + unsigned char sig64[3][64]; + secp256k1_gej_t pubkeyj[3]; + secp256k1_ge_t pubkey[3]; + secp256k1_scalar_t nonce[3], key[3]; + int i = 0; + int k; + + secp256k1_rand256_test(msg32); + + for (k = 0; k < 3; k++) { + random_scalar_order_test(&key[k]); + + do { + random_scalar_order_test(&nonce[k]); + if (secp256k1_schnorr_sig_sign(&ctx->ecmult_gen_ctx, sig64[k], &key[k], &nonce[k], NULL, &test_schnorr_hash, msg32)) { + break; + } + } while(1); + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubkeyj[k], &key[k]); + secp256k1_ge_set_gej_var(&pubkey[k], &pubkeyj[k]); + CHECK(secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64[k], &pubkey[k], &test_schnorr_hash, msg32)); + + for (i = 0; i < 4; i++) { + int pos = secp256k1_rand32() % 64; + int mod = 1 + (secp256k1_rand32() % 255); + sig64[k][pos] ^= mod; + CHECK(secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64[k], &pubkey[k], &test_schnorr_hash, msg32) == 0); + sig64[k][pos] ^= mod; + } + } +} + +void test_schnorr_threshold(void) { + unsigned char msg[32]; + unsigned char sec[5][32]; + unsigned char pub[5][33]; + unsigned char nonce[5][32]; + unsigned char pubnonce[5][33]; + unsigned char sig[5][64]; + const unsigned char* sigs[5]; + unsigned char allsig[64]; + const unsigned char* pubs[5]; + unsigned char allpub[33]; + int n, i; + int damage; + int ret = 0; + + damage = (secp256k1_rand32() % 2) ? (1 + (secp256k1_rand32() % 7)) : 0; + secp256k1_rand256_test(msg); + n = 2 + (secp256k1_rand32() % 4); + for (i = 0; i < n; i++) { + int pubkeylen = 33; + do { + secp256k1_rand256_test(sec[i]); + } while (!secp256k1_ec_seckey_verify(ctx, sec[i])); + CHECK(secp256k1_ec_pubkey_create(ctx, pub[i], &pubkeylen, sec[i], 1)); + CHECK(pubkeylen == 33); + CHECK(secp256k1_schnorr_generate_nonce_pair(ctx, msg, sec[i], NULL, NULL, pubnonce[i], nonce[i])); + pubs[i] = pub[i]; + } + if (damage == 1) { + nonce[secp256k1_rand32() % n][secp256k1_rand32() % 32] ^= 1 + (secp256k1_rand32() % 255); + } else if (damage == 2) { + sec[secp256k1_rand32() % n][secp256k1_rand32() % 32] ^= 1 + (secp256k1_rand32() % 255); + } else if (damage == 3) { + pubnonce[secp256k1_rand32() % n][1 + (secp256k1_rand32() % 32)] ^= 1 + (secp256k1_rand32() % 255); + } else if (damage == 4) { + pub[secp256k1_rand32() % n][1 + (secp256k1_rand32() % 32)] ^= 1 + (secp256k1_rand32() % 255); + } + for (i = 0; i < n; i++) { + unsigned char allpubnonce[33]; + const unsigned char *pubnonces[4]; + int j; + for (j = 0; j < i; j++) { + pubnonces[j] = pubnonce[j]; + } + for (j = i + 1; j < n; j++) { + pubnonces[j - 1] = pubnonce[j]; + } + CHECK(secp256k1_ec_pubkey_combine(ctx, allpubnonce, n - 1, pubnonces)); + ret |= (secp256k1_schnorr_partial_sign(ctx, msg, sig[i], sec[i], nonce[i], allpubnonce) != 1) * 1; + sigs[i] = sig[i]; + } + if (damage == 5) { + sig[secp256k1_rand32() % n][secp256k1_rand32() % 64] ^= 1 + (secp256k1_rand32() % 255); + } + ret |= (secp256k1_ec_pubkey_combine(ctx, allpub, n, pubs) != 1) * 2; + ret |= (secp256k1_schnorr_partial_combine(ctx, allsig, n, sigs) != 1) * 4; + if (damage == 6) { + allsig[secp256k1_rand32() % 32] ^= 1 + (secp256k1_rand32() % 255); + } else if (damage == 7) { + allpub[1 + (secp256k1_rand32() % 32)] ^= 1 + (secp256k1_rand32() % 255); + } + ret |= (secp256k1_schnorr_verify(ctx, msg, allsig, allpub, 33) != 1) * 8; + CHECK((ret == 0) == (damage == 0)); +} + +void test_schnorr_recovery(void) { + unsigned char msg32[32]; + unsigned char sig64[64]; + secp256k1_ge_t Q; + + secp256k1_rand256_test(msg32); + secp256k1_rand256_test(sig64); + secp256k1_rand256_test(sig64 + 32); + if (secp256k1_schnorr_sig_recover(&ctx->ecmult_ctx, sig64, &Q, &test_schnorr_hash, msg32) == 1) { + CHECK(secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64, &Q, &test_schnorr_hash, msg32) == 1); + } +} + +void run_schnorr_sign_verify(void) { + int i; + for (i = 0; i < 32 * count; i++) { + test_schnorr_sign_verify(); + } +} + +void run_schnorr_recovery(void) { + int i; + for (i = 0; i < 16 * count; i++) { + test_schnorr_recovery(); + } +} + +void run_schnorr_threshold(void) { + int i; + for (i = 0; i < 10 * count; i++) { + test_schnorr_threshold(); + } +} + #ifdef ENABLE_OPENSSL_TESTS EC_KEY *get_openssl_key(const secp256k1_scalar_t *key) { unsigned char privkey[300]; @@ -2227,6 +2418,7 @@ int main(int argc, char **argv) { run_ecmult_chain(); run_ecmult_constants(); run_ecmult_gen_blind(); + run_ec_combine(); /* ecdsa tests */ run_random_pubkeys(); @@ -2237,6 +2429,11 @@ int main(int argc, char **argv) { run_ecdsa_openssl(); #endif + /* Schnorr tests */ + run_schnorr_sign_verify(); + run_schnorr_recovery(); + run_schnorr_threshold(); + secp256k1_rand256(run32); printf("random run = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", run32[0], run32[1], run32[2], run32[3], run32[4], run32[5], run32[6], run32[7], run32[8], run32[9], run32[10], run32[11], run32[12], run32[13], run32[14], run32[15]);