diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 5fedcb07b0..68c30331f9 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_schnorr_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_schnorr_s2c_opening_parse( + const secp256k1_context* ctx, + secp256k1_schnorr_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_schnorr_s2c_opening`. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorr_s2c_opening_serialize( + const secp256k1_context* ctx, + unsigned char *output33, + const secp256k1_schnorr_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 cd651591c4..b5979157f0 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_schnorr_s2c_opening_init(secp256k1_schnorr_s2c_opening *opening) { + opening->magic = s2c_opening_magic; + opening->nonce_is_negated = 0; +} + +static int secp256k1_s2c_commit_is_init(const secp256k1_schnorr_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_schnorr_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_schnorr_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_schnorr_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_schnorr_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_schnorr_s2c_opening* opening) { + size_t outputlen = 33; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output33 != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(secp256k1_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) {