Skip to content
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

Add x-only ECDH support to ecdh module #1198

Open
wants to merge 4 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
47 changes: 47 additions & 0 deletions include/secp256k1_ecdh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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.
Copy link
Contributor

@real-or-random real-or-random Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I truly hope that it's constant time in the output (if hashfp is constant time).

*/
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
Expand Down
2 changes: 1 addition & 1 deletion src/bench.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
Expand Down
6 changes: 6 additions & 0 deletions src/ctime_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 40 additions & 5 deletions src/modules/ecdh/bench_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}
}

Expand All @@ -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);
}
Expand Down
57 changes: 57 additions & 0 deletions src/modules/ecdh/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Loading