diff --git a/include/secp256k1.h b/include/secp256k1.h index 576953f49d..d51e0f36ab 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -6,6 +6,7 @@ extern "C" { #endif #include +#include /* Unless explicitly stated all pointer arguments must not be NULL. * @@ -84,6 +85,29 @@ typedef struct { unsigned char data[64]; } secp256k1_ecdsa_signature; +/** 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_s2c_opening; + /** A pointer to a function to deterministically generate a nonce. * * Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail. @@ -475,6 +499,37 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( const secp256k1_ecdsa_signature* sig ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** 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_s2c_opening_parse( + const secp256k1_context* ctx, + secp256k1_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_s2c_opening`. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_serialize( + const secp256k1_context* ctx, + unsigned char *output33, + const secp256k1_s2c_opening* opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + /** Verify an ECDSA signature. * * Returns: 1: correct signature diff --git a/src/secp256k1.c b/src/secp256k1.c index 9b1141376c..e120825e09 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -788,6 +788,142 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, return 1; } +/* Compute an ec commitment tweak as hash(pubkey, data). */ +static int secp256k1_ec_commit_tweak(const secp256k1_context *ctx, unsigned char *tweak32, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + secp256k1_ge p; + unsigned char rbuf[33]; + size_t rbuf_size = sizeof(rbuf); + secp256k1_sha256 sha; + + if (data_size == 0) { + /* That's probably not what the caller wanted */ + return 0; + } + if(!secp256k1_pubkey_load(ctx, &p, pubkey)) { + return 0; + } + secp256k1_eckey_pubkey_serialize(&p, rbuf, &rbuf_size, 1); + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, rbuf, rbuf_size); + secp256k1_sha256_write(&sha, data, data_size); + secp256k1_sha256_finalize(&sha, tweak32); + return 1; +} + +/* Compute an ec commitment as pubkey + hash(pubkey, data)*G. */ +static int secp256k1_ec_commit(const secp256k1_context* ctx, secp256k1_pubkey *commitment, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + + *commitment = *pubkey; + if (!secp256k1_ec_commit_tweak(ctx, tweak, commitment, data, data_size)) { + return 0; + } + return secp256k1_ec_pubkey_tweak_add(ctx, commitment, tweak); +} + +/* Compute the seckey of an ec commitment from the original secret key of the pubkey as seckey + + * hash(pubkey, data). */ +static int secp256k1_ec_commit_seckey(const secp256k1_context* ctx, unsigned char *seckey, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + secp256k1_pubkey pubkey_tmp; + + if (pubkey == NULL) { + /* Compute pubkey from seckey if not provided */ + int overflow; + secp256k1_scalar x; + secp256k1_gej pj; + secp256k1_ge p; + + secp256k1_scalar_set_b32(&x, seckey, &overflow); + if (overflow != 0) { + return 0; + } + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &x); + secp256k1_ge_set_gej(&p, &pj); + secp256k1_pubkey_save(&pubkey_tmp, &p); + pubkey = &pubkey_tmp; + } + + if (!secp256k1_ec_commit_tweak(ctx, tweak, pubkey, data, data_size)) { + return 0; + } + return secp256k1_ec_privkey_tweak_add(ctx, seckey, tweak); +} + +/* Verify an ec commitment as pubkey + hash(pubkey, data)*G ?= commitment. */ +static int secp256k1_ec_commit_verify(const secp256k1_context* ctx, const secp256k1_pubkey *commitment, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + secp256k1_gej pj; + secp256k1_ge p; + secp256k1_pubkey commitment_tmp; + + if (!secp256k1_ec_commit(ctx, &commitment_tmp, pubkey, data, data_size)) { + return 0; + } + + /* Return commitment == commitment_tmp */ + secp256k1_pubkey_load(ctx, &p, &commitment_tmp); + secp256k1_gej_set_ge(&pj, &p); + secp256k1_pubkey_load(ctx, &p, commitment); + secp256k1_ge_neg(&p, &p); + secp256k1_gej_add_ge_var(&pj, &pj, &p, NULL); + return secp256k1_gej_is_infinity(&pj); +} + +static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL; + +static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening) { + opening->magic = s2c_opening_magic; + opening->nonce_is_negated = 0; +} + +static int secp256k1_s2c_commit_is_init(const secp256k1_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_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_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_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_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_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; +} + #ifdef ENABLE_MODULE_ECDH # include "modules/ecdh/main_impl.h" #endif diff --git a/src/tests.c b/src/tests.c index ebeef510f0..237035aa8c 100644 --- a/src/tests.c +++ b/src/tests.c @@ -3360,6 +3360,85 @@ void run_ec_combine(void) { } } +int test_ec_commit_seckey(unsigned char *seckey, secp256k1_pubkey *commitment) { + /* Return if seckey is the discrete log of commitment */ + secp256k1_pubkey pubkey_tmp; + return secp256k1_ec_pubkey_create(ctx, &pubkey_tmp, seckey) == 1 + && memcmp(&pubkey_tmp, commitment, sizeof(pubkey_tmp)) == 0; +} + +void test_ec_commit(void) { + unsigned char seckey[32]; + secp256k1_pubkey pubkey; + secp256k1_pubkey commitment; + unsigned char data[32]; + + /* Create random keypair and data */ + secp256k1_testrand256(seckey); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)); + secp256k1_testrand256_test(data); + + /* Commit to data and verify */ + CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 32)); + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 32)); + CHECK(secp256k1_ec_commit_seckey(ctx, seckey, &pubkey, data, 32)); + CHECK(test_ec_commit_seckey(seckey, &commitment) == 1); + + /* Check that verification fails with different data */ + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 31) == 0); +} + +void test_ec_commit_api(void) { + unsigned char seckey[32]; + secp256k1_pubkey pubkey; + secp256k1_pubkey commitment; + unsigned char data[32]; + + memset(data, 23, sizeof(data)); + + /* Create random keypair */ + secp256k1_testrand256(seckey); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)); + + CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 1) == 1); + /* The same pubkey can be both input and output of the function */ + { + secp256k1_pubkey pubkey_tmp = pubkey; + CHECK(secp256k1_ec_commit(ctx, &pubkey_tmp, &pubkey_tmp, data, 1) == 1); + CHECK(memcmp(commitment.data, pubkey_tmp.data, sizeof(commitment.data)) == 0); + } + + /* If the pubkey is not provided it will be computed from seckey */ + CHECK(secp256k1_ec_commit_seckey(ctx, seckey, NULL, data, 1) == 1); + CHECK(test_ec_commit_seckey(seckey, &commitment) == 1); + /* pubkey is not provided but seckey overflows */ + { + unsigned char overflowed_seckey[32]; + memset(overflowed_seckey, 0xFF, sizeof(overflowed_seckey)); + CHECK(secp256k1_ec_commit_seckey(ctx, overflowed_seckey, NULL, data, 1) == 0); + } + + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 1) == 1); + + /* Commitment to 0-len data should fail */ + CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 0) == 0); + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 0) == 0); + CHECK(memcmp(&pubkey.data, &commitment.data, sizeof(pubkey.data)) == 0); + { + unsigned char seckey_tmp[32]; + memcpy(seckey_tmp, seckey, 32); + CHECK(secp256k1_ec_commit_seckey(ctx, seckey_tmp, &pubkey, data, 0) == 0); + } +} + +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; @@ -5312,6 +5391,68 @@ void run_eckey_negate_test(void) { CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0); } + +void run_s2c_opening_test(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_s2c_opening opening; + size_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_s2c_opening_serialize(ctx, output, &opening) == 0); + CHECK(ecount == 1); + + /* First parsing, then serializing works */ + CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1); + CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1); + CHECK(secp256k1_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_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_s2c_opening_parse(ctx, &opening, input_tmp) == 0); + /* Unknown bit is set */ + input_tmp[0] = 8; + CHECK(secp256k1_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_s2c_opening_parse(ctx, &opening, input) == 1) { + CHECK(secp256k1_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 random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) { secp256k1_scalar nonce; do { @@ -6584,6 +6725,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(); @@ -6597,6 +6739,8 @@ int main(int argc, char **argv) { /* EC key arithmetic test */ run_eckey_negate_test(); + run_s2c_opening_test(); + #ifdef ENABLE_MODULE_ECDH /* ecdh tests */ run_ecdh_tests();