diff --git a/include/secp256k1_ecdsa_adaptor.h b/include/secp256k1_ecdsa_adaptor.h index 7bc432776..c67dd0a4b 100644 --- a/include/secp256k1_ecdsa_adaptor.h +++ b/include/secp256k1_ecdsa_adaptor.h @@ -10,7 +10,22 @@ extern "C" { * 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). -*/ + * + * WARNING! DANGER AHEAD! + * As mentioned in Lloyd Fournier's paper, the adaptor signature leaks the + * Diffie-Hellman key between the signing key and the encryption key. This is + * not a problem for ECDSA adaptor signatures themselves, but may result in a + * complete loss of security when they are composed with other schemes. More + * specifically, let us refer to the signer's public key as X = x*G, and to the + * encryption key as Y = y*G. Given X, Y and the adaptor signature, it is + * trivial to compute Y^x = X^y. + * + * A defense is to not reuse the signing key of ECDSA adaptor signatures in + * protocols that rely on the hardness of the CDH problem, e.g., Diffie-Hellman + * key exchange and ElGamal encryption. In general, it is a well-established + * cryptographic practice to seperate keys for different purposes whenever + * possible. + */ /** A pointer to a function to deterministically generate a nonce. * @@ -46,6 +61,100 @@ typedef int (*secp256k1_nonce_function_hardened_ecdsa_adaptor)( */ SECP256K1_API extern const secp256k1_nonce_function_hardened_ecdsa_adaptor secp256k1_nonce_function_ecdsa_adaptor; +/** Encrypted Signing + * + * Creates an adaptor signature, which includes a proof to verify the adaptor + * signature. + * WARNING: Make sure you have read and understood the WARNING at the top of + * this file and applied the suggested countermeasures. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object, initialized for signing + * Out: adaptor_sig162: pointer to 162 byte to store the returned signature + * In: seckey32: pointer to 32 byte secret key that will be used for + * signing + * enckey: pointer to the encryption public key + * msg32: pointer to the 32-byte message hash to sign + * noncefp: pointer to a nonce generation function. If NULL, + * secp256k1_nonce_function_ecdsa_adaptor is used + * ndata: pointer to arbitrary data used by the nonce generation + * function (can be NULL). If it is non-NULL and + * secp256k1_nonce_function_ecdsa_adaptor is used, then + * ndata must be a pointer to 32-byte auxiliary randomness + * as per BIP-340. + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_encrypt( + const secp256k1_context* ctx, + unsigned char *adaptor_sig162, + unsigned char *seckey32, + const secp256k1_pubkey *enckey, + const unsigned char *msg32, + secp256k1_nonce_function_hardened_ecdsa_adaptor noncefp, + void *ndata +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Encryption Verification + * + * Verifies that the adaptor decryption key 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 + * In: adaptor_sig162: pointer to 162-byte signature to verify + * pubkey: pointer to the public key corresponding to the secret key + * used for signing + * msg32: pointer to the 32-byte message hash being verified + * enckey: pointer to the adaptor encryption public key + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_verify( + const secp256k1_context* ctx, + const unsigned char *adaptor_sig162, + const secp256k1_pubkey *pubkey, + const unsigned char *msg32, + const secp256k1_pubkey *enckey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Signature Decryption + * + * Derives an ECDSA signature from an adaptor signature and an adaptor decryption key. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object + * Out: sig: pointer to the ECDSA signature to create + * In: deckey32: pointer to 32-byte decryption secret key for the adaptor + * encryption public key + * adaptor_sig162: pointer to 162-byte adaptor sig + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_decrypt( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature *sig, + const unsigned char *deckey32, + const unsigned char *adaptor_sig162 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Decryption Key Recovery + * + * Extracts the adaptor decryption key from the complete signature and the adaptor + * signature. + * + * Returns: 1 on success, 0 on failure + * Args: ctx: a secp256k1 context object, initialized for signing + * Out: deckey32: pointer to 32-byte adaptor decryption key for the adaptor + * encryption public key + * In: sig: pointer to ECDSA signature to recover the adaptor decryption + * key from + * adaptor_sig162: pointer to adaptor signature to recover the adaptor + * decryption key from + * enckey: pointer to the adaptor encryption public key + */ +SECP256K1_API int secp256k1_ecdsa_adaptor_recover( + const secp256k1_context* ctx, + unsigned char *deckey32, + const secp256k1_ecdsa_signature *sig, + const unsigned char *adaptor_sig162, + const secp256k1_pubkey *enckey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + #ifdef __cplusplus } #endif diff --git a/src/modules/ecdsa_adaptor/main_impl.h b/src/modules/ecdsa_adaptor/main_impl.h index 005baf0e8..18e6132dd 100644 --- a/src/modules/ecdsa_adaptor/main_impl.h +++ b/src/modules/ecdsa_adaptor/main_impl.h @@ -10,6 +10,61 @@ #include "include/secp256k1_ecdsa_adaptor.h" #include "modules/ecdsa_adaptor/dleq_impl.h" +/* (R, R', s', dleq_proof) */ +static int secp256k1_ecdsa_adaptor_sig_serialize(unsigned char *adaptor_sig162, secp256k1_ge *r, secp256k1_ge *rp, const secp256k1_scalar *sp, const secp256k1_scalar *dleq_proof_e, const secp256k1_scalar *dleq_proof_s) { + size_t size = 33; + + if (!secp256k1_eckey_pubkey_serialize(r, adaptor_sig162, &size, 1)) { + return 0; + } + if (!secp256k1_eckey_pubkey_serialize(rp, &adaptor_sig162[33], &size, 1)) { + return 0; + } + secp256k1_scalar_get_b32(&adaptor_sig162[66], sp); + secp256k1_scalar_get_b32(&adaptor_sig162[98], dleq_proof_e); + secp256k1_scalar_get_b32(&adaptor_sig162[130], dleq_proof_s); + + return 1; +} + +static int secp256k1_ecdsa_adaptor_sig_deserialize(secp256k1_ge *r, secp256k1_scalar *sigr, secp256k1_ge *rp, secp256k1_scalar *sp, secp256k1_scalar *dleq_proof_e, secp256k1_scalar *dleq_proof_s, const unsigned char *adaptor_sig162) { + /* If r is deserialized, require that a sigr is provided to receive + * the X-coordinate */ + VERIFY_CHECK((r == NULL) || (r != NULL && sigr != NULL)); + if (r != NULL) { + if (!secp256k1_eckey_pubkey_parse(r, &adaptor_sig162[0], 33)) { + return 0; + } + } + if (sigr != NULL) { + secp256k1_scalar_set_b32(sigr, &adaptor_sig162[1], NULL); + if (secp256k1_scalar_is_zero(sigr)) { + return 0; + } + } + if (rp != NULL) { + if (!secp256k1_eckey_pubkey_parse(rp, &adaptor_sig162[33], 33)) { + return 0; + } + } + if (sp != NULL) { + if (!secp256k1_scalar_set_b32_seckey(sp, &adaptor_sig162[66])) { + return 0; + } + } + if (dleq_proof_e != NULL) { + secp256k1_scalar_set_b32(dleq_proof_e, &adaptor_sig162[98], NULL); + } + if (dleq_proof_s != NULL) { + int overflow; + secp256k1_scalar_set_b32(dleq_proof_s, &adaptor_sig162[130], &overflow); + if (overflow) { + return 0; + } + } + return 1; +} + /* Initializes SHA256 with fixed midstate. This midstate was computed by applying * SHA256 to SHA256("ECDSAadaptor/non")||SHA256("ECDSAadaptor/non"). */ static void secp256k1_nonce_function_ecdsa_adaptor_sha256_tagged(secp256k1_sha256 *sha) { @@ -91,4 +146,220 @@ static int nonce_function_ecdsa_adaptor(unsigned char *nonce32, const unsigned c const secp256k1_nonce_function_hardened_ecdsa_adaptor secp256k1_nonce_function_ecdsa_adaptor = nonce_function_ecdsa_adaptor; +int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char *adaptor_sig162, unsigned char *seckey32, const secp256k1_pubkey *enckey, const unsigned char *msg32, secp256k1_nonce_function_hardened_ecdsa_adaptor noncefp, void *ndata) { + secp256k1_scalar k; + secp256k1_gej rj, rpj; + secp256k1_ge r, rp; + secp256k1_ge enckey_ge; + secp256k1_scalar dleq_proof_s; + secp256k1_scalar dleq_proof_e; + secp256k1_scalar sk; + secp256k1_scalar msg; + secp256k1_scalar sp; + secp256k1_scalar sigr; + secp256k1_scalar n; + unsigned char nonce32[32] = { 0 }; + unsigned char buf33[33]; + size_t size = 33; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(adaptor_sig162 != NULL); + ARG_CHECK(seckey32 != NULL); + ARG_CHECK(enckey != NULL); + ARG_CHECK(msg32 != NULL); + + secp256k1_scalar_clear(&dleq_proof_e); + secp256k1_scalar_clear(&dleq_proof_s); + + if (noncefp == NULL) { + noncefp = secp256k1_nonce_function_ecdsa_adaptor; + } + + ret &= secp256k1_pubkey_load(ctx, &enckey_ge, enckey); + ret &= secp256k1_eckey_pubkey_serialize(&enckey_ge, buf33, &size, 1); + ret &= !!noncefp(nonce32, msg32, seckey32, buf33, ecdsa_adaptor_algo, sizeof(ecdsa_adaptor_algo), ndata); + secp256k1_scalar_set_b32(&k, nonce32, NULL); + ret &= !secp256k1_scalar_is_zero(&k); + secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret); + + /* R' := k*G */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rpj, &k); + secp256k1_ge_set_gej(&rp, &rpj); + /* R = k*Y; */ + secp256k1_ecmult_const(&rj, &enckey_ge, &k, 256); + secp256k1_ge_set_gej(&r, &rj); + /* We declassify the non-secret values rp and r to allow using them + * as branch points. */ + secp256k1_declassify(ctx, &rp, sizeof(rp)); + secp256k1_declassify(ctx, &r, sizeof(r)); + + /* dleq_proof = DLEQ_prove(k, (R', Y, R)) */ + ret &= secp256k1_dleq_prove(ctx, &dleq_proof_s, &dleq_proof_e, &k, &enckey_ge, &rp, &r, noncefp, ndata); + + ret &= secp256k1_scalar_set_b32_seckey(&sk, seckey32); + secp256k1_scalar_cmov(&sk, &secp256k1_scalar_one, !ret); + secp256k1_scalar_set_b32(&msg, msg32, NULL); + secp256k1_fe_normalize(&r.x); + secp256k1_fe_get_b32(buf33, &r.x); + secp256k1_scalar_set_b32(&sigr, buf33, NULL); + ret &= !secp256k1_scalar_is_zero(&sigr); + /* s' = k⁻¹(m + R.x * x) */ + secp256k1_scalar_mul(&n, &sigr, &sk); + secp256k1_scalar_add(&n, &n, &msg); + secp256k1_scalar_inverse(&sp, &k); + secp256k1_scalar_mul(&sp, &sp, &n); + ret &= !secp256k1_scalar_is_zero(&sp); + + /* return (R, R', s', dleq_proof) */ + ret &= secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig162, &r, &rp, &sp, &dleq_proof_e, &dleq_proof_s); + + secp256k1_memczero(adaptor_sig162, 162, !ret); + secp256k1_scalar_clear(&n); + secp256k1_scalar_clear(&k); + secp256k1_scalar_clear(&sk); + + return ret; +} + +int secp256k1_ecdsa_adaptor_verify(const secp256k1_context* ctx, const unsigned char *adaptor_sig162, const secp256k1_pubkey *pubkey, const unsigned char *msg32, const secp256k1_pubkey *enckey) { + secp256k1_scalar dleq_proof_s, dleq_proof_e; + secp256k1_scalar msg; + secp256k1_ge pubkey_ge; + secp256k1_ge r, rp; + secp256k1_scalar sp; + secp256k1_scalar sigr; + secp256k1_ge enckey_ge; + secp256k1_gej derived_rp; + secp256k1_scalar sn, u1, u2; + secp256k1_gej pubkeyj; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(adaptor_sig162 != NULL); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(enckey != NULL); + + if (!secp256k1_ecdsa_adaptor_sig_deserialize(&r, &sigr, &rp, &sp, &dleq_proof_e, &dleq_proof_s, adaptor_sig162)) { + return 0; + } + if (!secp256k1_pubkey_load(ctx, &enckey_ge, enckey)) { + return 0; + } + /* DLEQ_verify((R', Y, R), dleq_proof) */ + if(!secp256k1_dleq_verify(&ctx->ecmult_ctx, &dleq_proof_s, &dleq_proof_e, &rp, &enckey_ge, &r)) { + return 0; + } + secp256k1_scalar_set_b32(&msg, msg32, NULL); + if (!secp256k1_pubkey_load(ctx, &pubkey_ge, pubkey)) { + return 0; + } + + /* return R' == s'⁻¹(m * G + R.x * X) */ + secp256k1_scalar_inverse_var(&sn, &sp); + secp256k1_scalar_mul(&u1, &sn, &msg); + secp256k1_scalar_mul(&u2, &sn, &sigr); + secp256k1_gej_set_ge(&pubkeyj, &pubkey_ge); + secp256k1_ecmult(&ctx->ecmult_ctx, &derived_rp, &pubkeyj, &u2, &u1); + if (secp256k1_gej_is_infinity(&derived_rp)) { + return 0; + } + secp256k1_gej_neg(&derived_rp, &derived_rp); + secp256k1_gej_add_ge_var(&derived_rp, &derived_rp, &rp, NULL); + return secp256k1_gej_is_infinity(&derived_rp); +} + +int secp256k1_ecdsa_adaptor_decrypt(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sig, const unsigned char *deckey32, const unsigned char *adaptor_sig162) { + secp256k1_scalar deckey; + secp256k1_scalar sp; + secp256k1_scalar s; + secp256k1_scalar sigr; + int overflow; + int high; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(deckey32 != NULL); + ARG_CHECK(adaptor_sig162 != NULL); + + secp256k1_scalar_clear(&sp); + secp256k1_scalar_set_b32(&deckey, deckey32, &overflow); + ret &= !overflow; + ret &= secp256k1_ecdsa_adaptor_sig_deserialize(NULL, &sigr, NULL, &sp, NULL, NULL, adaptor_sig162); + ret &= !secp256k1_scalar_is_zero(&deckey); + secp256k1_scalar_inverse(&s, &deckey); + /* s = s' * y⁻¹ */ + 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); + + secp256k1_memczero(&sig->data[0], 64, !ret); + secp256k1_scalar_clear(&deckey); + secp256k1_scalar_clear(&sp); + secp256k1_scalar_clear(&s); + + return ret; +} + +int secp256k1_ecdsa_adaptor_recover(const secp256k1_context* ctx, unsigned char *deckey32, const secp256k1_ecdsa_signature *sig, const unsigned char *adaptor_sig162, const secp256k1_pubkey *enckey) { + secp256k1_scalar sp, adaptor_sigr; + secp256k1_scalar s, r; + secp256k1_scalar deckey; + secp256k1_ge enckey_expected_ge; + secp256k1_gej enckey_expected_gej; + unsigned char enckey33[33]; + unsigned char enckey_expected33[33]; + size_t size = 33; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(deckey32 != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(adaptor_sig162 != NULL); + ARG_CHECK(enckey != NULL); + + if (!secp256k1_ecdsa_adaptor_sig_deserialize(NULL, &adaptor_sigr, NULL, &sp, NULL, NULL, adaptor_sig162)) { + return 0; + } + secp256k1_ecdsa_signature_load(ctx, &r, &s, sig); + /* Check that we're not looking at some unrelated signature */ + ret &= secp256k1_scalar_eq(&adaptor_sigr, &r); + /* y = s⁻¹ * s' */ + ret &= !secp256k1_scalar_is_zero(&s); + secp256k1_scalar_inverse(&deckey, &s); + secp256k1_scalar_mul(&deckey, &deckey, &sp); + + /* Deal with ECDSA malleability */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &enckey_expected_gej, &deckey); + secp256k1_ge_set_gej(&enckey_expected_ge, &enckey_expected_gej); + /* We declassify non-secret enckey_expected_ge to allow using it as a + * branch point. */ + secp256k1_declassify(ctx, &enckey_expected_ge, sizeof(enckey_expected_ge)); + if (!secp256k1_eckey_pubkey_serialize(&enckey_expected_ge, enckey_expected33, &size, SECP256K1_EC_COMPRESSED)) { + return 0; + } + if (!secp256k1_ec_pubkey_serialize(ctx, enckey33, &size, enckey, SECP256K1_EC_COMPRESSED)) { + return 0; + } + if (secp256k1_memcmp_var(&enckey_expected33[1], &enckey33[1], 32) != 0) { + return 0; + } + if (enckey_expected33[0] != enckey33[0]) { + /* try Y_implied == -Y */ + secp256k1_scalar_negate(&deckey, &deckey); + } + secp256k1_scalar_get_b32(deckey32, &deckey); + + secp256k1_scalar_clear(&deckey); + secp256k1_scalar_clear(&sp); + secp256k1_scalar_clear(&s); + + return ret; +} + #endif