Skip to content

x-only ECDH support #994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions include/secp256k1_ecdh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,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
Expand Down
78 changes: 60 additions & 18 deletions src/modules/ecdh/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,43 @@ 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) {
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);

Expand All @@ -68,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 */
30 changes: 30 additions & 0 deletions src/modules/extrakeys/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,13 +571,43 @@ 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();
test_xonly_pubkey_tweak();
test_xonly_pubkey_tweak_check();
test_xonly_pubkey_tweak_recursive();
test_xonly_pubkey_comparison();
test_xonly_ecdh();

/* keypair tests */
test_keypair();
Expand Down