From 290dee566e14efa852d4e5437546f6a8ff8bfa1a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 5 Dec 2020 23:34:14 +0000 Subject: [PATCH] ecdsa-s2c: add actual sign-to-contract functionality Co-authored-by: Marko Bencun Co-authored-by: Jonas Nick --- include/secp256k1_ecdsa_s2c.h | 37 ++++++ src/modules/ecdsa_s2c/main_impl.h | 112 ++++++++++++++++++ src/modules/ecdsa_s2c/tests_impl.h | 184 ++++++++++++++++++++++++++++- src/modules/recovery/main_impl.h | 2 +- src/secp256k1.c | 45 ++++++- 5 files changed, 376 insertions(+), 4 deletions(-) diff --git a/include/secp256k1_ecdsa_s2c.h b/include/secp256k1_ecdsa_s2c.h index 7f54e71f..b28003f0 100644 --- a/include/secp256k1_ecdsa_s2c.h +++ b/include/secp256k1_ecdsa_s2c.h @@ -51,6 +51,43 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_seria const secp256k1_ecdsa_s2c_opening* opening ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** Same as secp256k1_ecdsa_sign, but s2c_data32 is committed to inside the nonce + * + * Returns: 1: signature created + * 0: the nonce generation function failed, or the private key was invalid. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) + * s2c_opening: if non-NULL, pointer to an secp256k1_ecdsa_s2c_opening structure to populate + * In: msg32: the 32-byte message hash being signed (cannot be NULL) + * seckey: pointer to a 32-byte secret key (cannot be NULL) + * s2c_data32: pointer to a 32-byte data to commit to in the nonce (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_s2c_sign( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + secp256k1_ecdsa_s2c_opening* s2c_opening, + const unsigned char* msg32, + const unsigned char* seckey, + const unsigned char* s2c_data32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Verify a sign-to-contract commitment. + * + * Returns: 1: the signature contains a commitment to data32 (though it does + * not necessarily need to be a valid siganture!) + * 0: incorrect opening + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig: 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 SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_verify_commit( + const secp256k1_context* ctx, + const secp256k1_ecdsa_signature *sig, + const unsigned char *data32, + const secp256k1_ecdsa_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/ecdsa_s2c/main_impl.h b/src/modules/ecdsa_s2c/main_impl.h index cf152359..7246ddc7 100755 --- a/src/modules/ecdsa_s2c/main_impl.h +++ b/src/modules/ecdsa_s2c/main_impl.h @@ -10,6 +10,14 @@ #include "include/secp256k1.h" #include "include/secp256k1_ecdsa_s2c.h" +static void secp256k1_ecdsa_s2c_opening_save(secp256k1_ecdsa_s2c_opening* opening, secp256k1_ge* ge) { + secp256k1_pubkey_save((secp256k1_pubkey*) opening, ge); +} + +static int secp256k1_ecdsa_s2c_opening_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_ecdsa_s2c_opening* opening) { + return secp256k1_pubkey_load(ctx, ge, (const secp256k1_pubkey*) opening); +} + int secp256k1_ecdsa_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_ecdsa_s2c_opening* opening, const unsigned char* input33) { VERIFY_CHECK(ctx != NULL); ARG_CHECK(opening != NULL); @@ -25,4 +33,108 @@ int secp256k1_ecdsa_s2c_opening_serialize(const secp256k1_context* ctx, unsigned return secp256k1_ec_pubkey_serialize(ctx, output33, &out_len, (const secp256k1_pubkey*) opening, SECP256K1_EC_COMPRESSED); } +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("s2c/ecdsa/point")||SHA256("s2c/ecdsa/point"). */ +static void secp256k1_s2c_ecdsa_point_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xa9b21c7bul; + sha->s[1] = 0x358c3e3eul; + sha->s[2] = 0x0b6863d1ul; + sha->s[3] = 0xc62b2035ul; + sha->s[4] = 0xb44b40ceul; + sha->s[5] = 0x254a8912ul; + sha->s[6] = 0x0f85d0d4ul; + sha->s[7] = 0x8a5bf91cul; + + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("s2c/ecdsa/data")||SHA256("s2c/ecdsa/data"). */ +static void secp256k1_s2c_ecdsa_data_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xfeefd675ul; + sha->s[1] = 0x73166c99ul; + sha->s[2] = 0xe2309cb8ul; + sha->s[3] = 0x6d458113ul; + sha->s[4] = 0x01d3a512ul; + sha->s[5] = 0x00e18112ul; + sha->s[6] = 0x37ee0874ul; + sha->s[7] = 0x421fc55ful; + + sha->bytes = 64; +} + +int secp256k1_ecdsa_s2c_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature* signature, secp256k1_ecdsa_s2c_opening* s2c_opening, const unsigned char + *msg32, const unsigned char *seckey, const unsigned char* s2c_data32) { + secp256k1_scalar r, s; + int ret; + unsigned char ndata[32]; + secp256k1_sha256 s2c_sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(signature != NULL); + ARG_CHECK(seckey != NULL); + ARG_CHECK(s2c_data32 != NULL); + + /* Provide `s2c_data32` to the nonce function as additional data to + * derive the nonce. It is first hashed because it should be possible + * to derive nonces even if only a SHA256 commitment to the data is + * known. This is important in the ECDSA anti-klepto protocol. */ + secp256k1_s2c_ecdsa_data_sha256_tagged(&s2c_sha); + secp256k1_sha256_write(&s2c_sha, s2c_data32, 32); + secp256k1_sha256_finalize(&s2c_sha, ndata); + + secp256k1_s2c_ecdsa_point_sha256_tagged(&s2c_sha); + ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, &s2c_sha, s2c_opening, s2c_data32, msg32, seckey, NULL, ndata); + secp256k1_scalar_cmov(&r, &secp256k1_scalar_zero, !ret); + secp256k1_scalar_cmov(&s, &secp256k1_scalar_zero, !ret); + secp256k1_ecdsa_signature_save(signature, &r, &s); + return ret; +} + +int secp256k1_ecdsa_s2c_verify_commit(const secp256k1_context* ctx, const secp256k1_ecdsa_signature* sig, const unsigned char* data32, const secp256k1_ecdsa_s2c_opening* opening) { + secp256k1_ge commitment_ge; + secp256k1_ge original_pubnonce_ge; + unsigned char x_bytes[32]; + secp256k1_scalar sigr, sigs, x_scalar; + secp256k1_sha256 s2c_sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(sig != NULL); + ARG_CHECK(data32 != NULL); + ARG_CHECK(opening != NULL); + + if (!secp256k1_ecdsa_s2c_opening_load(ctx, &original_pubnonce_ge, opening)) { + return 0; + } + secp256k1_s2c_ecdsa_point_sha256_tagged(&s2c_sha); + if (!secp256k1_ec_commit(&ctx->ecmult_ctx, &commitment_ge, &original_pubnonce_ge, &s2c_sha, data32, 32)) { + return 0; + } + + /* Check that sig_r == commitment_x (mod n) + * sig_r is the x coordinate of R represented by a scalar. + * commitment_x is the x coordinate of the commitment (field element). + * + * Note that we are only checking the x-coordinate -- this is because the y-coordinate + * is not part of the ECDSA signature (and therefore not part of the commitment!) + */ + secp256k1_ecdsa_signature_load(ctx, &sigr, &sigs, sig); + + secp256k1_fe_normalize(&commitment_ge.x); + secp256k1_fe_get_b32(x_bytes, &commitment_ge.x); + /* Do not check overflow; overflowing a scalar does not affect whether + * or not the R value is a cryptographic commitment, only whether it + * is a valid R value for an ECDSA signature. If users care about that + * they should use `ecdsa_verify` or `anti_klepto_host_verify`. In other + * words, this check would be (at best) unnecessary, and (at worst) + * insufficient. */ + secp256k1_scalar_set_b32(&x_scalar, x_bytes, NULL); + return secp256k1_scalar_eq(&sigr, &x_scalar); +} + #endif /* SECP256K1_ECDSA_S2C_MAIN_H */ diff --git a/src/modules/ecdsa_s2c/tests_impl.h b/src/modules/ecdsa_s2c/tests_impl.h index cd7e18f6..6e8dae8f 100644 --- a/src/modules/ecdsa_s2c/tests_impl.h +++ b/src/modules/ecdsa_s2c/tests_impl.h @@ -9,6 +9,27 @@ #include "include/secp256k1_ecdsa_s2c.h" +static void test_ecdsa_s2c_tagged_hash(void) { + unsigned char tag_data[14] = "s2c/ecdsa/data"; + unsigned char tag_point[15] = "s2c/ecdsa/point"; + secp256k1_sha256 sha; + secp256k1_sha256 sha_optimized; + unsigned char output[32]; + unsigned char output_optimized[32]; + + secp256k1_sha256_initialize_tagged(&sha, tag_data, sizeof(tag_data)); + secp256k1_s2c_ecdsa_data_sha256_tagged(&sha_optimized); + secp256k1_sha256_finalize(&sha, output); + secp256k1_sha256_finalize(&sha_optimized, output_optimized); + CHECK(secp256k1_memcmp_var(output, output_optimized, 32) == 0); + + secp256k1_sha256_initialize_tagged(&sha, tag_point, sizeof(tag_point)); + secp256k1_s2c_ecdsa_point_sha256_tagged(&sha_optimized); + secp256k1_sha256_finalize(&sha, output); + secp256k1_sha256_finalize(&sha_optimized, output_optimized); + CHECK(secp256k1_memcmp_var(output, output_optimized, 32) == 0); +} + void run_s2c_opening_test(void) { int i = 0; unsigned char output[33]; @@ -62,15 +83,176 @@ void run_s2c_opening_test(void) { secp256k1_testrand256(&input[1]); /* Set pubkey oddness tag to first bit of input[1] */ input[0] = (input[1] & 1) + 2; - i++; } secp256k1_context_destroy(none); } +static void test_ecdsa_s2c_api(void) { + secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + secp256k1_ecdsa_s2c_opening s2c_opening; + secp256k1_ecdsa_signature sig; + const unsigned char msg[32] = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"; + const unsigned char sec[32] = "ssssssssssssssssssssssssssssssss"; + const unsigned char s2c_data[32] = "dddddddddddddddddddddddddddddddd"; + secp256k1_pubkey pk; + + int32_t ecount; + secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(both, counting_illegal_callback_fn, &ecount); + CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sec)); + + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(both, NULL, &s2c_opening, msg, sec, s2c_data) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, NULL, msg, sec, s2c_data) == 1); + CHECK(ecount == 1); /* NULL opening is not an API error */ + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, &s2c_opening, NULL, sec, s2c_data) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, &s2c_opening, msg, NULL, s2c_data) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, &s2c_opening, msg, sec, NULL) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_s2c_sign(none, &sig, &s2c_opening, msg, sec, s2c_data) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_s2c_sign(vrfy, &sig, &s2c_opening, msg, sec, s2c_data) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &sig, &s2c_opening, msg, sec, s2c_data) == 1); + CHECK(ecount == 6); + + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pk) == 1); + + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, NULL, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, &sig, NULL, &s2c_opening) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, &sig, s2c_data, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_verify_commit(none, &sig, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_s2c_verify_commit(sign, &sig, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, s2c_data, &s2c_opening) == 1); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, sec, &s2c_opening) == 0); + CHECK(ecount == 5); /* wrong data is not an API error */ + + /* Signing with NULL s2c_opening gives the same result */ + CHECK(secp256k1_ecdsa_s2c_sign(sign, &sig, NULL, msg, sec, s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, s2c_data, &s2c_opening) == 1); + + secp256k1_context_destroy(both); + secp256k1_context_destroy(vrfy); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(none); +} + +/* When using sign-to-contract commitments, the nonce function is fixed, so we can use fixtures to test. */ +typedef struct { + unsigned char s2c_data[32]; + unsigned char expected_s2c_opening[33]; +} ecdsa_s2c_test; + +static ecdsa_s2c_test ecdsa_s2c_tests[] = { + { + "\x1b\xf6\xfb\x42\xf4\x1e\xb8\x76\xc4\xd7\xaa\x0d\x67\x24\x2b\x00\xba\xab\x99\xdc\x20\x84\x49\x3e\x4e\x63\x27\x7f\xa1\xf7\x7f\x22", + "\x03\xf0\x30\xde\xf3\x18\x8c\x0f\x56\xfc\xea\x87\x43\x5b\x30\x76\x43\xf4\x5d\xaf\xe2\x2c\xbc\x82\xfd\x56\x03\x4f\xae\x97\x41\x7d\x3a", + }, + { + "\x35\x19\x9a\x8f\xbf\x84\xad\x6e\xf6\x9a\x18\x4c\x1b\x19\x28\x5b\xef\xbe\x06\xe6\x0b\x62\x64\xe6\xd3\x73\x89\x3f\x68\x55\xe2\x4a", + "\x03\x90\x17\x17\xce\x7c\x74\x84\xa2\xce\x1b\x7d\xc7\x40\x3b\x14\xe0\x35\x49\x71\x39\x3e\xc0\x92\xa7\xf3\xe0\xc8\xe4\xe2\xd2\x63\x9d", + }, +}; + +static void test_ecdsa_s2c_fixed_vectors(void) { + const unsigned char privkey[32] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + }; + const unsigned char message[32] = { + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + }; + size_t i; + + for (i = 0; i < sizeof(ecdsa_s2c_tests) / sizeof(ecdsa_s2c_tests[0]); i++) { + secp256k1_ecdsa_s2c_opening s2c_opening; + unsigned char opening_ser[33]; + const ecdsa_s2c_test *test = &ecdsa_s2c_tests[i]; + secp256k1_ecdsa_signature signature; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, test->s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_opening_serialize(ctx, opening_ser, &s2c_opening) == 1); + CHECK(memcmp(test->expected_s2c_opening, opening_ser, sizeof(opening_ser)) == 0); + } +} + +static void test_ecdsa_s2c_sign_verify(void) { + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + unsigned char message[32]; + unsigned char noncedata[32]; + unsigned char s2c_data[32]; + unsigned char s2c_data2[32]; + secp256k1_ecdsa_signature signature; + secp256k1_ecdsa_s2c_opening s2c_opening; + + /* Generate a random key, message, noncedata and s2c_data. */ + { + secp256k1_scalar key; + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(privkey, &key); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + + secp256k1_testrand256_test(message); + secp256k1_testrand256_test(noncedata); + secp256k1_testrand256_test(s2c_data); + secp256k1_testrand256_test(s2c_data2); + } + + { /* invalid privkeys */ + unsigned char zero_privkey[32] = {0}; + unsigned char overflow_privkey[32] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, zero_privkey, s2c_data) == 0); + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, overflow_privkey, s2c_data) == 0); + } + /* Check that the sign-to-contract signature is valid, with s2c_data. Also check the commitment. */ + { + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature, message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, s2c_data, &s2c_opening) == 1); + } + /* Check that an invalid commitment does not verify */ + { + unsigned char sigbytes[64]; + size_t i; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature, message, &pubkey) == 1); + + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, sigbytes, &signature) == 1); + for(i = 0; i < 32; i++) { + /* change one byte */ + sigbytes[i] = (((int)sigbytes[i]) + 1) % 256; + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &signature, sigbytes) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, s2c_data, &s2c_opening) == 0); + /* revert */ + sigbytes[i] = (((int)sigbytes[i]) + 255) % 256; + } + } +} static void run_ecdsa_s2c_tests(void) { run_s2c_opening_test(); + test_ecdsa_s2c_tagged_hash(); + test_ecdsa_s2c_api(); + test_ecdsa_s2c_fixed_vectors(); + test_ecdsa_s2c_sign_verify(); } #endif /* SECP256K1_MODULE_ECDSA_S2C_TESTS_H */ diff --git a/src/modules/recovery/main_impl.h b/src/modules/recovery/main_impl.h index e2576aa9..4a225dcb 100644 --- a/src/modules/recovery/main_impl.h +++ b/src/modules/recovery/main_impl.h @@ -129,7 +129,7 @@ int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecd ARG_CHECK(signature != NULL); ARG_CHECK(seckey != NULL); - ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, msg32, seckey, noncefp, noncedata); + ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, NULL, NULL, NULL, msg32, seckey, noncefp, noncedata); secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid); return ret; } diff --git a/src/secp256k1.c b/src/secp256k1.c index a48a7371..68390303 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -37,6 +37,18 @@ # include "modules/rangeproof/rangeproof.h" #endif +#ifdef ENABLE_MODULE_ECDSA_S2C +# include "include/secp256k1_ecdsa_s2c.h" +static void secp256k1_ecdsa_s2c_opening_save(secp256k1_ecdsa_s2c_opening* opening, secp256k1_ge* ge); +#else +typedef void secp256k1_ecdsa_s2c_opening; +static void secp256k1_ecdsa_s2c_opening_save(secp256k1_ecdsa_s2c_opening* opening, secp256k1_ge* ge) { + (void) opening; + (void) ge; + VERIFY_CHECK(0); +} +#endif + #define ARG_CHECK(cond) do { \ if (EXPECT(!(cond), 0)) { \ secp256k1_callback_call(&ctx->illegal_callback, #cond); \ @@ -488,7 +500,7 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m const secp256k1_nonce_function secp256k1_nonce_function_rfc6979 = nonce_function_rfc6979; const secp256k1_nonce_function secp256k1_nonce_function_default = nonce_function_rfc6979; -static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { +static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, secp256k1_sha256* s2c_sha, secp256k1_ecdsa_s2c_opening *s2c_opening, const unsigned char* s2c_data32, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { secp256k1_scalar sec, non, msg; int ret = 0; int is_sec_valid; @@ -503,6 +515,11 @@ static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_sc if (noncefp == NULL) { noncefp = secp256k1_nonce_function_default; } + /* 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. Otherwise an attacker can exfiltrate the secret key by + * signing the same message thrice with different commitments. */ + VERIFY_CHECK(s2c_data32 == NULL || noncefp == secp256k1_nonce_function_default); /* Fail if the secret key is invalid. */ is_sec_valid = secp256k1_scalar_set_b32_seckey(&sec, seckey); @@ -518,6 +535,30 @@ static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_sc /* The nonce is still secret here, but it being invalid is is less likely than 1:2^255. */ secp256k1_declassify(ctx, &is_nonce_valid, sizeof(is_nonce_valid)); if (is_nonce_valid) { + if (s2c_data32 != NULL) { + secp256k1_gej nonce_pj; + secp256k1_ge nonce_p; + + /* Compute original nonce commitment/pubkey */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_pj, &non); + secp256k1_ge_set_gej(&nonce_p, &nonce_pj); + if (s2c_opening != NULL) { + secp256k1_ecdsa_s2c_opening_save(s2c_opening, &nonce_p); + } + + /* Because the nonce is valid, the nonce point isn't the point + * at infinity and we can declassify that information to be able to + * serialize the point. */ + secp256k1_declassify(ctx, &nonce_p.infinity, sizeof(nonce_p.infinity)); + + /* Tweak nonce with s2c commitment. */ + ret = secp256k1_ec_commit_seckey(&non, &nonce_p, s2c_sha, s2c_data32, 32); + secp256k1_declassify(ctx, &ret, sizeof(ret)); /* may be secret that the tweak falied, but happens with negligible probability */ + if (!ret) { + break; + } + } + ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, r, s, &sec, &msg, &non, recid); /* The final signature is no longer a secret, nor is the fact that we were successful or not. */ secp256k1_declassify(ctx, &ret, sizeof(ret)); @@ -553,7 +594,7 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature ARG_CHECK(signature != NULL); ARG_CHECK(seckey != NULL); - ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, msg32, seckey, noncefp, noncedata); + ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, NULL, NULL, NULL, msg32, seckey, noncefp, noncedata); secp256k1_ecdsa_signature_save(signature, &r, &s); return ret; }