From 839c8b6bafc7314a7edd3d6af7dc41af46bcc84f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 20 Oct 2021 11:24:36 +1030 Subject: [PATCH 1/3] secp256k1_ecdh: add secp256k1_ecdh_hash_function_xonly_sha256 In a future world where xonly pubkeys rule, it seems strange to hash the y-coordinate. It also fails if you need to invert one of the keys for the ECDH. Suggested-by: Jonas Nick Signed-off-by: Rusty Russell --- include/secp256k1_ecdh.h | 4 ++++ src/modules/ecdh/main_impl.h | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/secp256k1_ecdh.h b/include/secp256k1_ecdh.h index c8577984b1..ea18a0c3e0 100644 --- a/include/secp256k1_ecdh.h +++ b/include/secp256k1_ecdh.h @@ -29,6 +29,10 @@ typedef int (*secp256k1_ecdh_hash_function)( * Populates the output parameter with 32 bytes. */ SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256; +/** An implementation of SHA256 hash function that applies to the x-only part of a public key. + * Populates the output parameter with 32 bytes. */ +SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_xonly_sha256; + /** A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256). * Populates the output parameter with 32 bytes. */ SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default; diff --git a/src/modules/ecdh/main_impl.h b/src/modules/ecdh/main_impl.h index 5408c9de70..0ac9800e70 100644 --- a/src/modules/ecdh/main_impl.h +++ b/src/modules/ecdh/main_impl.h @@ -23,7 +23,20 @@ static int ecdh_hash_function_sha256(unsigned char *output, const unsigned char return 1; } +static int ecdh_hash_function_xonly_sha256(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) { + secp256k1_sha256 sha; + (void)data; + (void)y32; + + 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_xonly_sha256 = ecdh_hash_function_xonly_sha256; const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default = ecdh_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) { From 4f7ebc44b60b8256630fb3a48d8930ed5e33f169 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 20 Oct 2021 11:26:51 +1030 Subject: [PATCH 2/3] ecdh: add (optional) xonly variant. This is in the ecdh module, since that allows convenient inline access. The alternative would be to expose ecdh_core() and put this inside extrakeys, which would avoid open-coding secp256k1_xonly_pubkey_load(). Signed-off-by: Rusty Russell --- include/secp256k1_ecdh.h | 26 +++++++++++++++ src/modules/ecdh/main_impl.h | 65 ++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/include/secp256k1_ecdh.h b/include/secp256k1_ecdh.h index ea18a0c3e0..4afa3427af 100644 --- a/include/secp256k1_ecdh.h +++ b/include/secp256k1_ecdh.h @@ -60,6 +60,32 @@ 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); +#include "secp256k1_extrakeys.h" + +/** Compute an EC Diffie-Hellman secret in constant time (xonly version) + * (Only present if extrakeys is enabled). + * + * Returns: 1: exponentiation was successful + * 0: scalar was invalid (zero or overflow) or hashfp returned 0 + * Args: ctx: pointer to a context object. + * Out: output: pointer to an array to be filled by hashfp. + * In: pubkey: a pointer to a secp256k1_xonly_pubkey containing an initialized public key. + * seckey: a 32-byte scalar with which to multiply the point. + * hashfp: pointer to a hash function. If NULL, + * secp256k1_ecdh_hash_function_xonly_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_hash_function_xonly_sha256). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdh_xonly( + const secp256k1_context* ctx, + unsigned char *output, + const secp256k1_xonly_pubkey *pubkey, + const unsigned char *scalar, + secp256k1_ecdh_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 0ac9800e70..aa0aafd434 100644 --- a/src/modules/ecdh/main_impl.h +++ b/src/modules/ecdh/main_impl.h @@ -39,38 +39,27 @@ const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256 = ecdh_ha const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_xonly_sha256 = ecdh_hash_function_xonly_sha256; const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default = ecdh_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) { +static int ecdh_core(unsigned char *output, secp256k1_ge *pt, const unsigned char *scalar, secp256k1_ecdh_hash_function hashfp, void *data) { int ret = 0; int overflow = 0; secp256k1_gej res; - secp256k1_ge pt; secp256k1_scalar s; unsigned char x[32]; unsigned char y[32]; - VERIFY_CHECK(ctx != NULL); - ARG_CHECK(output != NULL); - ARG_CHECK(point != NULL); - ARG_CHECK(scalar != NULL); - - if (hashfp == NULL) { - hashfp = secp256k1_ecdh_hash_function_default; - } - - secp256k1_pubkey_load(ctx, &pt, point); secp256k1_scalar_set_b32(&s, scalar, &overflow); overflow |= secp256k1_scalar_is_zero(&s); secp256k1_scalar_cmov(&s, &secp256k1_scalar_one, overflow); - secp256k1_ecmult_const(&res, &pt, &s, 256); - secp256k1_ge_set_gej(&pt, &res); + secp256k1_ecmult_const(&res, pt, &s, 256); + secp256k1_ge_set_gej(pt, &res); /* Compute a hash of the point */ - secp256k1_fe_normalize(&pt.x); - secp256k1_fe_normalize(&pt.y); - secp256k1_fe_get_b32(x, &pt.x); - secp256k1_fe_get_b32(y, &pt.y); + secp256k1_fe_normalize(&pt->x); + secp256k1_fe_normalize(&pt->y); + secp256k1_fe_get_b32(x, &pt->x); + secp256k1_fe_get_b32(y, &pt->y); ret = hashfp(output, x, y, data); @@ -81,4 +70,44 @@ int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *output, const se return !!ret & !overflow; } +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) { + secp256k1_ge pt; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output != NULL); + ARG_CHECK(point != NULL); + ARG_CHECK(scalar != NULL); + + if (hashfp == NULL) { + hashfp = secp256k1_ecdh_hash_function_default; + } + + secp256k1_pubkey_load(ctx, &pt, point); + return ecdh_core(output, &pt, scalar, hashfp, data); +} + +#ifdef ENABLE_MODULE_EXTRAKEYS +#include "../../../include/secp256k1_extrakeys.h" + +int secp256k1_ecdh_xonly(const secp256k1_context* ctx, unsigned char *output, const secp256k1_xonly_pubkey *pubkey, const unsigned char *scalar, secp256k1_ecdh_hash_function hashfp, void *data) { + secp256k1_ge pt; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output != NULL); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(scalar != NULL); + + if (hashfp == NULL) { + hashfp = secp256k1_ecdh_hash_function_xonly_sha256; + } + + /* FIXME: this is secp256k1_xonly_pubkey_load(), but that's buried inside + * extrakeys/main_impl.h. */ + if (!secp256k1_pubkey_load(ctx, &pt, (const secp256k1_pubkey *) pubkey)) { + return 0; + } + return ecdh_core(output, &pt, scalar, hashfp, data); +} +#endif /* ENABLE_MODULE_EXTRAKEYS */ + #endif /* SECP256K1_MODULE_ECDH_MAIN_H */ From a3a4cff8b8bb847d5074bc134b6c3a9f7cb833cf Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 20 Oct 2021 11:27:05 +1030 Subject: [PATCH 3/3] extrakeys: test for x-only ecdh. Signed-off-by: Rusty Russell --- src/modules/extrakeys/tests_impl.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/modules/extrakeys/tests_impl.h b/src/modules/extrakeys/tests_impl.h index dd53f9e12a..495f7a4338 100644 --- a/src/modules/extrakeys/tests_impl.h +++ b/src/modules/extrakeys/tests_impl.h @@ -571,6 +571,35 @@ void test_keypair_add(void) { secp256k1_context_destroy(verify); } +void test_xonly_ecdh(void) { +#ifdef ENABLE_MODULE_ECDH + unsigned char sk1[32], sk2[32]; + secp256k1_keypair keypair1, keypair2; + secp256k1_xonly_pubkey xpubkey1, xpubkey2; + secp256k1_pubkey pubkey1; + unsigned char output1[32], output2[32], output3[32]; + + /* Create random points */ + secp256k1_testrand256(sk1); + secp256k1_testrand256(sk2); + CHECK(secp256k1_keypair_create(ctx, &keypair1, sk1) == 1); + CHECK(secp256k1_keypair_create(ctx, &keypair2, sk2) == 1); + CHECK(secp256k1_keypair_xonly_pub(ctx, &xpubkey1, NULL, &keypair1) == 1); + CHECK(secp256k1_keypair_xonly_pub(ctx, &xpubkey2, NULL, &keypair2) == 1); + + /* They should get the same shared secret. */ + CHECK(secp256k1_ecdh_xonly(ctx, output1, &xpubkey1, sk2, NULL, NULL) == 1); + CHECK(secp256k1_ecdh_xonly(ctx, output2, &xpubkey2, sk1, NULL, NULL) == 1); + CHECK(memcmp(output1, output2, sizeof(output1)) == 0); + + /* We can also do ECDH via normal compressed pubkey functions, if we + * use the same hashfn */ + CHECK(secp256k1_keypair_pub(ctx, &pubkey1, &keypair1) == 1); + CHECK(secp256k1_ecdh(ctx, output3, &pubkey1, sk2, secp256k1_ecdh_hash_function_xonly_sha256, NULL) == 1); + CHECK(memcmp(output1, output3, sizeof(output1)) == 0); +#endif /* ENABLE_MODULE_ECDH */ +} + void run_extrakeys_tests(void) { /* xonly key test cases */ test_xonly_pubkey(); @@ -578,6 +607,7 @@ void run_extrakeys_tests(void) { test_xonly_pubkey_tweak_check(); test_xonly_pubkey_tweak_recursive(); test_xonly_pubkey_comparison(); + test_xonly_ecdh(); /* keypair tests */ test_keypair();