From b66e0d1ea226dcae568024d6712218c9dcd134c1 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 27 Jan 2023 10:58:10 -0500 Subject: [PATCH 1/4] Add x-only ECDH functionality to ecdh module --- include/secp256k1_ecdh.h | 47 +++++++++++++++++++++++++++++ src/modules/ecdh/main_impl.h | 57 ++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/include/secp256k1_ecdh.h b/include/secp256k1_ecdh.h index 515e174299..b258e1f091 100644 --- a/include/secp256k1_ecdh.h +++ b/include/secp256k1_ecdh.h @@ -25,6 +25,22 @@ typedef int (*secp256k1_ecdh_hash_function)( void *data ); +/** A pointer to a function that hashes an X coordinate to obtain an ECDH secret + * + * Returns: 1 if the point was successfully hashed. + * 0 will cause secp256k1_ecdh_xonly to fail and return 0. + * Other return values are not allowed, and the behaviour of + * secp256k1_ecdh_xonly is undefined for other return values. + * Out: output: pointer to an array to be filled by the function + * In: x32: pointer to a 32-byte x coordinate + * data: arbitrary data pointer that is passed through + */ +typedef int (*secp256k1_ecdh_xonly_hash_function)( + unsigned char *output, + const unsigned char *x32, + void *data +); + /** An implementation of SHA256 hash function that applies to compressed public key. * Populates the output parameter with 32 bytes. */ SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256; @@ -33,6 +49,10 @@ SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sh * Populates the output parameter with 32 bytes. */ SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default; +/** An implementation of SHA256 hash function that applies to the X coordinate. + * Populates the output parameter with 32 bytes. */ +SECP256K1_API const secp256k1_ecdh_xonly_hash_function secp256k1_ecdh_xonly_hash_function_sha256; + /** Compute an EC Diffie-Hellman secret in constant time * * Returns: 1: exponentiation was successful @@ -56,6 +76,33 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdh( void *data ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Compute an EC X-only Diffie-Hellman secret in constant time + * + * Returns: 1: exponentiation was successful + * 0: scalar was invalid (zero or overflow), input is not a valid X coordinate, or hashfp + * returned 0. + * Args: ctx: pointer to a context object. + * Out: output: pointer to an array to be filled by hashfp. + * In: xpubkey: a pointer to the 32-byte serialization of an x-only public key (see the + * extrakeys module for details). + * seckey: a 32-byte scalar with which to multiply the point. + * hashfp: pointer to a hash function. If NULL, + * secp256k1_ecdh_xonly+hash_function_sha256 is used + * (in which case, 32 bytes will be written to output). + * data: arbitrary data pointer that is passed through to hashfp + * (can be NULL for secp256k1_ecdh_xonly_hash_function_sha256). + * + * The function is constant time in seckey. It is not constant time in xpubkey, hashfp, or the output. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdh_xonly( + const secp256k1_context* ctx, + unsigned char *output, + const unsigned char *xpubkey, + const unsigned char *seckey, + secp256k1_ecdh_xonly_hash_function hashfp, + void *data +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/ecdh/main_impl.h b/src/modules/ecdh/main_impl.h index 82b082a9f0..6403dbc011 100644 --- a/src/modules/ecdh/main_impl.h +++ b/src/modules/ecdh/main_impl.h @@ -23,8 +23,20 @@ static int ecdh_hash_function_sha256(unsigned char *output, const unsigned char return 1; } +static int ecdh_xonly_hash_function_sha256(unsigned char *output, const unsigned char *x32, void *data) { + secp256k1_sha256 sha; + (void)data; + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, x32, 32); + secp256k1_sha256_finalize(&sha, output); + + return 1; +} + const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256 = ecdh_hash_function_sha256; const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default = ecdh_hash_function_sha256; +const secp256k1_ecdh_xonly_hash_function secp256k1_ecdh_xonly_hash_function_sha256 = ecdh_xonly_hash_function_sha256; int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *output, const secp256k1_pubkey *point, const unsigned char *scalar, secp256k1_ecdh_hash_function hashfp, void *data) { int ret = 0; @@ -68,4 +80,49 @@ int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *output, const se return !!ret & !overflow; } +int secp256k1_ecdh_xonly(const secp256k1_context* ctx, unsigned char *output, const unsigned char* xpubkey, const unsigned char *scalar, secp256k1_ecdh_xonly_hash_function hashfp, void *data) { + int ret; + int overflow; + secp256k1_fe x; + unsigned char x32[32]; + secp256k1_scalar s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output != NULL); + ARG_CHECK(xpubkey != NULL); + ARG_CHECK(scalar != NULL); + + if (hashfp == NULL) { + hashfp = secp256k1_ecdh_xonly_hash_function_sha256; + } + + if (!secp256k1_fe_set_b32_limit(&x, xpubkey)) { + /* X-only public key overflow, bail out early (we don't need to be constant time in pubkey). */ + memset(output, 0, 32); + return 0; + } + + secp256k1_scalar_set_b32(&s, scalar, &overflow); + + overflow |= secp256k1_scalar_is_zero(&s); + secp256k1_scalar_cmov(&s, &secp256k1_scalar_one, overflow); + + if (!secp256k1_ecmult_const_xonly(&x, &x, NULL, &s, 0)) { + /* Input is not a valid X coordinate, bail out early. */ + memset(output, 0, 32); + return 0; + } + + /* Compute a hash of the point */ + secp256k1_fe_normalize(&x); + secp256k1_fe_get_b32(x32, &x); + + ret = hashfp(output, x32, data); + + memset(x32, 0, 32); + secp256k1_scalar_clear(&s); + + return !!ret & !overflow; +} + #endif /* SECP256K1_MODULE_ECDH_MAIN_H */ From b62b7b72fa6e20eee796750bde47054a9d002587 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 27 Jan 2023 11:31:22 -0500 Subject: [PATCH 2/4] Update ECDH benchmarks to include pubkey decode and add x-only --- src/bench.c | 2 +- src/modules/ecdh/bench_impl.h | 45 +++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/bench.c b/src/bench.c index 1127df67ae..80e8af1458 100644 --- a/src/bench.c +++ b/src/bench.c @@ -176,7 +176,7 @@ int main(int argc, char** argv) { int iters = get_iters(default_iters); /* Check for invalid user arguments */ - char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", + char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "ecdh_xonly", "recover", "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; diff --git a/src/modules/ecdh/bench_impl.h b/src/modules/ecdh/bench_impl.h index c23aaa94d1..fd5235c3c8 100644 --- a/src/modules/ecdh/bench_impl.h +++ b/src/modules/ecdh/bench_impl.h @@ -11,14 +11,31 @@ typedef struct { secp256k1_context *ctx; - secp256k1_pubkey point; + unsigned char point[33]; unsigned char scalar[32]; } bench_ecdh_data; + +/* Outputs a hash of the coordinates, but also updates data->point with the coordinates. */ +static int ecdh_hash_function_bench(unsigned char* output, const unsigned char *x32, const unsigned char *y32, void* arg) { + bench_ecdh_data* data = arg; + int ret = secp256k1_ecdh_hash_function_sha256(output, x32, y32, NULL); + data->point[0] ^= y32[17] & 1; + memcpy(data->point + 1, x32, 32); + return ret; +} + +static int ecdh_xonly_hash_function_bench(unsigned char* output, const unsigned char *x32, void* arg) { + bench_ecdh_data* data = arg; + int ret = secp256k1_ecdh_xonly_hash_function_sha256(output, x32, NULL); + memcpy(data->point + 1, x32, 32); + return ret; +} + static void bench_ecdh_setup(void* arg) { int i; bench_ecdh_data *data = (bench_ecdh_data*)arg; - const unsigned char point[] = { + static const unsigned char point[33] = { 0x03, 0x54, 0x94, 0xc1, 0x5d, 0x32, 0x09, 0x97, 0x06, 0xc2, 0x39, 0x5f, 0x94, 0x34, 0x87, 0x45, 0xfd, @@ -29,16 +46,33 @@ static void bench_ecdh_setup(void* arg) { for (i = 0; i < 32; i++) { data->scalar[i] = i + 1; } - CHECK(secp256k1_ec_pubkey_parse(data->ctx, &data->point, point, sizeof(point)) == 1); + memcpy(data->point, point, sizeof(point)); } static void bench_ecdh(void* arg, int iters) { int i; - unsigned char res[32]; bench_ecdh_data *data = (bench_ecdh_data*)arg; for (i = 0; i < iters; i++) { - CHECK(secp256k1_ecdh(data->ctx, res, &data->point, data->scalar, NULL, NULL) == 1); + /* Compute point multiplication of data->point with data->scalar, and then update: + * - data->scalar to be the computed shared secret (hash of point multiplication output) + * - data->point to have X coordinate equal to X coordinate of point multiplication output, + * and optionally flipped Y coordinate. */ + secp256k1_pubkey pubkey; + CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pubkey, data->point, sizeof(data->point)) == 1); + CHECK(secp256k1_ecdh(data->ctx, data->scalar, &pubkey, data->scalar, &ecdh_hash_function_bench, arg) == 1); + } +} + +static void bench_ecdh_xonly(void* arg, int iters) { + int i; + bench_ecdh_data *data = (bench_ecdh_data*)arg; + + for (i = 0; i < iters; i++) { + /* Compute X-only point multiplication of data->point with data->scalar, and then update: + * - data->scalar to be the computed shared secret (hash of point multiplication output X coordinate) + * - data->point to have X coordinate equal to X coordinate of point multiplication output. */ + CHECK(secp256k1_ecdh_xonly(data->ctx, data->scalar, data->point + 1, data->scalar, &ecdh_xonly_hash_function_bench, arg) == 1); } } @@ -50,6 +84,7 @@ static void run_ecdh_bench(int iters, int argc, char** argv) { data.ctx = secp256k1_context_create(SECP256K1_FLAGS_TYPE_CONTEXT); if (d || have_flag(argc, argv, "ecdh")) run_benchmark("ecdh", bench_ecdh, bench_ecdh_setup, NULL, &data, 10, iters); + if (d || have_flag(argc, argv, "ecdh") || have_flag(argc, argv, "ecdh_xonly")) run_benchmark("ecdh_xonly", bench_ecdh_xonly, bench_ecdh_setup, NULL, &data, 10, iters); secp256k1_context_destroy(data.ctx); } From 0e8602cd5f5e55d3047cb2acd4957b5c4ab8424b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 27 Jan 2023 13:25:24 -0500 Subject: [PATCH 3/4] Add X-only ECDH unit tests --- src/modules/ecdh/tests_impl.h | 73 ++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/modules/ecdh/tests_impl.h b/src/modules/ecdh/tests_impl.h index 6be96eacbe..b859d9caee 100644 --- a/src/modules/ecdh/tests_impl.h +++ b/src/modules/ecdh/tests_impl.h @@ -15,6 +15,13 @@ static int ecdh_hash_function_test_fail(unsigned char *output, const unsigned ch return 0; } +static int ecdh_xonly_hash_function_test_fail(unsigned char *output, const unsigned char *x, void *data) { + (void)output; + (void)x; + (void)data; + return 0; +} + static int ecdh_hash_function_custom(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { (void)data; /* Save x and y as uncompressed public key */ @@ -24,9 +31,17 @@ static int ecdh_hash_function_custom(unsigned char *output, const unsigned char return 1; } +static int ecdh_xonly_hash_function_custom(unsigned char *output, const unsigned char *x, void *data) { + (void)data; + /* Output X coordinate. */ + memcpy(output, x, 32); + return 1; +} + static void test_ecdh_api(void) { secp256k1_pubkey point; unsigned char res[32]; + unsigned char x32[32]; unsigned char s_one[32] = { 0 }; s_one[31] = 1; @@ -38,14 +53,28 @@ static void test_ecdh_api(void) { CHECK_ILLEGAL(CTX, secp256k1_ecdh(CTX, res, NULL, s_one, NULL, NULL)); CHECK_ILLEGAL(CTX, secp256k1_ecdh(CTX, res, &point, NULL, NULL, NULL)); CHECK(secp256k1_ecdh(CTX, res, &point, s_one, NULL, NULL) == 1); + + /* And the same for secp256k1_ecdh_xonly. */ + memset(x32, 131, 32); /* sum(131*256^j, j=0..31) is a valid x coordinate. */ + CHECK(secp256k1_ecdh_xonly(CTX, res, x32, s_one, NULL, NULL) == 1); + CHECK_ILLEGAL(CTX, secp256k1_ecdh_xonly(CTX, NULL, x32, s_one, NULL, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_ecdh_xonly(CTX, res, NULL, s_one, NULL, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_ecdh_xonly(CTX, res, x32, NULL, NULL, NULL)); + CHECK(secp256k1_ecdh_xonly(CTX, res, x32, s_one, NULL, NULL) == 1); + memset(x32, 205, 32); /* sum(205*256^j, j=0..31) is not a valid x coordinate. */ + CHECK(secp256k1_ecdh_xonly(CTX, res, x32, s_one, NULL, NULL) == 0); } static void test_ecdh_generator_basepoint(void) { unsigned char s_one[32] = { 0 }; + unsigned char x32_g[32]; secp256k1_pubkey point[2]; int i; s_one[31] = 1; + CHECK(secp256k1_ec_pubkey_create(CTX, &point[0], s_one) == 1); + secp256k1_fe_get_b32(x32_g, &secp256k1_ge_const_g.x); + /* Check against pubkey creation when the basepoint is the generator */ for (i = 0; i < 2 * COUNT; ++i) { secp256k1_sha256 sha; @@ -59,7 +88,6 @@ static void test_ecdh_generator_basepoint(void) { random_scalar_order(&s); secp256k1_scalar_get_b32(s_b32, &s); - CHECK(secp256k1_ec_pubkey_create(CTX, &point[0], s_one) == 1); CHECK(secp256k1_ec_pubkey_create(CTX, &point[1], s_b32) == 1); /* compute using ECDH function with custom hash function */ @@ -69,6 +97,11 @@ static void test_ecdh_generator_basepoint(void) { /* compare */ CHECK(secp256k1_memcmp_var(output_ecdh, point_ser, 65) == 0); + /* Do the same with x-only ECDH. */ + CHECK(secp256k1_ecdh_xonly(CTX, output_ecdh, x32_g, s_b32, ecdh_xonly_hash_function_custom, NULL) == 1); + /* compare */ + CHECK(secp256k1_memcmp_var(output_ecdh, point_ser + 1, 32) == 0); + /* compute using ECDH function with default hash function */ CHECK(secp256k1_ecdh(CTX, output_ecdh, &point[0], s_b32, NULL, NULL) == 1); /* compute "explicitly" */ @@ -78,6 +111,15 @@ static void test_ecdh_generator_basepoint(void) { secp256k1_sha256_finalize(&sha, output_ser); /* compare */ CHECK(secp256k1_memcmp_var(output_ecdh, output_ser, 32) == 0); + + /* And the same with x-only ECDH. */ + CHECK(secp256k1_ecdh_xonly(CTX, output_ecdh, x32_g, s_b32, NULL, NULL) == 1); + /* compute "explicitly" */ + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, point_ser +1 , 32); + secp256k1_sha256_finalize(&sha, output_ser); + /* compare */ + CHECK(secp256k1_memcmp_var(output_ecdh, output_ser, 32) == 0); } } @@ -91,13 +133,16 @@ static void test_bad_scalar(void) { }; unsigned char s_rand[32] = { 0 }; unsigned char output[32]; + unsigned char point_ser[33]; secp256k1_scalar rand; secp256k1_pubkey point; + size_t point_ser_len = sizeof(point_ser); /* Create random point */ random_scalar_order(&rand); secp256k1_scalar_get_b32(s_rand, &rand); CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_rand) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, point_ser, &point_ser_len, &point, SECP256K1_EC_COMPRESSED) == 1); /* Try to multiply it by bad values */ CHECK(secp256k1_ecdh(CTX, output, &point, s_zero, NULL, NULL) == 0); @@ -106,39 +151,63 @@ static void test_bad_scalar(void) { s_overflow[31] -= 1; CHECK(secp256k1_ecdh(CTX, output, &point, s_overflow, NULL, NULL) == 1); + /* And repeat for x-only. */ + s_overflow[31] += 1; + CHECK(secp256k1_ecdh_xonly(CTX, output, point_ser + 1, s_zero, NULL, NULL) == 0); + CHECK(secp256k1_ecdh_xonly(CTX, output, point_ser + 1, s_overflow, NULL, NULL) == 0); + s_overflow[31] -= 1; + CHECK(secp256k1_ecdh_xonly(CTX, output, point_ser + 1, s_overflow, NULL, NULL) == 1); + /* Hash function failure results in ecdh failure */ CHECK(secp256k1_ecdh(CTX, output, &point, s_overflow, ecdh_hash_function_test_fail, NULL) == 0); + CHECK(secp256k1_ecdh_xonly(CTX, output, point_ser, s_overflow, ecdh_xonly_hash_function_test_fail, NULL) == 0); } /** Test that ECDH(sG, 1/s) == ECDH((1/s)G, s) == ECDH(G, 1) for a few random s. */ static void test_result_basepoint(void) { secp256k1_pubkey point; secp256k1_scalar rand; + unsigned char point_ser[33]; + unsigned char x32_g[32]; unsigned char s[32]; unsigned char s_inv[32]; unsigned char out[32]; unsigned char out_inv[32]; unsigned char out_base[32]; + unsigned char out_base_xonly[32]; + size_t point_ser_len; int i; unsigned char s_one[32] = { 0 }; s_one[31] = 1; + secp256k1_fe_get_b32(x32_g, &secp256k1_ge_const_g.x); CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_one) == 1); CHECK(secp256k1_ecdh(CTX, out_base, &point, s_one, NULL, NULL) == 1); + CHECK(secp256k1_ecdh_xonly(CTX, out_base_xonly, x32_g, s_one, NULL, NULL) == 1); for (i = 0; i < 2 * COUNT; i++) { random_scalar_order(&rand); secp256k1_scalar_get_b32(s, &rand); - secp256k1_scalar_inverse(&rand, &rand); + secp256k1_scalar_inverse_var(&rand, &rand); secp256k1_scalar_get_b32(s_inv, &rand); CHECK(secp256k1_ec_pubkey_create(CTX, &point, s) == 1); + point_ser_len = sizeof(point_ser); + CHECK(secp256k1_ec_pubkey_serialize(CTX, point_ser, &point_ser_len, &point, SECP256K1_EC_COMPRESSED)); + CHECK(secp256k1_ecdh(CTX, out, &point, s_inv, NULL, NULL) == 1); CHECK(secp256k1_memcmp_var(out, out_base, 32) == 0); + CHECK(secp256k1_ecdh_xonly(CTX, out, point_ser + 1, s_inv, NULL, NULL) == 1); + CHECK(secp256k1_memcmp_var(out, out_base_xonly, 32) == 0); CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_inv) == 1); CHECK(secp256k1_ecdh(CTX, out_inv, &point, s, NULL, NULL) == 1); + point_ser_len = sizeof(point_ser); + CHECK(secp256k1_ec_pubkey_serialize(CTX, point_ser, &point_ser_len, &point, SECP256K1_EC_COMPRESSED)); + CHECK(secp256k1_memcmp_var(out_inv, out_base, 32) == 0); + CHECK(secp256k1_ecdh_xonly(CTX, out_inv, point_ser + 1, s, NULL, NULL) == 1); + CHECK(secp256k1_memcmp_var(out_inv, out_base_xonly, 32) == 0); } } From a732222f9aeee9a395c217049a6e6b8b693de85b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 27 Jan 2023 13:30:59 -0500 Subject: [PATCH 4/4] Add x-only ECDH ctime tests --- src/ctime_tests.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ctime_tests.c b/src/ctime_tests.c index a384e83152..b968eab1f8 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -115,6 +115,12 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { ret = secp256k1_ecdh(ctx, msg, &pubkey, key, NULL, NULL); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); + + /* Test X-only ECDH. */ + SECP256K1_CHECKMEM_UNDEFINE(key, 32); + ret = secp256k1_ecdh_xonly(ctx, msg, spubkey + 1, key, NULL, NULL); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); #endif #ifdef ENABLE_MODULE_RECOVERY