From 4ae2cd521f6ce496ff296d045e6613914df715f7 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 1 Nov 2018 10:31:24 +0000 Subject: [PATCH 1/5] add eccommit functionality The files are copied from: - https://github.com/ElementsProject/secp256k1-zkp/blob/d22774e248c703a191049b78f8d04f37d6fcfa05/src/eccommit.h - https://github.com/ElementsProject/secp256k1-zkp/blob/d22774e248c703a191049b78f8d04f37d6fcfa05/src/eccommit_impl.h The test is copied from: https://github.com/ElementsProject/secp256k1-zkp/blob/d22774e248c703a191049b78f8d04f37d6fcfa05/src/tests.c#L3755 Originally introduced in: https://github.com/ElementsProject/secp256k1-zkp/commit/826bd04b43f823813c633449223595031d5c31f7, where it was used to implement sign-to-contract for ECDSA. Co-authored-by: Marko Bencun Co-authored-by: Jonas Nick --- Makefile.am | 2 ++ src/eccommit.h | 28 ++++++++++++++++ src/eccommit_impl.h | 73 ++++++++++++++++++++++++++++++++++++++++ src/secp256k1.c | 1 + src/tests.c | 81 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/eccommit.h create mode 100644 src/eccommit_impl.h diff --git a/Makefile.am b/Makefile.am index 32bc729a41..d69faf558f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,6 +18,8 @@ noinst_HEADERS += src/scalar_8x32_impl.h noinst_HEADERS += src/scalar_low_impl.h noinst_HEADERS += src/group.h noinst_HEADERS += src/group_impl.h +noinst_HEADERS += src/eccommit.h +noinst_HEADERS += src/eccommit_impl.h noinst_HEADERS += src/ecdsa.h noinst_HEADERS += src/ecdsa_impl.h noinst_HEADERS += src/eckey.h diff --git a/src/eccommit.h b/src/eccommit.h new file mode 100644 index 0000000000..6bb1103990 --- /dev/null +++ b/src/eccommit.h @@ -0,0 +1,28 @@ +/********************************************************************** + * Copyright (c) 2020 The libsecp256k1-zkp Developers * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_ECCOMMIT_H +#define SECP256K1_ECCOMMIT_H + +/** Helper function to add a 32-byte value to a scalar */ +static int secp256k1_ec_seckey_tweak_add_helper(secp256k1_scalar *sec, const unsigned char *tweak); +/** Helper function to add a 32-byte value, times G, to an EC point */ +static int secp256k1_ec_pubkey_tweak_add_helper(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge *p, const unsigned char *tweak); + +/** Serializes elem as a 33 byte array. This is non-constant time with respect to + * whether pubp is the point at infinity. Thus, you may need to declassify + * pubp->infinity before calling this function. */ +static int secp256k1_ec_commit_pubkey_serialize_const(secp256k1_ge *pubp, unsigned char *buf33); +/** Compute an ec commitment tweak as hash(pubkey, data). */ +static int secp256k1_ec_commit_tweak(unsigned char *tweak32, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); +/** Compute an ec commitment as pubkey + hash(pubkey, data)*G. */ +static int secp256k1_ec_commit(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); +/** Compute a secret key commitment as seckey + hash(pubkey, data). */ +static int secp256k1_ec_commit_seckey(const secp256k1_ecmult_gen_context* ecmult_gen_ctx, secp256k1_scalar* seckey, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); +/** Verify an ec commitment as pubkey + hash(pubkey, data)*G ?= commitment. */ +static int secp256k1_ec_commit_verify(const secp256k1_ecmult_context* ecmult_ctx, const secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); + +#endif /* SECP256K1_ECCOMMIT_H */ diff --git a/src/eccommit_impl.h b/src/eccommit_impl.h new file mode 100644 index 0000000000..2dc24257b2 --- /dev/null +++ b/src/eccommit_impl.h @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (c) 2020 The libsecp256k1 Developers * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include + +#include "eckey.h" +#include "hash.h" + +/* from secp256k1.c */ +static int secp256k1_ec_seckey_tweak_add_helper(secp256k1_scalar *sec, const unsigned char *tweak); +static int secp256k1_ec_pubkey_tweak_add_helper(secp256k1_ge *pubp, const unsigned char *tweak); + +static int secp256k1_ec_commit_pubkey_serialize_const(secp256k1_ge *pubp, unsigned char *buf33) { + if (secp256k1_ge_is_infinity(pubp)) { + return 0; + } + secp256k1_fe_normalize(&pubp->x); + secp256k1_fe_normalize(&pubp->y); + secp256k1_fe_get_b32(&buf33[1], &pubp->x); + buf33[0] = secp256k1_fe_is_odd(&pubp->y) ? SECP256K1_TAG_PUBKEY_ODD : SECP256K1_TAG_PUBKEY_EVEN; + return 1; +} + +/* Compute an ec commitment tweak as hash(pubp, data). */ +static int secp256k1_ec_commit_tweak(unsigned char *tweak32, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) +{ + unsigned char rbuf[33]; + + if (!secp256k1_ec_commit_pubkey_serialize_const(pubp, rbuf)) { + return 0; + } + secp256k1_sha256_write(sha, rbuf, sizeof(rbuf)); + secp256k1_sha256_write(sha, data, data_size); + secp256k1_sha256_finalize(sha, tweak32); + return 1; +} + +/* Compute an ec commitment as pubp + hash(pubp, data)*G. */ +static int secp256k1_ec_commit(secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + + *commitp = *pubp; + return secp256k1_ec_commit_tweak(tweak, commitp, sha, data, data_size) + && secp256k1_ec_pubkey_tweak_add_helper(commitp, tweak); +} + +/* Compute the seckey of an ec commitment from the original secret key of the pubkey as seckey + + * hash(pubp, data). */ +static int secp256k1_ec_commit_seckey(secp256k1_scalar* seckey, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + return secp256k1_ec_commit_tweak(tweak, pubp, sha, data, data_size) + && secp256k1_ec_seckey_tweak_add_helper(seckey, tweak); +} + +/* Verify an ec commitment as pubp + hash(pubp, data)*G ?= commitment. */ +static int secp256k1_ec_commit_verify(const secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) { + secp256k1_gej pj; + secp256k1_ge p; + + if (!secp256k1_ec_commit(&p, pubp, sha, data, data_size)) { + return 0; + } + + /* Return p == commitp */ + secp256k1_ge_neg(&p, &p); + secp256k1_gej_set_ge(&pj, &p); + secp256k1_gej_add_ge_var(&pj, &pj, commitp, NULL); + return secp256k1_gej_is_infinity(&pj); +} + diff --git a/src/secp256k1.c b/src/secp256k1.c index 4c11e7f0b8..8847a0452d 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -27,6 +27,7 @@ #include "field_impl.h" #include "scalar_impl.h" #include "group_impl.h" +#include "eccommit_impl.h" #include "ecmult_impl.h" #include "ecmult_const_impl.h" #include "ecmult_gen_impl.h" diff --git a/src/tests.c b/src/tests.c index e5a47ac98e..cf35100a86 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4166,7 +4166,85 @@ static void run_ec_combine(void) { } } -static void test_group_decompress(const secp256k1_fe* x) { +void test_ec_commit(void) { + secp256k1_scalar seckey_s; + secp256k1_ge pubkey; + secp256k1_gej pubkeyj; + secp256k1_ge commitment; + unsigned char data[32]; + secp256k1_sha256 sha; + + /* Create random keypair and data */ + random_scalar_order_test(&seckey_s); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pubkeyj, &seckey_s); + secp256k1_ge_set_gej(&pubkey, &pubkeyj); + secp256k1_testrand256_test(data); + + /* Commit to data and verify */ + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit(&commitment, &pubkey, &sha, data, 32) == 1); + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_verify(&commitment, &pubkey, &sha, data, 32) == 1); + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_seckey(&seckey_s, &pubkey, &sha, data, 32) == 1); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pubkeyj, &seckey_s); + ge_equals_gej(&commitment, &pubkeyj); + + /* Check that verification fails with different data */ + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_verify(&commitment, &pubkey, &sha, data, 31) == 0); + + /* Check that commmitting fails when the inner pubkey is the point at + * infinity */ + secp256k1_sha256_initialize(&sha); + secp256k1_ge_set_infinity(&pubkey); + CHECK(secp256k1_ec_commit(&commitment, &pubkey, &sha, data, 32) == 0); + secp256k1_scalar_set_int(&seckey_s, 0); + CHECK(secp256k1_ec_commit_seckey(&seckey_s, &pubkey, &sha, data, 32) == 0); + CHECK(secp256k1_ec_commit_verify(&commitment, &pubkey, &sha, data, 32) == 0); +} + + +void test_ec_commit_api(void) { + unsigned char seckey[32]; + secp256k1_scalar seckey_s; + secp256k1_ge pubkey; + secp256k1_gej pubkeyj; + secp256k1_ge commitment; + unsigned char data[32]; + secp256k1_sha256 sha; + + memset(data, 23, sizeof(data)); + + /* Create random keypair */ + random_scalar_order_test(&seckey_s); + secp256k1_scalar_get_b32(seckey, &seckey_s); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pubkeyj, &seckey_s); + secp256k1_ge_set_gej(&pubkey, &pubkeyj); + + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit(&commitment, &pubkey, &sha, data, 1) == 1); + /* The same pubkey can be both input and output of the function */ + { + secp256k1_ge pubkey_tmp = pubkey; + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit(&pubkey_tmp, &pubkey_tmp, &sha, data, 1) == 1); + ge_equals_ge(&commitment, &pubkey_tmp); + } + + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_verify(&commitment, &pubkey, &sha, data, 1) == 1); +} + +void run_ec_commit(void) { + int i; + for (i = 0; i < COUNT * 8; i++) { + test_ec_commit(); + } + test_ec_commit_api(); +} + +void test_group_decompress(const secp256k1_fe* x) { /* The input itself, normalized. */ secp256k1_fe fex = *x; /* Results of set_xo_var(..., 0), set_xo_var(..., 1). */ @@ -7819,6 +7897,7 @@ int main(int argc, char **argv) { run_ecmult_const_tests(); run_ecmult_multi_tests(); run_ec_combine(); + run_ec_commit(); /* endomorphism tests */ run_endomorphism_tests(); From 999c82f7d7219370dcfb5ff36457de002393d0c3 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Wed, 13 Feb 2019 14:15:23 +0000 Subject: [PATCH 2/5] add schnorr sign-to-contract opening with parse/ serialize functions Adapted from https://github.com/bitcoin-core/secp256k1/pull/589/. Co-authored-by: Marko Bencun --- include/secp256k1_schnorrsig.h | 56 +++++++++++++++++++++++++ src/modules/schnorrsig/main_impl.h | 54 ++++++++++++++++++++++++ src/modules/schnorrsig/tests_impl.h | 64 ++++++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 1 deletion(-) diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 26358533f6..231e978204 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -1,6 +1,8 @@ #ifndef SECP256K1_SCHNORRSIG_H #define SECP256K1_SCHNORRSIG_H +#include + #include "secp256k1.h" #include "secp256k1_extrakeys.h" @@ -13,6 +15,60 @@ extern "C" { * (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). */ +/** Data structure that holds a sign-to-contract ("s2c") opening information. + * Sign-to-contract allows a signer to commit to some data as part of a signature. It + * can be used as an Out-argument in certain signing functions. + * + * This structure is not opaque, but it is strongly discouraged to read or write to + * it directly. + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. It can + * be safely copied/moved. + */ +typedef struct { + /* magic is set during initialization */ + uint64_t magic; + /* Public nonce before applying the sign-to-contract commitment */ + secp256k1_pubkey original_pubnonce; + /* Byte indicating if signing algorithm negated the nonce. Alternatively when + * verifying we could compute the EC commitment of original_pubnonce and the + * data and negate if this would not be a valid nonce. But this would prevent + * batch verification of sign-to-contract commitments. */ + int nonce_is_negated; +} secp256k1_schnorrsig_s2c_opening; + +/** Parse a sign-to-contract opening. + * + * Returns: 1 if the opening was fully valid. + * 0 if the opening could not be parsed or is invalid. + * Args: ctx: a secp256k1 context object. + * Out: opening: pointer to an opening object. If 1 is returned, it is set to a + * parsed version of input. If not, its value is undefined. + * In: input33: pointer to 33-byte array with a serialized opening + * + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_s2c_opening_parse( + const secp256k1_context* ctx, + secp256k1_schnorrsig_s2c_opening* opening, + const unsigned char *input33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a sign-to-contract opening into a byte sequence. + * + * Returns: 1 if the opening was successfully serialized. + * 0 if the opening was not initializaed. + * Args: ctx: a secp256k1 context object. + * Out: output33: pointer to a 33-byte array to place the serialized opening + * in. + * In: opening: a pointer to an initialized `secp256k1_schnorrsig_s2c_opening`. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_s2c_opening_serialize( + const secp256k1_context* ctx, + unsigned char *output33, + const secp256k1_schnorrsig_s2c_opening* opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + /** A pointer to a function to deterministically generate a nonce. * * Same as secp256k1_nonce function with the exception of accepting an diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 4e7b45a045..39590abcb4 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -11,6 +11,60 @@ #include "../../../include/secp256k1_schnorrsig.h" #include "../../hash.h" +static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL; + +static void secp256k1_schnorrsig_s2c_opening_init(secp256k1_schnorrsig_s2c_opening *opening) { + opening->magic = s2c_opening_magic; + opening->nonce_is_negated = 0; +} + +static int secp256k1_schnorrsig_s2c_commit_is_init(const secp256k1_schnorrsig_s2c_opening *opening) { + return opening->magic == s2c_opening_magic; +} + +/* s2c_opening is serialized as 33 bytes containing the compressed original pubnonce. In addition to + * holding the EVEN or ODD tag, the first byte has the third bit set to 1 if the nonce was negated. + * The remaining bits in the first byte are 0. */ +int secp256k1_schnorrsig_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_schnorrsig_s2c_opening* opening, const unsigned char *input33) { + unsigned char pk_ser[33]; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(input33 != NULL); + + secp256k1_schnorrsig_s2c_opening_init(opening); + /* Return 0 if unknown bits are set */ + if ((input33[0] & ~0x06) != 0) { + return 0; + } + /* Read nonce_is_negated bit */ + opening->nonce_is_negated = input33[0] & (1 << 2); + memcpy(pk_ser, input33, sizeof(pk_ser)); + /* Unset nonce_is_negated bit to allow parsing the public key */ + pk_ser[0] &= ~(1 << 2); + return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &pk_ser[0], 33); +} + +int secp256k1_schnorrsig_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_schnorrsig_s2c_opening* opening) { + size_t outputlen = 33; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output33 != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(secp256k1_schnorrsig_s2c_commit_is_init(opening)); + + if (!secp256k1_ec_pubkey_serialize(ctx, &output33[0], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED)) { + return 0; + } + /* Verify that ec_pubkey_serialize only sets the first two bits of the + * first byte, otherwise this function doesn't make any sense */ + VERIFY_CHECK(output33[0] == 0x02 || output33[0] == 0x03); + if (opening->nonce_is_negated) { + /* Set nonce_is_negated bit */ + output33[0] |= (1 << 2); + } + return 1; +} + /* Initializes SHA256 with fixed midstate. This midstate was computed by applying * SHA256 to SHA256("BIP0340/nonce")||SHA256("BIP0340/nonce"). */ static void secp256k1_nonce_function_bip340_sha256_tagged(secp256k1_sha256 *sha) { diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 90337ff03e..4d72bdcc4d 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -1000,7 +1000,68 @@ static void test_schnorrsig_taproot(void) { CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); } -static void run_schnorrsig_tests(void) { +void test_s2c_opening(void) { + int i = 0; + unsigned char output[33]; + /* First byte 0x06 means that nonce_is_negated and EVEN tag for the + * following compressed pubkey (which is valid). */ + unsigned char input[33] = { + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02 + }; + secp256k1_schnorrsig_s2c_opening opening; + int32_t ecount = 0; + + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); + + /* Uninitialized opening can't be serialized. Actually testing that would be + * undefined behavior. Therefore we simulate it by setting the opening to 0. */ + memset(&opening, 0, sizeof(opening)); + CHECK(ecount == 0); + CHECK(secp256k1_schnorrsig_s2c_opening_serialize(CTX, output, &opening) == 0); + CHECK(ecount == 1); + + /* First parsing, then serializing works */ + CHECK(secp256k1_schnorrsig_s2c_opening_parse(CTX, &opening, input) == 1); + CHECK(secp256k1_schnorrsig_s2c_opening_serialize(CTX, output, &opening) == 1); + CHECK(secp256k1_schnorrsig_s2c_opening_parse(CTX, &opening, input) == 1); + + { + /* Invalid pubkey makes parsing fail */ + unsigned char input_tmp[33]; + memcpy(input_tmp, input, sizeof(input_tmp)); + /* Pubkey oddness tag is invalid */ + input_tmp[0] = 0; + CHECK(secp256k1_schnorrsig_s2c_opening_parse(CTX, &opening, input_tmp) == 0); + /* nonce_is_negated bit is set but pubkey oddness tag is invalid */ + input_tmp[0] = 5; + CHECK(secp256k1_schnorrsig_s2c_opening_parse(CTX, &opening, input_tmp) == 0); + /* Unknown bit is set */ + input_tmp[0] = 8; + CHECK(secp256k1_schnorrsig_s2c_opening_parse(CTX, &opening, input_tmp) == 0); + } + + /* Try parsing and serializing a bunch of openings */ + do { + /* This is expected to fail in about 50% of iterations because the + * points' x-coordinates are uniformly random */ + if (secp256k1_schnorrsig_s2c_opening_parse(CTX, &opening, input) == 1) { + CHECK(secp256k1_schnorrsig_s2c_opening_serialize(CTX, output, &opening) == 1); + CHECK(memcmp(output, input, sizeof(output)) == 0); + } + secp256k1_testrand256(&input[1]); + /* Set pubkey oddness tag to first bit of input[1] */ + input[0] = (input[1] & 1) + 2; + /* Set nonce_is_negated bit to input[1]'s 3rd bit */ + input[0] |= (input[1] & (1 << 2)); + i++; + } while(i < COUNT); +} + +void run_schnorrsig_tests(void) { int i; run_nonce_function_bip340_tests(); @@ -1012,6 +1073,7 @@ static void run_schnorrsig_tests(void) { test_schnorrsig_sign_verify(); } test_schnorrsig_taproot(); + test_s2c_opening(); } #endif From a8c192ddcff460e71a69171dd0422f3af88e6b20 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Sun, 11 Sep 2022 12:55:13 +0200 Subject: [PATCH 3/5] allow creating and verifying Schnorr sign-to-contract commitments Adapted from https://github.com/bitcoin-core/secp256k1/pull/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 --- include/secp256k1_schnorrsig.h | 44 +++++-- src/modules/schnorrsig/main_impl.h | 85 +++++++++++- src/modules/schnorrsig/tests_impl.h | 197 +++++++++++++++++++++------- 3 files changed, 270 insertions(+), 56 deletions(-) diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 231e978204..ae05ae0df1 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -125,26 +125,36 @@ SECP256K1_API const secp256k1_nonce_function_hardened secp256k1_nonce_function_b * setting it to SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT. * * Members: - * magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization - * and has no other function than making sure the object is - * initialized. - * 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 - * pointer to 32-byte auxiliary randomness as per BIP-340. + * magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization + * and has no other function than making sure the object is + * initialized. + * 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 + * pointer to 32-byte auxiliary randomness as per BIP-340. + * s2c_opening: pointer to an secp256k1_schnorrsig_s2c_opening structure which can be + * NULL but is required to be not NULL if this signature creates + * a sign-to-contract commitment (i.e. the `s2c_data32` argument + * is not NULL). + * s2c_data32: pointer to a 32-byte data to create an optional + * sign-to-contract commitment to if not NULL (can be NULL). */ typedef struct { unsigned char magic[4]; secp256k1_nonce_function_hardened noncefp; void *ndata; + secp256k1_schnorrsig_s2c_opening* s2c_opening; + const unsigned char* s2c_data32; } secp256k1_schnorrsig_extraparams; #define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC { 0xda, 0x6f, 0xb3, 0x8c } #define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT {\ SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC,\ NULL,\ + NULL,\ + NULL,\ NULL\ } @@ -239,6 +249,22 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify( const secp256k1_xonly_pubkey *pubkey ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5); +/** Verify a sign-to-contract commitment. + * + * Returns: 1: the signature contains a commitment to data32 + * 0: incorrect opening + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig64: the signature containing the sign-to-contract commitment (cannot be NULL) + * data32: the 32-byte data that was committed to (cannot be NULL) + * opening: pointer to the opening created during signing (cannot be NULL) + */ +SECP256K1_API int secp256k1_schnorrsig_verify_s2c_commit( + const secp256k1_context* ctx, + const unsigned char *sig64, + const unsigned char *data32, + const secp256k1_schnorrsig_s2c_opening *opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 39590abcb4..b0c9bfe07c 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -13,6 +13,9 @@ static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL; +static const unsigned char s2c_data_tag[16] = "s2c/schnorr/data"; +static const unsigned char s2c_point_tag[17] = "s2c/schnorr/point"; + static void secp256k1_schnorrsig_s2c_opening_init(secp256k1_schnorrsig_s2c_opening *opening) { opening->magic = s2c_opening_magic; opening->nonce_is_negated = 0; @@ -183,16 +186,18 @@ static void secp256k1_schnorrsig_challenge(secp256k1_scalar* e, const unsigned c secp256k1_scalar_set_b32(e, buf, NULL); } -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) { +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) { secp256k1_scalar sk; secp256k1_scalar e; secp256k1_scalar k; secp256k1_gej rj; secp256k1_ge pk; secp256k1_ge r; + secp256k1_sha256 sha; unsigned char buf[32] = { 0 }; unsigned char pk_buf[32]; unsigned char seckey[32]; + unsigned char noncedata[32]; int ret = 1; VERIFY_CHECK(ctx != NULL); @@ -200,6 +205,12 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi ARG_CHECK(sig64 != NULL); ARG_CHECK(msg != NULL || msglen == 0); ARG_CHECK(keypair != NULL); + /* sign-to-contract commitments only work with the default nonce function, + * because we need to ensure that s2c_data is actually hashed into the nonce and + * not just ignored because otherwise this could result in nonce reuse. */ + ARG_CHECK(s2c_data32 == NULL || (noncefp == NULL || noncefp == secp256k1_nonce_function_bip340)); + /* s2c_opening and s2c_data32 should be either both non-NULL or both NULL. */ + ARG_CHECK((s2c_opening != NULL) == (s2c_data32 != NULL)); if (noncefp == NULL) { noncefp = secp256k1_nonce_function_bip340; @@ -215,6 +226,25 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi secp256k1_scalar_get_b32(seckey, &sk); secp256k1_fe_get_b32(pk_buf, &pk.x); + + if (s2c_data32 != NULL) { + /* Provide s2c_data32 and ndata (if not NULL) to the the nonce function + * as additional data to derive the nonce from. If both pointers are + * not NULL, they need to be hashed to get the nonce data 32 bytes. + * Even if only s2c_data32 is not NULL, it's hashed because it should + * be possible to derive nonces even if only a SHA256 commitment to the + * data is known. This is for example important in the anti nonce + * sidechannel protocol. + */ + secp256k1_sha256_initialize_tagged(&sha, s2c_data_tag, sizeof(s2c_data_tag)); + secp256k1_sha256_write(&sha, s2c_data32, 32); + if (ndata != NULL) { + secp256k1_sha256_write(&sha, ndata, 32); + } + secp256k1_sha256_finalize(&sha, noncedata); + ndata = &noncedata; + } + 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); @@ -223,12 +253,27 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); secp256k1_ge_set_gej(&r, &rj); + if (s2c_opening != NULL) { + secp256k1_schnorrsig_s2c_opening_init(s2c_opening); + if (s2c_data32 != NULL) { + secp256k1_sha256_initialize_tagged(&sha, s2c_point_tag, sizeof(s2c_point_tag)); + /* Create sign-to-contract commitment */ + secp256k1_pubkey_save(&s2c_opening->original_pubnonce, &r); + secp256k1_ec_commit_seckey(&k, &r, &sha, s2c_data32, 32); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); + secp256k1_ge_set_gej(&r, &rj); + } + } + /* We declassify r to allow using it as a branch point. This is fine * because r is not a secret. */ secp256k1_declassify(ctx, &r, sizeof(r)); secp256k1_fe_normalize_var(&r.y); if (secp256k1_fe_is_odd(&r.y)) { secp256k1_scalar_negate(&k, &k); + if (s2c_opening != NULL) { + s2c_opening->nonce_is_negated = 1; + } } secp256k1_fe_normalize_var(&r.x); secp256k1_fe_get_b32(&sig64[0], &r.x); @@ -248,7 +293,7 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi int secp256k1_schnorrsig_sign32(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) { /* We cast away const from the passed aux_rand32 argument since we know the default nonce function does not modify it. */ - return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32); + return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32, NULL, NULL); } 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 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) { secp256k1_nonce_function_hardened noncefp = NULL; void *ndata = NULL; + secp256k1_schnorrsig_s2c_opening *s2c_opening = NULL; + const unsigned char *s2c_data32 = NULL; VERIFY_CHECK(ctx != NULL); if (extraparams != NULL) { @@ -266,8 +313,10 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char sizeof(extraparams->magic)) == 0); noncefp = extraparams->noncefp; ndata = extraparams->ndata; + s2c_opening = extraparams->s2c_opening; + s2c_data32 = extraparams->s2c_data32; } - return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata); + return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata, s2c_opening, s2c_data32); } 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 secp256k1_fe_equal_var(&rx, &r.x); } +int secp256k1_schnorrsig_verify_s2c_commit(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *data32, const secp256k1_schnorrsig_s2c_opening *opening) { + secp256k1_fe rx; + secp256k1_ge original_r; + secp256k1_ge r; + secp256k1_sha256 sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(data32 != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(secp256k1_schnorrsig_s2c_commit_is_init(opening)); + + if (!secp256k1_fe_set_b32_limit(&rx, &sig64[0])) { + return 0; + } + if (!secp256k1_ge_set_xo_var(&r, &rx, 0)) { + return 0; + } + if (opening->nonce_is_negated) { + secp256k1_ge_neg(&r, &r); + } + + if (!secp256k1_pubkey_load(ctx, &original_r, &opening->original_pubnonce)) { + return 0; + } + + secp256k1_sha256_initialize_tagged(&sha, s2c_point_tag, sizeof(s2c_point_tag)); + return secp256k1_ec_commit_verify(&r, &original_r, &sha, data32, 32); +} + #endif diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 4d72bdcc4d..b9123d2d31 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -103,18 +103,62 @@ static void run_nonce_function_bip340_tests(void) { CHECK(secp256k1_memcmp_var(nonce_z, nonce, 32) == 0); } -static void test_schnorrsig_api(void) { +/* Nonce function that returns constant 0 */ +static int nonce_function_failing(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) { + (void) msg; + (void) msglen; + (void) key32; + (void) xonly_pk32; + (void) algo; + (void) algolen; + (void) data; + (void) nonce32; + return 0; +} + +/* Nonce function that sets nonce to 0 */ +static int nonce_function_0(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) { + (void) msg; + (void) msglen; + (void) key32; + (void) xonly_pk32; + (void) algo; + (void) algolen; + (void) data; + + memset(nonce32, 0, 32); + return 1; +} + +/* Nonce function that sets nonce to 0xFF...0xFF */ +static int nonce_function_overflowing(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) { + (void) msg; + (void) msglen; + (void) key32; + (void) xonly_pk32; + (void) algo; + (void) algolen; + (void) data; + + memset(nonce32, 0xFF, 32); + return 1; +} + +void test_schnorrsig_api(void) { unsigned char sk1[32]; unsigned char sk2[32]; unsigned char sk3[32]; unsigned char msg[32]; + unsigned char s2c_data32[32]; + secp256k1_schnorrsig_s2c_opening s2c_opening; secp256k1_keypair keypairs[3]; secp256k1_keypair invalid_keypair = {{ 0 }}; secp256k1_xonly_pubkey pk[3]; secp256k1_xonly_pubkey zero_pk; unsigned char sig[64]; secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; - secp256k1_schnorrsig_extraparams invalid_extraparams = {{ 0 }, NULL, NULL}; + secp256k1_schnorrsig_extraparams extraparams_s2c; + secp256k1_schnorrsig_extraparams invalid_extraparams = {{ 0 }, NULL, NULL, NULL, NULL}; /** setup **/ int ecount = 0; @@ -128,6 +172,7 @@ static void test_schnorrsig_api(void) { secp256k1_testrand256(sk2); secp256k1_testrand256(sk3); secp256k1_testrand256(msg); + secp256k1_testrand256(s2c_data32); CHECK(secp256k1_keypair_create(CTX, &keypairs[0], sk1) == 1); CHECK(secp256k1_keypair_create(CTX, &keypairs[1], sk2) == 1); CHECK(secp256k1_keypair_create(CTX, &keypairs[2], sk3) == 1); @@ -170,6 +215,22 @@ static void test_schnorrsig_api(void) { CHECK(ecount == 5); CHECK(secp256k1_schnorrsig_sign_custom(STATIC_CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 0); CHECK(ecount == 6); + extraparams_s2c = extraparams; + extraparams_s2c.s2c_opening = &s2c_opening; + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams_s2c) == 0); + CHECK(ecount == 7); + extraparams_s2c = extraparams; + extraparams_s2c.s2c_opening = &s2c_opening; + extraparams_s2c.s2c_data32 = s2c_data32; + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams_s2c) == 1); + CHECK(ecount == 7); + /* s2c commitments with a different nonce function than bipschnorr are not allowed */ + extraparams_s2c = extraparams; + extraparams_s2c.s2c_opening = &s2c_opening; + extraparams_s2c.s2c_data32 = s2c_data32; + extraparams_s2c.noncefp = nonce_function_0; + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams_s2c) == 0); + CHECK(ecount == 8); ecount = 0; CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &keypairs[0], NULL) == 1); @@ -188,6 +249,22 @@ static void test_schnorrsig_api(void) { secp256k1_context_set_error_callback(STATIC_CTX, NULL, NULL); secp256k1_context_set_illegal_callback(STATIC_CTX, NULL, NULL); + + /* Create sign-to-contract commitment to data32 for testing verify_s2c_commit */ + ecount = 0; + extraparams_s2c = extraparams; + extraparams_s2c.s2c_opening = &s2c_opening; + extraparams_s2c.s2c_data32 = s2c_data32; + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams_s2c) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig, s2c_data32, &s2c_opening) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, NULL, s2c_data32, &s2c_opening) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig, NULL, &s2c_opening) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig, s2c_data32, NULL) == 0); + CHECK(ecount == 3); } /* Checks that hash initialized by secp256k1_schnorrsig_sha256_tagged has the @@ -796,48 +873,7 @@ static void test_schnorrsig_bip_vectors(void) { } } -/* Nonce function that returns constant 0 */ -static int nonce_function_failing(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) { - (void) msg; - (void) msglen; - (void) key32; - (void) xonly_pk32; - (void) algo; - (void) algolen; - (void) data; - (void) nonce32; - return 0; -} - -/* Nonce function that sets nonce to 0 */ -static int nonce_function_0(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) { - (void) msg; - (void) msglen; - (void) key32; - (void) xonly_pk32; - (void) algo; - (void) algolen; - (void) data; - - memset(nonce32, 0, 32); - return 1; -} - -/* Nonce function that sets nonce to 0xFF...0xFF */ -static int nonce_function_overflowing(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) { - (void) msg; - (void) msglen; - (void) key32; - (void) xonly_pk32; - (void) algo; - (void) algolen; - (void) data; - - memset(nonce32, 0xFF, 32); - return 1; -} - -static void test_schnorrsig_sign(void) { +void test_schnorrsig_sign(void) { unsigned char sk[32]; secp256k1_xonly_pubkey pk; secp256k1_keypair keypair; @@ -1000,6 +1036,76 @@ static void test_schnorrsig_taproot(void) { CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); } +void test_schnorrsig_s2c_commit_verify(void) { + unsigned char data32[32]; + unsigned char sig[64]; + secp256k1_schnorrsig_s2c_opening s2c_opening; + unsigned char msg[32]; + unsigned char sk[32]; + secp256k1_xonly_pubkey pk; + secp256k1_keypair keypair; + unsigned char noncedata[32]; + secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; + extraparams.ndata = noncedata; + extraparams.s2c_opening = &s2c_opening; + extraparams.s2c_data32 = data32; + + secp256k1_testrand256(data32); + secp256k1_testrand256(msg); + secp256k1_testrand256(sk); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair)); + secp256k1_testrand256(noncedata); + + /* Create and verify correct commitment */ + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk)); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig, data32, &s2c_opening) == 1); + { + /* verify_s2c_commit fails if nonce_is_negated is wrong */ + secp256k1_schnorrsig_s2c_opening s2c_opening_tmp; + s2c_opening_tmp = s2c_opening; + s2c_opening_tmp.nonce_is_negated = !s2c_opening.nonce_is_negated; + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig, data32, &s2c_opening_tmp) == 0); + } + { + /* verify_s2c_commit fails if given data does not match committed data */ + unsigned char data32_tmp[32]; + memcpy(data32_tmp, data32, sizeof(data32_tmp)); + data32_tmp[31] ^= 1; + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig, data32_tmp, &s2c_opening) == 0); + } + { + /* verify_s2c_commit fails if signature does not commit to data */ + unsigned char sig_tmp[64]; + memcpy(sig_tmp, sig, 64); + secp256k1_testrand256(sig_tmp); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig_tmp, data32, &s2c_opening) == 0); + } + { + /* A commitment to different data creates a different original_pubnonce + * (i.e. data is hashed into the nonce) */ + secp256k1_schnorrsig_s2c_opening s2c_opening_tmp; + unsigned char sig_tmp[64]; + unsigned char data32_tmp[32]; + unsigned char serialized_nonce[33]; + unsigned char serialized_nonce_tmp[33]; + size_t outputlen = 33; + secp256k1_schnorrsig_extraparams extraparams_tmp = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; + extraparams_tmp.s2c_opening = &s2c_opening_tmp; + extraparams_tmp.s2c_data32 = data32_tmp; + secp256k1_testrand256(data32_tmp); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig_tmp, msg, sizeof(msg), &keypair, &extraparams_tmp) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig_tmp, msg, sizeof(msg), &pk)); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(CTX, sig_tmp, data32_tmp, &s2c_opening_tmp) == 1); + secp256k1_ec_pubkey_serialize(CTX, serialized_nonce, &outputlen, &s2c_opening.original_pubnonce, SECP256K1_EC_COMPRESSED); + CHECK(outputlen == 33); + secp256k1_ec_pubkey_serialize(CTX, serialized_nonce_tmp, &outputlen, &s2c_opening_tmp.original_pubnonce, SECP256K1_EC_COMPRESSED); + CHECK(outputlen == 33); + CHECK(memcmp(serialized_nonce, serialized_nonce_tmp, outputlen) != 0); + } +} + void test_s2c_opening(void) { int i = 0; unsigned char output[33]; @@ -1071,6 +1177,9 @@ void run_schnorrsig_tests(void) { for (i = 0; i < COUNT; i++) { test_schnorrsig_sign(); test_schnorrsig_sign_verify(); + /* Run multiple times to increase probability that the nonce is negated in + * a test. */ + test_schnorrsig_s2c_commit_verify(); } test_schnorrsig_taproot(); test_s2c_opening(); From 82557b467def663d41299b621d24106a5067e5da Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Sun, 11 Sep 2022 19:20:10 +0200 Subject: [PATCH 4/5] add Schnorr anti-exfil functions These functions allow to perform the anti-exfil protocol. It is very similar to the implementation of the same protocol for ECDSA in ElementsProject/secp256k1-zkp. The opening struct can't be use in `secp256k1_schnorrsig_anti_exfil_signer_commit()` as it contains the ``nonce_is_negated` field, which can only be set correctly during signing with s2c data. As a result, we must use the opening in the commitment verification, so we also must check that the signer commitment is the same as the one used during signing. The alternative is to only compare the x-coordinate, in which case the opening struct could skip `nonce_is_negated` and the struct could be reused in `secp256k1_schnorrsig_anti_exfil_signer_commit()`, but it seems to have a downside that it would prevent batch-verification of the commitments. --- include/secp256k1_schnorrsig.h | 63 +++++++++++++++++++++++++++ src/modules/schnorrsig/main_impl.h | 67 +++++++++++++++++++++++++++++ src/modules/schnorrsig/tests_impl.h | 45 +++++++++++++++++++ 3 files changed, 175 insertions(+) diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index ae05ae0df1..5abe1424b4 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -38,6 +38,9 @@ typedef struct { int nonce_is_negated; } secp256k1_schnorrsig_s2c_opening; +/** The signer commitment in the anti-exfil protocol is the original public nonce. */ +typedef secp256k1_pubkey secp256k1_schnorrsig_anti_exfil_signer_commitment; + /** Parse a sign-to-contract opening. * * Returns: 1 if the opening was fully valid. @@ -265,6 +268,66 @@ SECP256K1_API int secp256k1_schnorrsig_verify_s2c_commit( const secp256k1_schnorrsig_s2c_opening *opening ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create the initial host commitment to `rho`. Part of the Anti-Exfil Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: rand_commitment32: pointer to 32-byte array to store the returned commitment (cannot be NULL) + * In: rand32: the 32-byte randomness to commit to (cannot be NULL). It must come from + * a cryptographically secure RNG. As per the protocol, this value must not + * be revealed to the client until after the host has received the client + * commitment. + */ +SECP256K1_API int secp256k1_schnorrsig_anti_exfil_host_commit( + const secp256k1_context* ctx, + unsigned char* rand_commitment32, + const unsigned char* rand32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + + /** Compute signer's original nonce. Part of the Anti-Exfil Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: signer_commitment: where the signer's public nonce will be placed. (cannot be NULL) + * In: msg: the message to be signed (cannot be NULL) + * msglen: length of the message + * keypair: pointer to an initialized keypair (cannot be NULL). + * rand_commitment32: the 32-byte randomness commitment from the host (cannot be NULL) + */ +SECP256K1_API int secp256k1_schnorrsig_anti_exfil_signer_commit( + const secp256k1_context* ctx, + secp256k1_schnorrsig_anti_exfil_signer_commitment* signer_commitment, + const unsigned char *msg, + size_t msglen, + const secp256k1_keypair *keypair, + const unsigned char* rand_commitment32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Verify a signature was correctly constructed using the Anti-Exfil Protocol. + * + * Returns: 1: the signature is valid and contains a commitment to host_data32 + * 0: failure + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig64: pointer to the 64-byte signature to verify. + * 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) + * host_data32: the 32-byte data provided by the host (cannot be NULL) + * signer_commitment: signer commitment produced by `secp256k1_schnorrsig_anti_exfil_signer_commit()`. + * opening: the s2c opening provided by the signer (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_anti_exfil_host_verify( + const secp256k1_context* ctx, + const unsigned char *sig64, + const unsigned char *msg, + size_t msglen, + const secp256k1_xonly_pubkey *pubkey, + const unsigned char *host_data32, + const secp256k1_schnorrsig_anti_exfil_signer_commitment *signer_commitment, + const secp256k1_schnorrsig_s2c_opening *opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); + #ifdef __cplusplus } #endif diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index b0c9bfe07c..5312432d7b 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -397,4 +397,71 @@ int secp256k1_schnorrsig_verify_s2c_commit(const secp256k1_context* ctx, const u return secp256k1_ec_commit_verify(&r, &original_r, &sha, data32, 32); } +int secp256k1_schnorrsig_anti_exfil_host_commit(const secp256k1_context* ctx, unsigned char* rand_commitment32, const unsigned char* rand32) { + secp256k1_sha256 sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(rand_commitment32 != NULL); + ARG_CHECK(rand32 != NULL); + + secp256k1_sha256_initialize_tagged(&sha, s2c_data_tag, sizeof(s2c_data_tag)); + secp256k1_sha256_write(&sha, rand32, 32); + secp256k1_sha256_finalize(&sha, rand_commitment32); + + return 1; +} + +int secp256k1_schnorrsig_anti_exfil_signer_commit(const secp256k1_context* ctx, secp256k1_schnorrsig_anti_exfil_signer_commitment* signer_commitment, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, const unsigned char* rand_commitment32) { + secp256k1_scalar sk; + secp256k1_scalar k; + secp256k1_gej rj; + secp256k1_ge pk; + secp256k1_ge r; + unsigned char buf[32] = { 0 }; + unsigned char pk_buf[32]; + unsigned char seckey[32]; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(msg != NULL || msglen == 0); + ARG_CHECK(keypair != NULL); + + ret &= secp256k1_keypair_load(ctx, &sk, &pk, keypair); + /* Because we are signing for a x-only pubkey, the secret key is negated + * before signing if the point corresponding to the secret key does not + * have an even Y. */ + if (secp256k1_fe_is_odd(&pk.y)) { + secp256k1_scalar_negate(&sk, &sk); + } + + secp256k1_scalar_get_b32(seckey, &sk); + secp256k1_fe_get_b32(pk_buf, &pk.x); + + ret &= !!secp256k1_nonce_function_bip340(buf, msg, msglen, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), (void*)rand_commitment32); + secp256k1_scalar_set_b32(&k, buf, NULL); + ret &= !secp256k1_scalar_is_zero(&k); + secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret); + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); + secp256k1_ge_set_gej(&r, &rj); + + secp256k1_pubkey_save(signer_commitment, &r); + + return ret; +} + +int secp256k1_schnorrsig_anti_exfil_host_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey, const unsigned char *host_data32, const secp256k1_schnorrsig_anti_exfil_signer_commitment *signer_commitment, const secp256k1_schnorrsig_s2c_opening *opening) { + /* Verify that the signer commitment matches the opening made when signing */ + if (secp256k1_ec_pubkey_cmp(ctx, signer_commitment, &opening->original_pubnonce)) { + return 0; + } + /* Verify signature */ + if (!secp256k1_schnorrsig_verify(ctx, sig64, msg, msglen, pubkey)) { + return 0; + } + /* Verify that the host nonce contribution was committed to */ + return secp256k1_schnorrsig_verify_s2c_commit(ctx, sig64, host_data32, opening); +} + #endif diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index b9123d2d31..81e03e7850 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -1167,6 +1167,50 @@ void test_s2c_opening(void) { } while(i < COUNT); } +/* Uses the s2c primitives to perform the anti-exfil protocol */ +void test_s2c_anti_exfil(void) { + unsigned char sk[32]; + secp256k1_xonly_pubkey pk; + secp256k1_keypair keypair; + unsigned char host_msg[32]; + unsigned char host_commitment[32]; + unsigned char host_nonce_contribution[32]; + secp256k1_schnorrsig_s2c_opening s2c_opening; + secp256k1_schnorrsig_anti_exfil_signer_commitment signer_commitment; + unsigned char sig[64]; + /* Generate a random key, message. */ + { + secp256k1_testrand256(sk); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair)); + secp256k1_testrand256_test(host_msg); + secp256k1_testrand256_test(host_nonce_contribution); + } + + /* Protocol step 1. */ + /* Make host commitment. */ + CHECK(secp256k1_schnorrsig_anti_exfil_host_commit(CTX, host_commitment, host_nonce_contribution) == 1); + + /* Protocol step 2. */ + /* Make signer commitment */ + CHECK(secp256k1_schnorrsig_anti_exfil_signer_commit(CTX, &signer_commitment, host_msg, sizeof(host_msg), &keypair, host_commitment) == 1); + + /* Protocol step 3: host_nonce_contribution send to signer to be used in step 4. */ + + /* Protocol step 4. */ + /* Sign with host nonce contribution */ + { + secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; + extraparams.s2c_opening = &s2c_opening; + extraparams.s2c_data32 = host_nonce_contribution; + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, host_msg, sizeof(host_msg), &keypair, &extraparams) == 1); + } + + /* Protocol step 5. */ + /* Verify commitment and signature */ + CHECK(secp256k1_schnorrsig_anti_exfil_host_verify(CTX, sig, host_msg, sizeof(host_msg), &pk, host_nonce_contribution, &signer_commitment, &s2c_opening) == 1); +} + void run_schnorrsig_tests(void) { int i; run_nonce_function_bip340_tests(); @@ -1183,6 +1227,7 @@ void run_schnorrsig_tests(void) { } test_schnorrsig_taproot(); test_s2c_opening(); + test_s2c_anti_exfil(); } #endif From e6978c4150b06954a02e21efbdbeed796dc739b6 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Sun, 30 Jul 2023 00:28:21 +0200 Subject: [PATCH 5/5] use the magic in the schnorrsig extraparams struct for versioning This ensures compatibility in that it makes sure that the `secp256k1_schnorrsig_sign_custom()` works for users using an older version of the headers but linking against a newer version of the library. --- include/secp256k1_schnorrsig.h | 29 +++++++++++++++++++++++++---- src/modules/schnorrsig/main_impl.h | 16 ++++++++++++---- src/modules/schnorrsig/tests_impl.h | 6 ++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 5abe1424b4..c7d237bc8c 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -122,6 +122,15 @@ typedef int (*secp256k1_nonce_function_hardened)( */ SECP256K1_API const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340; +/** First version of the extraparams struct. See `secp256k1_schnorrsig_extraparams` for the + latest version and its documentation. + */ +typedef struct { + unsigned char magic[4]; + secp256k1_nonce_function_hardened noncefp; + void *ndata; +} secp256k1_schnorrsig_extraparams_v0; + /** Data structure that contains additional arguments for schnorrsig_sign_custom. * * A schnorrsig_extraparams structure object can be initialized correctly by @@ -150,16 +159,28 @@ typedef struct { void *ndata; secp256k1_schnorrsig_s2c_opening* s2c_opening; const unsigned char* s2c_data32; -} secp256k1_schnorrsig_extraparams; +} secp256k1_schnorrsig_extraparams_v1; + +typedef secp256k1_schnorrsig_extraparams_v1 secp256k1_schnorrsig_extraparams; + +#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V0 { 0xda, 0x6f, 0xb3, 0x8c } +#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V1 { 0x05, 0x96, 0x5b, 0x5c } +#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V1 + +#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT_V0 {\ + SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V0,\ + NULL,\ + NULL\ +} -#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC { 0xda, 0x6f, 0xb3, 0x8c } -#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT {\ - SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC,\ +#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT_V1 {\ + SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V1,\ NULL,\ NULL,\ NULL,\ NULL\ } +#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT_V1 /** Create a Schnorr signature. * diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 5312432d7b..1320956ea5 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -104,7 +104,8 @@ 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 const unsigned char schnorrsig_extraparams_magic[4] = SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC; +static const unsigned char schnorrsig_extraparams_magic_v0[4] = SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V0; +static const unsigned char schnorrsig_extraparams_magic_v1[4] = SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC_V1; 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; @@ -309,12 +310,19 @@ int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char if (extraparams != NULL) { ARG_CHECK(secp256k1_memcmp_var(extraparams->magic, - schnorrsig_extraparams_magic, + schnorrsig_extraparams_magic_v0, + sizeof(extraparams->magic)) == 0 || + secp256k1_memcmp_var(extraparams->magic, + schnorrsig_extraparams_magic_v1, sizeof(extraparams->magic)) == 0); noncefp = extraparams->noncefp; ndata = extraparams->ndata; - s2c_opening = extraparams->s2c_opening; - s2c_data32 = extraparams->s2c_data32; + if (secp256k1_memcmp_var(extraparams->magic, + schnorrsig_extraparams_magic_v1, + sizeof(extraparams->magic)) == 0) { + s2c_opening = extraparams->s2c_opening; + s2c_data32 = extraparams->s2c_data32; + } } return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg, msglen, keypair, noncefp, ndata, s2c_opening, s2c_data32); } diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 81e03e7850..e71d757840 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -881,6 +881,7 @@ void test_schnorrsig_sign(void) { unsigned char sig[64]; unsigned char sig2[64]; unsigned char zeros64[64] = { 0 }; + secp256k1_schnorrsig_extraparams_v0 extraparams_v0 = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT_V0; secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; unsigned char aux_rand[32]; @@ -917,6 +918,11 @@ void test_schnorrsig_sign(void) { CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); CHECK(secp256k1_schnorrsig_sign32(CTX, sig2, msg, &keypair, extraparams.ndata) == 1); CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0); + + /* Test extraparams v0 to simulate users using old headers linking against a new version of the library */ + memset(sig, 1, sizeof(sig)); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, (secp256k1_schnorrsig_extraparams*)&extraparams_v0) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk)); } #define N_SIGS 3