diff --git a/Makefile.am b/Makefile.am index e73b1baf38..860509ed77 100644 --- a/Makefile.am +++ b/Makefile.am @@ -150,3 +150,8 @@ endif if ENABLE_MODULE_RECOVERY include src/modules/recovery/Makefile.am.include endif + +if ENABLE_MODULE_ECDSA_ADAPTOR +include src/modules/ecdsa_adaptor/Makefile.am.include +endif + diff --git a/configure.ac b/configure.ac index cbcd17537d..8f3348b302 100644 --- a/configure.ac +++ b/configure.ac @@ -135,6 +135,12 @@ AC_ARG_ENABLE(module_recovery, [enable_module_recovery=$enableval], [enable_module_recovery=no]) +AC_ARG_ENABLE(module_ecdsa-adaptor, + AS_HELP_STRING([--enable-module-ecdsa-adaptor],[enable ECDSA adaptor module [default=no]]), + [enable_module_ecdsa_adaptor=$enableval], + [enable_module_ecdsa_adaptor=no]) + + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [use_external_default_callbacks=$enableval], @@ -492,6 +498,10 @@ if test x"$enable_module_recovery" = x"yes"; then AC_DEFINE(ENABLE_MODULE_RECOVERY, 1, [Define this symbol to enable the ECDSA pubkey recovery module]) fi +if test x"$enable_module_ecdsa_adaptor" = x"yes"; then + AC_DEFINE(ENABLE_MODULE_ECDSA_ADAPTOR, 1, [Define this symbol to enable the ECDSA adaptor module]) +fi + AC_C_BIGENDIAN() if test x"$use_external_asm" = x"yes"; then @@ -530,6 +540,7 @@ AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"]) AM_CONDITIONAL([USE_ECMULT_STATIC_PRECOMPUTATION], [test x"$set_precomp" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_ECDSA_ADAPTOR], [test x"$enable_module_ecdsa_adaptor" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"]) @@ -549,6 +560,7 @@ echo " with benchmarks = $use_benchmark" echo " with coverage = $enable_coverage" echo " module ecdh = $enable_module_ecdh" echo " module recovery = $enable_module_recovery" +echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor" echo echo " asm = $set_asm" echo " bignum = $set_bignum" diff --git a/include/secp256k1_ecdsa_adaptor.h b/include/secp256k1_ecdsa_adaptor.h new file mode 100644 index 0000000000..085d8d1fb1 --- /dev/null +++ b/include/secp256k1_ecdsa_adaptor.h @@ -0,0 +1,131 @@ +#ifndef SECP256K1_ECDSA_ADAPTOR_H +#define SECP256K1_ECDSA_ADAPTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** This module implements single signer ECDSA adaptor signatures following + * "One-Time Verifiably Encrypted Signatures A.K.A. Adaptor Signatures" by + * Lloyd Fournier + * (https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-November/002316.html + * and https://github.com/LLFourn/one-time-VES/blob/master/main.pdf). + * + * Note that at this module is currently a work in progress. It's not secure + * nor stable. Let me repeat: IT IS EXTREMELY DANGEROUS AND RECKLESS TO USE + * THIS MODULE IN PRODUCTION. DON'T! + * + * This module passes a rudimentary test suite. But there are some things left + * TODO: + * - add API tests + * - add tests for the various overflow conditions + * - refactor adaptor verify to reuse code from secp256k1_ecdsa_verify() + * - test ecdsa_adaptor_sig_verify() more systematically. This is the most + * crucial function in this module. If it passes, we need to be sure that + * it is possible to compute the adaptor secret from the final ecdsa + * signature. + * - add ecdsa_adaptor_sign(), ecdsa_adaptor_adapt() and + * ecdsa_adaptor_extract_secret() to valgrind_ctime_test.c + * - allow using your own nonce function (noncefp, noncedata, synthetic + * nonces) + * - test module in travis + * - add comments to ease review + */ + +/** Adaptor sign ("EncSign") + * + * Creates an adaptor signature along with a proof to verify the adaptor + * signature. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object, initialized for signing + * (cannot be NULL) + * Out: adaptor_sig65: pointer to 65 byte to store the returned signature + * (cannot be NULL) + * adaptor_proof97: pointer to 97 byte to store the adaptor proof (cannot be + * NULL) + * In: seckey32: pointer to 32 byte secret key corresponding to the + * pubkey (cannot be NULL) + * adaptor: pointer to the adaptor point (cannot be NULL) + * msg32: pointer to the 32-byte message to sign (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_sign( + const secp256k1_context* ctx, + unsigned char *adaptor_sig65, + unsigned char *adaptor_proof97, + unsigned char *seckey32, + const secp256k1_pubkey *adaptor, + const unsigned char *msg32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Adaptor verify ("EncVrfy") + * + * Verifies that the adaptor secret can be extracted from the adaptor signature + * and the completed ECDSA signature. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object, initialized for verification + * (cannot be NULL) + * In: adaptor_sig65: pointer to 65-byte signature to verify (cannot be NULL) + * pubkey: pointer to the public key (cannot be NULL) + * msg32: pointer to the 32-byte message (cannot be NULL) + * adaptor: pointer to the adaptor point (cannot be NULL) + * adaptor_proof97: pointer to 97-byte adaptor proof (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_sig_verify( + const secp256k1_context* ctx, + const unsigned char *adaptor_sig65, + const secp256k1_pubkey *pubkey, + const unsigned char *msg32, + const secp256k1_pubkey *adaptor, + const unsigned char *adaptor_proof97 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Adapt signature ("DecSig") + * + * Creates an ECDSA signature from an adaptor signature and an adaptor secret. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object (cannot be NULL) + * Out: sig: pointer to the ecdsa signature to create (cannot + * be NULL) + * In: adaptor_secret32: pointer to 32-byte byte adaptor secret of the adaptor + * point (cannot be NULL) + * adaptor_sig65: pointer to 65-byte byte adaptor sig (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_adapt( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature *sig, + const unsigned char *adaptor_secret32, + const unsigned char *adaptor_sig65 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Adaptor extract ("Rec") + * + * Extracts the adaptor secret from the complete signature and the adaptor + * signature. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object, initialized for signing + * (cannot be NULL) + * Out: adaptor_secret32: pointer to 32-byte adaptor secret of the adaptor point + * (cannot be NULL) + * In: sig: pointer to ecdsa signature to extract the adaptor_secret + * from (cannot be NULL) + * adaptor_sig: pointer to adaptor sig to extract the adaptor_secret + * from (cannot be NULL) + * adaptor: pointer to the adaptor point (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_extract_secret( + const secp256k1_context* ctx, + unsigned char *adaptor_secret32, + const secp256k1_ecdsa_signature *sig, + const unsigned char *adaptor_sig65, + const secp256k1_pubkey *adaptor +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_ADAPTOR_H */ diff --git a/src/hash_impl.h b/src/hash_impl.h index 782f97216c..1ab05cae1d 100644 --- a/src/hash_impl.h +++ b/src/hash_impl.h @@ -163,6 +163,19 @@ static void secp256k1_sha256_finalize(secp256k1_sha256 *hash, unsigned char *out memcpy(out32, (const unsigned char*)out, 32); } +/* Initializes a sha256 struct and writes the 64 byte string + * SHA256(tag)||SHA256(tag) into it. */ +static void secp256k1_sha256_initialize_tagged(secp256k1_sha256 *hash, const unsigned char *tag, size_t taglen) { + unsigned char buf[32]; + secp256k1_sha256_initialize(hash); + secp256k1_sha256_write(hash, tag, taglen); + secp256k1_sha256_finalize(hash, buf); + + secp256k1_sha256_initialize(hash); + secp256k1_sha256_write(hash, buf, 32); + secp256k1_sha256_write(hash, buf, 32); +} + static void secp256k1_hmac_sha256_initialize(secp256k1_hmac_sha256 *hash, const unsigned char *key, size_t keylen) { size_t n; unsigned char rkey[64]; diff --git a/src/modules/ecdsa_adaptor/Makefile.am.include b/src/modules/ecdsa_adaptor/Makefile.am.include new file mode 100644 index 0000000000..e212b93cf7 --- /dev/null +++ b/src/modules/ecdsa_adaptor/Makefile.am.include @@ -0,0 +1,4 @@ +include_HEADERS += include/secp256k1_ecdsa_adaptor.h +noinst_HEADERS += src/modules/ecdsa_adaptor/main_impl.h +noinst_HEADERS += src/modules/ecdsa_adaptor/dleq_impl.h +noinst_HEADERS += src/modules/ecdsa_adaptor/tests_impl.h \ No newline at end of file diff --git a/src/modules/ecdsa_adaptor/dleq_impl.h b/src/modules/ecdsa_adaptor/dleq_impl.h new file mode 100644 index 0000000000..ef9cf110da --- /dev/null +++ b/src/modules/ecdsa_adaptor/dleq_impl.h @@ -0,0 +1,171 @@ +#ifndef _SECP256K1_DLEQ_IMPL_H_ +#define _SECP256K1_DLEQ_IMPL_H_ + +/* Remove terminating NUL bytes */ +static int algo16_len(const unsigned char *algo16) { + int algo16_len = 16; + + /* Remove terminating null bytes */ + while (algo16_len > 0 && !algo16[algo16_len - 1]) { + algo16_len--; + } + return algo16_len; +} + +/* Modified bip340 nonce function */ +static int nonce_function_dleq(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16) { + secp256k1_sha256 sha; + + if (algo16 == NULL) { + return 0; + } + secp256k1_sha256_initialize_tagged(&sha, algo16, algo16_len(algo16)); + secp256k1_sha256_write(&sha, key32, 32); + secp256k1_sha256_write(&sha, msg32, 32); + secp256k1_sha256_finalize(&sha, nonce32); + return 1; +} + +static void secp256k1_dleq_serialize_point(unsigned char *buf33, const secp256k1_ge *p) { + secp256k1_fe x = p->x; + secp256k1_fe y = p->y; + + secp256k1_fe_normalize(&y); + buf33[0] = secp256k1_fe_is_odd(&y); + secp256k1_fe_normalize(&x); + secp256k1_fe_get_b32(&buf33[1], &x); +} + +static int secp256k1_dleq_deserialize_point(secp256k1_ge *p, const unsigned char *buf33) { + secp256k1_fe x; + + if (!secp256k1_fe_set_b32(&x, &buf33[1])) { + return 0; + } + if (buf33[0] > 1) { + return 0; + } + return secp256k1_ge_set_xo_var(p, &x, buf33[0]); +} + +/* TODO: Remove these debuggin functions */ +static void print_buf(const unsigned char *buf, size_t n) { + size_t i; + for (i = 0; i < n; i++) { + printf("%02X", buf[i]); + } + printf("\n"); +} +static void print_scalar(const secp256k1_scalar *x) { + unsigned char buf32[32]; + secp256k1_scalar_get_b32(buf32, x); + print_buf(buf32, 32); +} + +static void print_ge(const secp256k1_ge *p) { + unsigned char buf33[33]; + secp256k1_dleq_serialize_point(buf33, p); + print_buf(buf33, 33); +} + +static void secp256k1_dleq_hash_point(secp256k1_sha256 *sha, const secp256k1_ge *p) { + unsigned char buf33[33]; + secp256k1_dleq_serialize_point(buf33, p); + secp256k1_sha256_write(sha, buf33, 33); +} + +static void secp256k1_dleq_challenge_hash(secp256k1_scalar *e, const unsigned char *algo16, const secp256k1_ge *gen2, const secp256k1_ge *r1, const secp256k1_ge *r2, const secp256k1_ge *p1, const secp256k1_ge *p2) { + secp256k1_sha256 sha; + unsigned char buf32[32]; + + secp256k1_sha256_initialize_tagged(&sha, algo16, algo16_len(algo16)); + secp256k1_dleq_hash_point(&sha, gen2); + secp256k1_dleq_hash_point(&sha, r1); + secp256k1_dleq_hash_point(&sha, r2); + secp256k1_dleq_hash_point(&sha, p1); + secp256k1_dleq_hash_point(&sha, p2); + secp256k1_sha256_finalize(&sha, buf32); + + secp256k1_scalar_set_b32(e, buf32, NULL); +} + +/* p1 = x*G, p2 = x*gen2, constant time */ +static void secp256k1_dleq_pair(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_ge *p1, secp256k1_ge *p2, const secp256k1_scalar *sk, const secp256k1_ge *gen2) { + secp256k1_gej p1j, p2j; + secp256k1_ecmult_gen(ecmult_gen_ctx, &p1j, sk); + secp256k1_ge_set_gej(p1, &p1j); + secp256k1_ecmult_const(&p2j, gen2, sk, 256); + secp256k1_ge_set_gej(p2, &p2j); +} + +/* TODO: allow signing a message by including it in the challenge hash */ +static int secp256k1_dleq_proof(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_scalar *s, secp256k1_scalar *e, const unsigned char *algo16, const secp256k1_scalar *sk, const secp256k1_ge *gen2) { + unsigned char nonce32[32]; + unsigned char key32[32]; + secp256k1_ge p1, p2; + secp256k1_sha256 sha; + secp256k1_gej r1j, r2j; + secp256k1_ge r1, r2; + unsigned char buf32[32]; + secp256k1_scalar k; + + secp256k1_dleq_pair(ecmult_gen_ctx, &p1, &p2, sk, gen2); + + /* Everything that goes into the challenge hash must go into the nonce as well... */ + secp256k1_sha256_initialize(&sha); + secp256k1_dleq_hash_point(&sha, gen2); + secp256k1_dleq_hash_point(&sha, &p1); + secp256k1_dleq_hash_point(&sha, &p2); + secp256k1_sha256_finalize(&sha, buf32); + secp256k1_scalar_get_b32(key32, sk); + if (!nonce_function_dleq(nonce32, buf32, key32, algo16)) { + return 0; + } + secp256k1_scalar_set_b32(&k, nonce32, NULL); + if (secp256k1_scalar_is_zero(&k)) { + return 0; + } + + secp256k1_ecmult_gen(ecmult_gen_ctx, &r1j, &k); + secp256k1_ge_set_gej(&r1, &r1j); + secp256k1_ecmult_const(&r2j, gen2, &k, 256); + secp256k1_ge_set_gej(&r2, &r2j); + + secp256k1_dleq_challenge_hash(e, algo16, gen2, &r1, &r2, &p1, &p2); + secp256k1_scalar_mul(s, e, sk); + secp256k1_scalar_add(s, s, &k); + + secp256k1_scalar_clear(&k); + return 1; +} + +static int secp256k1_dleq_verify(const secp256k1_ecmult_context *ecmult_ctx, const unsigned char *algo16, const secp256k1_scalar *s, const secp256k1_scalar *e, const secp256k1_ge *p1, const secp256k1_ge *gen2, const secp256k1_ge *p2) { + secp256k1_scalar e_neg; + secp256k1_scalar e_expected; + secp256k1_gej gen2j; + secp256k1_gej p1j, p2j; + secp256k1_gej r1j, r2j; + secp256k1_ge r1, r2; + secp256k1_gej tmpj; + + secp256k1_gej_set_ge(&p1j, p1); + secp256k1_gej_set_ge(&p2j, p2); + + secp256k1_scalar_negate(&e_neg, e); + /* R1 = s*G - e*P1 */ + secp256k1_ecmult(ecmult_ctx, &r1j, &p1j, &e_neg, s); + /* R2 = s*gen2 - e*P2 */ + secp256k1_ecmult(ecmult_ctx, &tmpj, &p2j, &e_neg, &secp256k1_scalar_zero); + secp256k1_gej_set_ge(&gen2j, gen2); + secp256k1_ecmult(ecmult_ctx, &r2j, &gen2j, s, &secp256k1_scalar_zero); + secp256k1_gej_add_var(&r2j, &r2j, &tmpj, NULL); + + secp256k1_ge_set_gej(&r1, &r1j); + secp256k1_ge_set_gej(&r2, &r2j); + secp256k1_dleq_challenge_hash(&e_expected, algo16, gen2, &r1, &r2, p1, p2); + + secp256k1_scalar_add(&e_expected, &e_expected, &e_neg); + return secp256k1_scalar_is_zero(&e_expected); +} + +#endif /* _SECP256K1_DLEQ_IMPL_H_ */ diff --git a/src/modules/ecdsa_adaptor/main_impl.h b/src/modules/ecdsa_adaptor/main_impl.h new file mode 100644 index 0000000000..4a505c508e --- /dev/null +++ b/src/modules/ecdsa_adaptor/main_impl.h @@ -0,0 +1,315 @@ +/********************************************************************** + * Copyright (c) 2015 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_MODULE_ECDSA_ADAPTOR_MAIN_H +#define SECP256K1_MODULE_ECDSA_ADAPTOR_MAIN_H + +#include "include/secp256k1_ecdsa_adaptor.h" +#include "modules/ecdsa_adaptor/dleq_impl.h" + +static void secp256k1_ecdsa_adaptor_sig_serialize(unsigned char *adaptor_sig65, const secp256k1_ge *r, const secp256k1_scalar *sp) { + secp256k1_dleq_serialize_point(adaptor_sig65, r); + secp256k1_scalar_get_b32(&adaptor_sig65[33], sp); +} + +static int secp256k1_ecdsa_adaptor_sig_deserialize(secp256k1_ge *r, secp256k1_scalar *sigr, secp256k1_scalar *sp, const unsigned char *adaptor_sig65) { + /* Ensure that whenever you call this function to deserialize r you also + * check that X fits into a sigr */ + VERIFY_CHECK((r == NULL) || (r != NULL && sigr != NULL)); + if (r != NULL) { + if (!secp256k1_dleq_deserialize_point(r, &adaptor_sig65[0])) { + return 0; + } + } + if (sigr != NULL) { + int overflow; + secp256k1_scalar_set_b32(sigr, &adaptor_sig65[1], &overflow); + if(overflow) { + return 0; + } + } + if (sp != NULL) { + int overflow; + secp256k1_scalar_set_b32(sp, &adaptor_sig65[33], &overflow); + if(overflow) { + return 0; + } + } + return 1; +} + +static void secp256k1_ecdsa_adaptor_proof_serialize(unsigned char *adaptor_proof97, const secp256k1_ge *rp, const secp256k1_scalar *dleq_proof_s, const secp256k1_scalar *dleq_proof_e) { + secp256k1_dleq_serialize_point(adaptor_proof97, rp); + secp256k1_scalar_get_b32(&adaptor_proof97[33], dleq_proof_s); + secp256k1_scalar_get_b32(&adaptor_proof97[33+32], dleq_proof_e); +} + +static int secp256k1_ecdsa_adaptor_proof_deserialize(secp256k1_ge *rp, secp256k1_scalar *dleq_proof_s, secp256k1_scalar *dleq_proof_e, const unsigned char *adaptor_proof97) { + int overflow; + if (!secp256k1_dleq_deserialize_point(rp, &adaptor_proof97[0])) { + return 0; + } + secp256k1_scalar_set_b32(dleq_proof_s, &adaptor_proof97[33], &overflow); + if (overflow) { + return 0; + } + secp256k1_scalar_set_b32(dleq_proof_e, &adaptor_proof97[33 + 32], &overflow); + if (overflow) { + return 0; + } + return 1; +} + +int secp256k1_ecdsa_adaptor_fe_to_scalar(secp256k1_scalar *s, const secp256k1_fe *fe) { + unsigned char b[32]; + int overflow; + + secp256k1_fe_get_b32(b, fe); + secp256k1_scalar_set_b32(s, b, &overflow); + return !overflow; +} + +/* 5. s' = k⁻¹(H(m) + x_coord(R)x) */ +int secp256k1_ecdsa_adaptor_sign_helper(secp256k1_scalar *sigs, secp256k1_scalar *message, secp256k1_scalar *k, secp256k1_ge *r, secp256k1_scalar *sk) { + secp256k1_scalar sigr; + secp256k1_scalar n; + + secp256k1_fe_normalize(&r->x); + if (!secp256k1_ecdsa_adaptor_fe_to_scalar(&sigr, &r->x)) { + return 0; + } + secp256k1_scalar_mul(&n, &sigr, sk); + secp256k1_scalar_add(&n, &n, message); + secp256k1_scalar_inverse(sigs, k); + secp256k1_scalar_mul(sigs, sigs, &n); + + secp256k1_scalar_clear(&n); + return !secp256k1_scalar_is_zero(sigs); +} + +int secp256k1_ecdsa_adaptor_sign(const secp256k1_context* ctx, unsigned char *adaptor_sig65, unsigned char *adaptor_proof97, unsigned char *seckey32, const secp256k1_pubkey *adaptor, const unsigned char *msg32) { + unsigned char nonce32[32]; + unsigned char buf33[33]; + secp256k1_sha256 sha; + secp256k1_scalar k; + secp256k1_gej rj, rpj; + secp256k1_ge r, rp; + secp256k1_ge adaptor_ge; + secp256k1_scalar dleq_proof_s; + secp256k1_scalar dleq_proof_e; + secp256k1_scalar sk; + secp256k1_scalar msg; + secp256k1_scalar sp; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(adaptor_sig65 != NULL); + ARG_CHECK(adaptor_proof97 != NULL); + ARG_CHECK(adaptor != NULL); + ARG_CHECK(msg32 != NULL); + + /* 1. Choose k randomly, R' = k*G */ + /* Include msg32 and adaptor in nonce derivation */ + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, msg32, 32); + if (!secp256k1_pubkey_load(ctx, &adaptor_ge, adaptor)) { + return 0; + } + secp256k1_dleq_serialize_point(buf33, &adaptor_ge); + secp256k1_sha256_write(&sha, buf33, 33); + secp256k1_sha256_finalize(&sha, buf33); + if (!nonce_function_dleq(nonce32, buf33, seckey32, (unsigned char *)"ECDSAAdaptorNon")) { + return 0; + } + secp256k1_scalar_set_b32(&k, nonce32, NULL); + if (secp256k1_scalar_is_zero(&k)) { + return 0; + } + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rpj, &k); + + /* 2. R = k*Y; */ + secp256k1_ecmult_const(&rj, &adaptor_ge, &k, 256); + + /* 4. [sic] proof = DLEQ_prove((G,R'),(Y, R)) */ + if (!secp256k1_dleq_proof(&ctx->ecmult_gen_ctx, &dleq_proof_s, &dleq_proof_e, (unsigned char *)"ECDSAAdaptorSig", &k, &adaptor_ge)) { + return 0; + } + + /* 5. s' = k⁻¹(H(m) + x_coord(R)x) */ + secp256k1_ge_set_gej(&r, &rj); + secp256k1_scalar_set_b32(&sk, seckey32, &overflow); + if (overflow || secp256k1_scalar_is_zero(&sk)) { + return 0; + } + secp256k1_scalar_set_b32(&msg, msg32, NULL); + if(!secp256k1_ecdsa_adaptor_sign_helper(&sp, &msg, &k, &r, &sk)) { + secp256k1_scalar_clear(&k); + secp256k1_scalar_clear(&sk); + return 0; + } + + /* 6. return (R, R', s', proof) */ + secp256k1_ge_set_gej(&rp, &rpj); + secp256k1_ecdsa_adaptor_proof_serialize(adaptor_proof97, &rp, &dleq_proof_s, &dleq_proof_e); + secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig65, &r, &sp); + + secp256k1_scalar_clear(&k); + secp256k1_scalar_clear(&sk); + return 1; +} + +SECP256K1_API int secp256k1_ecdsa_adaptor_sig_verify_helper(const secp256k1_context* ctx, secp256k1_ge *result, secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_ge *pubkey, const secp256k1_scalar *message) { + secp256k1_scalar sn, u1, u2; + secp256k1_gej pubkeyj; + secp256k1_gej pr; + + if (secp256k1_scalar_is_zero(sigr) || secp256k1_scalar_is_zero(sigs)) { + return 0; + } + + secp256k1_scalar_inverse_var(&sn, sigs); + secp256k1_scalar_mul(&u1, &sn, message); + secp256k1_scalar_mul(&u2, &sn, sigr); + + secp256k1_gej_set_ge(&pubkeyj, pubkey); + secp256k1_ecmult(&ctx->ecmult_ctx, &pr, &pubkeyj, &u2, &u1); + if (secp256k1_gej_is_infinity(&pr)) { + return 0; + } + secp256k1_ge_set_gej(result, &pr); + return 1; +} + +int secp256k1_ecdsa_adaptor_sig_verify(const secp256k1_context* ctx, const unsigned char *adaptor_sig65, const secp256k1_pubkey *pubkey, const unsigned char *msg32, const secp256k1_pubkey *adaptor, const unsigned char *adaptor_proof97) { + secp256k1_scalar dleq_proof_s, dleq_proof_e; + secp256k1_scalar msg; + secp256k1_ge q; + secp256k1_ge r, rp; + secp256k1_scalar sp; + secp256k1_scalar sigr; + secp256k1_ge adaptor_ge; + secp256k1_ge rhs; + secp256k1_gej lhs; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(adaptor_sig65 != NULL); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(adaptor != NULL); + ARG_CHECK(adaptor_proof97 != NULL); + + /* 1. DLEQ_verify((G,R'),(Y, R)) */ + if (!secp256k1_ecdsa_adaptor_proof_deserialize(&rp, &dleq_proof_s, &dleq_proof_e, adaptor_proof97)) { + return 0; + } + if (!secp256k1_ecdsa_adaptor_sig_deserialize(&r, &sigr, &sp, adaptor_sig65)) { + return 0; + } + if (!secp256k1_pubkey_load(ctx, &adaptor_ge, adaptor)) { + return 0; + } + if(!secp256k1_dleq_verify(&ctx->ecmult_ctx, (unsigned char *)"ECDSAAdaptorSig", &dleq_proof_s, &dleq_proof_e, &rp, &adaptor_ge, &r)) { + return 0; + } + + /* 2. return x_coord(R') == x_coord(s'⁻¹(H(m) * G + x_coord(R) * X)) */ + secp256k1_scalar_set_b32(&msg, msg32, NULL); + if (!secp256k1_pubkey_load(ctx, &q, pubkey)) { + return 0; + } + if (!secp256k1_ecdsa_adaptor_sig_verify_helper(ctx, &rhs, &sigr, &sp, &q, &msg)) { + return 0; + } + + secp256k1_gej_set_ge(&lhs, &rp); + secp256k1_ge_neg(&rhs, &rhs); + secp256k1_gej_add_ge_var(&lhs, &lhs, &rhs, NULL); + return secp256k1_gej_is_infinity(&lhs); +} + +int secp256k1_ecdsa_adaptor_adapt(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sig, const unsigned char *adaptor_secret32, const unsigned char *adaptor_sig65) { + secp256k1_scalar adaptor_secret; + secp256k1_scalar sp; + secp256k1_scalar s; + secp256k1_scalar sigr; + int overflow; + unsigned char buf32[32]; + int high; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(adaptor_secret32 != NULL); + ARG_CHECK(adaptor_sig65 != NULL); + + secp256k1_scalar_set_b32(&adaptor_secret, adaptor_secret32, &overflow); + if (overflow) { + return 0; + } + + if (!secp256k1_ecdsa_adaptor_sig_deserialize(NULL, &sigr, &sp, adaptor_sig65)) { + secp256k1_scalar_clear(&adaptor_secret); + return 0; + } + secp256k1_scalar_inverse(&s, &adaptor_secret); + secp256k1_scalar_mul(&s, &s, &sp); + high = secp256k1_scalar_is_high(&s); + secp256k1_scalar_cond_negate(&s, high); + + secp256k1_ecdsa_signature_save(sig, &sigr, &s); + + memset(buf32, 0, sizeof(buf32)); + secp256k1_scalar_clear(&adaptor_secret); + secp256k1_scalar_clear(&sp); + secp256k1_scalar_clear(&s); + + return 1; +} + +int secp256k1_ecdsa_adaptor_extract_secret(const secp256k1_context* ctx, unsigned char *adaptor_secret32, const secp256k1_ecdsa_signature *sig, const unsigned char *adaptor_sig65, const secp256k1_pubkey *adaptor) { + secp256k1_scalar sp, adaptor_sigr; + secp256k1_scalar s, r; + secp256k1_scalar adaptor_secret; + secp256k1_ge adaptor_expected_ge; + secp256k1_gej adaptor_expected_gej; + secp256k1_pubkey adaptor_expected; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(adaptor_secret32 != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(adaptor_sig65 != NULL); + ARG_CHECK(adaptor != NULL); + + if (!secp256k1_ecdsa_adaptor_sig_deserialize(NULL, &adaptor_sigr, &sp, adaptor_sig65)) { + return 0; + } + secp256k1_ecdsa_signature_load(ctx, &r, &s, sig); + /* Check that we're not looking at some unrelated signature */ + if (!secp256k1_scalar_eq(&adaptor_sigr, &r)) { + return 0; + } + secp256k1_scalar_inverse(&adaptor_secret, &s); + secp256k1_scalar_mul(&adaptor_secret, &adaptor_secret, &sp); + + /* Deal with ECDSA malleability */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &adaptor_expected_gej, &adaptor_secret); + secp256k1_ge_set_gej(&adaptor_expected_ge, &adaptor_expected_gej); + secp256k1_pubkey_save(&adaptor_expected, &adaptor_expected_ge); + if (memcmp(&adaptor_expected, adaptor, sizeof(adaptor_expected)) != 0) { + secp256k1_scalar_negate(&adaptor_secret, &adaptor_secret); + } + secp256k1_scalar_get_b32(adaptor_secret32, &adaptor_secret); + + secp256k1_scalar_clear(&adaptor_secret); + secp256k1_scalar_clear(&sp); + secp256k1_scalar_clear(&s); + + return 1; +} + +#endif /* SECP256K1_MODULE_ECDSA_ADAPTOR_MAIN_H */ diff --git a/src/modules/ecdsa_adaptor/tests_impl.h b/src/modules/ecdsa_adaptor/tests_impl.h new file mode 100644 index 0000000000..220f78ff5f --- /dev/null +++ b/src/modules/ecdsa_adaptor/tests_impl.h @@ -0,0 +1,147 @@ +#ifndef SECP256K1_MODULE_ECDSA_ADAPTOR_TESTS_H +#define SECP256K1_MODULE_ECDSA_ADAPTOR_TESTS_H + +#include "include/secp256k1_ecdsa_adaptor.h" + +void rand_scalar(secp256k1_scalar *scalar) { + unsigned char buf32[32]; + secp256k1_rand256(buf32); + secp256k1_scalar_set_b32(scalar, buf32, NULL); +} + +void rand_point(secp256k1_ge *point) { + secp256k1_scalar x; + secp256k1_gej pointj; + rand_scalar(&x); + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pointj, &x); + secp256k1_ge_set_gej(point, &pointj); +} + +void dleq_tests(void) { + secp256k1_scalar s, e; + unsigned char algo16[16] = { 0 }; + secp256k1_scalar sk; + secp256k1_ge gen2; + secp256k1_ge p1, p2; + + rand_point(&gen2); + rand_scalar(&sk); + CHECK(secp256k1_dleq_proof(&ctx->ecmult_gen_ctx, &s, &e, algo16, &sk, &gen2) == 1); + secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &p1, &p2, &sk, &gen2); + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16, &s, &e, &p1, &gen2, &p2) == 1); + + { + unsigned char algo16_tmp[16] = { 1 }; + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16_tmp, &s, &e, &p1, &gen2, &p2) == 0); + } + { + secp256k1_scalar tmp; + secp256k1_scalar_set_int(&tmp, 1); + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16, &tmp, &e, &p1, &gen2, &p2) == 0); + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16, &s, &tmp, &p1, &gen2, &p2) == 0); + } + { + secp256k1_ge p_tmp; + rand_point(&p_tmp); + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16, &s, &e, &p_tmp, &gen2, &p2) == 0); + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16, &s, &e, &p1, &p_tmp, &p2) == 0); + CHECK(secp256k1_dleq_verify(&ctx->ecmult_ctx, algo16, &s, &e, &p1, &gen2, &p_tmp) == 0); + } +} + +void rand_flip_bit(unsigned char *array, size_t n) { + array[secp256k1_rand_int(n)] ^= 1 << secp256k1_rand_int(8); +} + +void adaptor_tests(void) { + unsigned char seckey[32]; + secp256k1_pubkey pubkey; + unsigned char msg[32]; + unsigned char adaptor_secret[32]; + secp256k1_pubkey adaptor; + unsigned char adaptor_sig[65]; + unsigned char adaptor_proof[97]; + secp256k1_ecdsa_signature sig; + + secp256k1_rand256(seckey); + secp256k1_rand256(msg); + secp256k1_rand256(adaptor_secret); + + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, adaptor_secret) == 1); + CHECK(secp256k1_ecdsa_adaptor_sign(ctx, adaptor_sig, adaptor_proof, seckey, &adaptor, msg) == 1); + { + /* Test adaptor_sig_serialize roundtrip */ + secp256k1_ge r; + secp256k1_scalar sigr; + secp256k1_scalar sp; + unsigned char adaptor_sig_tmp[65]; + CHECK(secp256k1_ecdsa_adaptor_sig_deserialize(&r, &sigr, &sp, adaptor_sig) == 1); + secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig_tmp, &r, &sp); + CHECK(memcmp(adaptor_sig_tmp, adaptor_sig, sizeof(adaptor_sig_tmp)) == 0); + } + + /* Test adaptor_sig_verify */ + CHECK(secp256k1_ecdsa_adaptor_sig_verify(ctx, adaptor_sig, &pubkey, msg, &adaptor, adaptor_proof) == 1); + { + unsigned char adaptor_sig_tmp[65]; + memcpy(adaptor_sig_tmp, adaptor_sig, sizeof(adaptor_sig_tmp)); + rand_flip_bit(&adaptor_sig_tmp[1], sizeof(adaptor_sig_tmp) - 1); + CHECK(secp256k1_ecdsa_adaptor_sig_verify(ctx, adaptor_sig_tmp, &pubkey, msg, &adaptor, adaptor_proof) == 0); + } + CHECK(secp256k1_ecdsa_adaptor_sig_verify(ctx, adaptor_sig, &adaptor, msg, &adaptor, adaptor_proof) == 0); + { + unsigned char msg_tmp[32]; + memcpy(msg_tmp, msg, sizeof(msg_tmp)); + rand_flip_bit(msg_tmp, sizeof(msg_tmp)); + CHECK(secp256k1_ecdsa_adaptor_sig_verify(ctx, adaptor_sig, &pubkey, msg_tmp, &adaptor, adaptor_proof) == 0); + } + CHECK(secp256k1_ecdsa_adaptor_sig_verify(ctx, adaptor_sig, &pubkey, msg, &pubkey, adaptor_proof) == 0); + { + unsigned char adaptor_proof_tmp[97]; + memcpy(adaptor_proof_tmp, adaptor_proof, sizeof(adaptor_proof_tmp)); + rand_flip_bit(adaptor_proof_tmp, sizeof(adaptor_proof_tmp)); + CHECK(secp256k1_ecdsa_adaptor_sig_verify(ctx, adaptor_sig, &pubkey, msg, &adaptor, adaptor_proof_tmp) == 0); + } + + /* Test adaptor_adapt */ + CHECK(secp256k1_ecdsa_adaptor_adapt(ctx, &sig, adaptor_secret, adaptor_sig) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pubkey) == 1); + + { + /* Test adaptor_extract_secret */ + unsigned char adaptor_secret_tmp[32]; + CHECK(secp256k1_ecdsa_adaptor_extract_secret(ctx, adaptor_secret_tmp, &sig, adaptor_sig, &adaptor) == 1); + CHECK(memcmp(adaptor_secret, adaptor_secret_tmp, sizeof(adaptor_secret)) == 0); + } +} + +/*/\* TODO: test multi hop lock *\/ */ +/* struct agent { */ +/* secp256k1_pubkey left_lock; */ +/* secp256k1_pubkey right_lock; */ +/* /\* adaptor secret for right_lock - left_lock *\/ */ +/* unsigned char adaptor_secret[32]; */ +/* secp256k1_pubkey pubkey; */ +/* unsigned char secret[32]; */ +/* }; */ + +/* void multi_hop_lock_test(void) { */ +/* struct agent Sender; */ +/* struct agent Intermediate; */ +/* struct agent Receiver; */ + +/* } */ + +void run_ecdsa_adaptor_tests(void) { + int i; + for (i = 0; i < count; i++) { + dleq_tests(); + } + for (i = 0; i < count; i++) { + adaptor_tests(); + } +} + +#endif /* SECP256K1_MODULE_ECDSA_ADAPTOR_TESTS_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index c10ac4cda5..7a8dc9f719 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -712,3 +712,7 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey * #ifdef ENABLE_MODULE_RECOVERY # include "modules/recovery/main_impl.h" #endif + +#ifdef ENABLE_MODULE_ECDSA_ADAPTOR +# include "modules/ecdsa_adaptor/main_impl.h" +#endif diff --git a/src/tests.c b/src/tests.c index 2f2cb71539..e92071fc33 100644 --- a/src/tests.c +++ b/src/tests.c @@ -5,6 +5,7 @@ **********************************************************************/ #if defined HAVE_CONFIG_H + #include "libsecp256k1-config.h" #endif @@ -5166,6 +5167,10 @@ void run_ecdsa_openssl(void) { # include "modules/recovery/tests_impl.h" #endif +#ifdef ENABLE_MODULE_ECDSA_ADAPTOR +# include "modules/ecdsa_adaptor/tests_impl.h" +#endif + void run_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -5314,6 +5319,11 @@ int main(int argc, char **argv) { run_recovery_tests(); #endif +#ifdef ENABLE_MODULE_ECDSA_ADAPTOR + /* ECDSA pubkey recovery tests */ + run_ecdsa_adaptor_tests(); +#endif + /* util tests */ run_memczero_test();