diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 937fc7a44a..c1c0fa92a0 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -38,6 +38,9 @@ typedef struct { int nonce_is_negated; } secp256k1_schnorrsig_s2c_opening; +/** The signer commitment in the anti-exfil protocol is the original public nonce. */ +typedef secp256k1_pubkey secp256k1_schnorrsig_anti_exfil_signer_commitment; + /** Parse a sign-to-contract opening. * * Returns: 1 if the opening was fully valid. @@ -257,6 +260,66 @@ SECP256K1_API int secp256k1_schnorrsig_verify_s2c_commit( const secp256k1_schnorrsig_s2c_opening *opening ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create the initial host commitment to `rho`. Part of the Anti-Exfil Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: rand_commitment32: pointer to 32-byte array to store the returned commitment (cannot be NULL) + * In: rand32: the 32-byte randomness to commit to (cannot be NULL). It must come from + * a cryptographically secure RNG. As per the protocol, this value must not + * be revealed to the client until after the host has received the client + * commitment. + */ +SECP256K1_API int secp256k1_schnorrsig_anti_exfil_host_commit( + const secp256k1_context* ctx, + unsigned char* rand_commitment32, + const unsigned char* rand32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + + /** Compute signer's original nonce. Part of the Anti-Exfil Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: signer_commitment: where the signer's public nonce will be placed. (cannot be NULL) + * In: msg: the message to be signed (cannot be NULL) + * msglen: length of the message + * keypair: pointer to an initialized keypair (cannot be NULL). + * rand_commitment32: the 32-byte randomness commitment from the host (cannot be NULL) + */ +SECP256K1_API int secp256k1_schnorrsig_anti_exfil_signer_commit( + const secp256k1_context* ctx, + secp256k1_schnorrsig_anti_exfil_signer_commitment* signer_commitment, + const unsigned char *msg, + size_t msglen, + const secp256k1_keypair *keypair, + const unsigned char* rand_commitment32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Verify a signature was correctly constructed using the Anti-Exfil Protocol. + * + * Returns: 1: the signature is valid and contains a commitment to host_data32 + * 0: failure + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig64: pointer to the 64-byte signature to verify. + * msg: the message being verified. Can only be NULL if msglen is 0. + * msglen: length of the message + * pubkey: pointer to an x-only public key to verify with (cannot be NULL) + * host_data32: the 32-byte data provided by the host (cannot be NULL) + * signer_commitment: signer commitment produced by `secp256k1_schnorrsig_anti_exfil_signer_commit()`. + * opening: the s2c opening provided by the signer (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_anti_exfil_host_verify( + const secp256k1_context* ctx, + const unsigned char *sig64, + const unsigned char *msg, + size_t msglen, + const secp256k1_xonly_pubkey *pubkey, + const unsigned char *host_data32, + const secp256k1_schnorrsig_anti_exfil_signer_commitment *signer_commitment, + const secp256k1_schnorrsig_s2c_opening *opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); + #ifdef __cplusplus } #endif diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 62652cbe8c..31df2fba2a 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -397,4 +397,71 @@ int secp256k1_schnorrsig_verify_s2c_commit(const secp256k1_context* ctx, const u return secp256k1_ec_commit_verify(&r, &original_r, &sha, data32, 32); } +int secp256k1_schnorrsig_anti_exfil_host_commit(const secp256k1_context* ctx, unsigned char* rand_commitment32, const unsigned char* rand32) { + secp256k1_sha256 sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(rand_commitment32 != NULL); + ARG_CHECK(rand32 != NULL); + + secp256k1_sha256_initialize_tagged(&sha, s2c_data_tag, sizeof(s2c_data_tag)); + secp256k1_sha256_write(&sha, rand32, 32); + secp256k1_sha256_finalize(&sha, rand_commitment32); + + return 1; +} + +int secp256k1_schnorrsig_anti_exfil_signer_commit(const secp256k1_context* ctx, secp256k1_schnorrsig_anti_exfil_signer_commitment* signer_commitment, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, const unsigned char* rand_commitment32) { + secp256k1_scalar sk; + secp256k1_scalar k; + secp256k1_gej rj; + secp256k1_ge pk; + secp256k1_ge r; + unsigned char buf[32] = { 0 }; + unsigned char pk_buf[32]; + unsigned char seckey[32]; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(msg != NULL || msglen == 0); + ARG_CHECK(keypair != NULL); + + ret &= secp256k1_keypair_load(ctx, &sk, &pk, keypair); + /* Because we are signing for a x-only pubkey, the secret key is negated + * before signing if the point corresponding to the secret key does not + * have an even Y. */ + if (secp256k1_fe_is_odd(&pk.y)) { + secp256k1_scalar_negate(&sk, &sk); + } + + secp256k1_scalar_get_b32(seckey, &sk); + secp256k1_fe_get_b32(pk_buf, &pk.x); + + ret &= !!secp256k1_nonce_function_bip340(buf, msg, msglen, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), (void*)rand_commitment32); + secp256k1_scalar_set_b32(&k, buf, NULL); + ret &= !secp256k1_scalar_is_zero(&k); + secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret); + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); + secp256k1_ge_set_gej(&r, &rj); + + secp256k1_pubkey_save(signer_commitment, &r); + + return ret; +} + +int secp256k1_schnorrsig_anti_exfil_host_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_xonly_pubkey *pubkey, const unsigned char *host_data32, const secp256k1_schnorrsig_anti_exfil_signer_commitment *signer_commitment, const secp256k1_schnorrsig_s2c_opening *opening) { + /* Verify that the signer commitment matches the opening made when signing */ + if (secp256k1_ec_pubkey_cmp(ctx, signer_commitment, &opening->original_pubnonce)) { + return 0; + } + /* Verify signature */ + if (!secp256k1_schnorrsig_verify(ctx, sig64, msg, msglen, pubkey)) { + return 0; + } + /* Verify that the host nonce contribution was committed to */ + return secp256k1_schnorrsig_verify_s2c_commit(ctx, sig64, host_data32, opening); +} + #endif diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 04b98ef1e9..05eb2a743b 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -1055,6 +1055,50 @@ void test_s2c_opening(void) { } while(i < count); } +/* Uses the s2c primitives to perform the anti-exfil protocol */ +void test_s2c_anti_exfil(void) { + unsigned char sk[32]; + secp256k1_xonly_pubkey pk; + secp256k1_keypair keypair; + unsigned char host_msg[32]; + unsigned char host_commitment[32]; + unsigned char host_nonce_contribution[32]; + secp256k1_schnorrsig_s2c_opening s2c_opening; + secp256k1_schnorrsig_anti_exfil_signer_commitment signer_commitment; + unsigned char sig[64]; + /* Generate a random key, message. */ + { + secp256k1_testrand256(sk); + CHECK(secp256k1_keypair_create(ctx, &keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)); + secp256k1_testrand256_test(host_msg); + secp256k1_testrand256_test(host_nonce_contribution); + } + + /* Protocol step 1. */ + /* Make host commitment. */ + CHECK(secp256k1_schnorrsig_anti_exfil_host_commit(ctx, host_commitment, host_nonce_contribution) == 1); + + /* Protocol step 2. */ + /* Make signer commitment */ + CHECK(secp256k1_schnorrsig_anti_exfil_signer_commit(ctx, &signer_commitment, host_msg, sizeof(host_msg), &keypair, host_commitment) == 1); + + /* Protocol step 3: host_nonce_contribution send to signer to be used in step 4. */ + + /* Protocol step 4. */ + /* Sign with host nonce contribution */ + { + secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT; + extraparams.s2c_opening = &s2c_opening; + extraparams.s2c_data32 = host_nonce_contribution; + CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, host_msg, sizeof(host_msg), &keypair, &extraparams) == 1); + } + + /* Protocol step 5. */ + /* Verify commitment and signature */ + CHECK(secp256k1_schnorrsig_anti_exfil_host_verify(ctx, sig, host_msg, sizeof(host_msg), &pk, host_nonce_contribution, &signer_commitment, &s2c_opening) == 1); +} + void run_schnorrsig_tests(void) { int i; run_nonce_function_bip340_tests(); @@ -1071,6 +1115,7 @@ void run_schnorrsig_tests(void) { } test_schnorrsig_taproot(); test_s2c_opening(); + test_s2c_anti_exfil(); } #endif