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/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 26358533f6..c7d237bc8c 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,63 @@ 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; + +/** 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. + * 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 @@ -63,34 +122,65 @@ 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 * 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_extraparams; + secp256k1_schnorrsig_s2c_opening* s2c_opening; + const unsigned char* s2c_data32; +} 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. * @@ -183,6 +273,82 @@ 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); + + +/** 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/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/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 4e7b45a045..1320956ea5 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -11,6 +11,63 @@ #include "../../../include/secp256k1_schnorrsig.h" #include "../../hash.h" +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; +} + +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) { @@ -47,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; @@ -129,16 +187,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); @@ -146,6 +206,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; @@ -161,6 +227,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); @@ -169,12 +254,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); @@ -194,7 +294,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) { @@ -204,16 +304,27 @@ 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) { 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; + 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); + 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) { @@ -264,4 +375,101 @@ 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); +} + +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 90337ff03e..e71d757840 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; @@ -845,6 +881,7 @@ static 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]; @@ -881,6 +918,11 @@ static 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 @@ -1000,7 +1042,182 @@ 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_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]; + /* 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); +} + +/* 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(); @@ -1010,8 +1227,13 @@ static 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(); + test_s2c_anti_exfil(); } #endif 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();