From b9d91b3ecbc9b5fd3797294d51f935360efbec00 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Tue, 10 Nov 2020 22:33:47 +0000 Subject: [PATCH] musig: add pubkey_tweak_add function to allow taproot tweaking --- include/secp256k1_musig.h | 50 ++++++++++++- src/modules/musig/main_impl.h | 84 ++++++++++++++++++--- src/modules/musig/tests_impl.h | 131 ++++++++++++++++++++++++++++++++- 3 files changed, 250 insertions(+), 15 deletions(-) diff --git a/include/secp256k1_musig.h b/include/secp256k1_musig.h index 52b78ef0..46fe6c28 100644 --- a/include/secp256k1_musig.h +++ b/include/secp256k1_musig.h @@ -27,11 +27,18 @@ extern "C" { * pk_hash: The 32-byte hash of the original public keys * pk_parity: Whether the MuSig-aggregated point was negated when * converting it to the combined xonly pubkey. + * is_tweaked: Whether the combined pubkey was tweaked + * tweak: If is_tweaked, array with the 32-byte tweak + * internal_key_parity: If is_tweaked, the parity of the combined pubkey + * before tweaking */ typedef struct { uint64_t magic; unsigned char pk_hash[32]; int pk_parity; + int is_tweaked; + unsigned char tweak[32]; + int internal_key_parity; } secp256k1_musig_pre_session; /** Data structure containing data related to a signing session resulting in a single @@ -139,7 +146,7 @@ typedef struct { * multiexponentiation. If NULL, an inefficient algorithm is used. * Out: combined_pk: the MuSig-combined xonly public key (cannot be NULL) * pre_session: if non-NULL, pointer to a musig_pre_session struct to be used in - * `musig_session_init`. + * `musig_session_init` or `musig_pubkey_tweak_add`. * In: pubkeys: input array of public keys to combine. The order is important; * a different order will result in a different combined public * key (cannot be NULL) @@ -154,6 +161,42 @@ SECP256K1_API int secp256k1_musig_pubkey_combine( size_t n_pubkeys ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); +/** Tweak an x-only public key by adding the generator multiplied with tweak32 + * to it. The resulting output_pubkey with the given internal_pubkey and tweak + * passes `secp256k1_xonly_pubkey_tweak_test`. + * + * This function is only useful before initializing a signing session. If you + * are only computing a public key, but not intending to create a signature for + * it, you can just use `secp256k1_xonly_pubkey_tweak_add`. Can only be called + * once with a given pre_session. + * + * Returns: 0 if the arguments are invalid or the resulting public key would be + * invalid (only when the tweak is the negation of the corresponding + * secret key). 1 otherwise. + * Args: ctx: pointer to a context object initialized for verification + * (cannot be NULL) + * pre_session: pointer to a `musig_pre_session` struct initialized in + * `musig_pubkey_combine` (cannot be NULL) + * Out: output_pubkey: pointer to a public key to store the result. Will be set + * to an invalid value if this function returns 0 (cannot + * be NULL) + * In: internal_pubkey: pointer to the `combined_pk` from + * `musig_pubkey_combine` to which the tweak is applied. + * (cannot be NULL). + * tweak32: pointer to a 32-byte tweak. If the tweak is invalid + * according to secp256k1_ec_seckey_verify, this function + * returns 0. For uniformly random 32-byte arrays the + * chance of being invalid is negligible (around 1 in + * 2^128) (cannot be NULL). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_pubkey_tweak_add( + const secp256k1_context* ctx, + secp256k1_musig_pre_session *pre_session, + secp256k1_pubkey *output_pubkey, + const secp256k1_xonly_pubkey *internal_pubkey, + const unsigned char *tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + /** Initializes a signing session for a signer * * Returns: 1: session is successfully initialized @@ -173,8 +216,9 @@ SECP256K1_API int secp256k1_musig_pubkey_combine( * because it reduces nonce misuse resistance. If NULL, must be * set with `musig_session_get_public_nonce`. * combined_pk: the combined xonly public key of all signers (cannot be NULL) - * pre_session: pointer to a musig_pre_session struct from - * `musig_pubkey_combine` (cannot be NULL) + * pre_session: pointer to a musig_pre_session struct after initializing + * it with `musig_pubkey_combine` and optionally provided to + * `musig_pubkey_tweak_add` (cannot be NULL). * n_signers: length of signers array. Number of signers participating in * the MuSig. Must be greater than 0 and at most 2^32 - 1. * my_index: index of this signer in the signers array. Must be less diff --git a/src/modules/musig/main_impl.h b/src/modules/musig/main_impl.h index 7f750945..d397eab3 100644 --- a/src/modules/musig/main_impl.h +++ b/src/modules/musig/main_impl.h @@ -127,10 +127,36 @@ int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scrat pre_session->magic = pre_session_magic; memcpy(pre_session->pk_hash, ecmult_data.ell, 32); pre_session->pk_parity = pk_parity; + pre_session->is_tweaked = 0; } return 1; } +int secp256k1_musig_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_musig_pre_session *pre_session, secp256k1_pubkey *output_pubkey, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) { + secp256k1_ge pk; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pre_session != NULL); + ARG_CHECK(pre_session->magic == pre_session_magic); + /* This function can only be called once because otherwise signing would not + * succeed */ + ARG_CHECK(pre_session->is_tweaked == 0); + + pre_session->internal_key_parity = pre_session->pk_parity; + if(!secp256k1_xonly_pubkey_tweak_add(ctx, output_pubkey, internal_pubkey, tweak32)) { + return 0; + } + + memcpy(pre_session->tweak, tweak32, 32); + pre_session->is_tweaked = 1; + + if (!secp256k1_pubkey_load(ctx, &pk, output_pubkey)) { + return 0; + } + pre_session->pk_parity = secp256k1_extrakeys_ge_even_y(&pk); + return 1; +} + static const uint64_t session_magic = 0xd92e6fc1ee41b4cbUL; int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_session *session, secp256k1_musig_session_signer_data *signers, unsigned char *nonce_commitment32, const unsigned char *session_id32, const unsigned char *msg32, const secp256k1_xonly_pubkey *combined_pk, const secp256k1_musig_pre_session *pre_session, size_t n_signers, size_t my_index, const unsigned char *seckey) { @@ -181,22 +207,33 @@ int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_s return 0; } secp256k1_musig_coefficient(&mu, session->pre_session.pk_hash, (uint32_t) my_index); - /* Compute the signers public key point and determine if the secret needs to - * be negated before signing. If the signer's pubkey has an odd Y coordinate - * XOR the MuSig-combined pubkey has an odd Y coordinate, the secret has to - * be negated. This can be seen by looking at the secret key belonging to - * `combined_pk`. Let's define + /* Compute the signer's public key point and determine if the secret is + * negated before signing. That happens if if the signer's pubkey has an odd + * Y coordinate XOR the MuSig-combined pubkey has an odd Y coordinate XOR + * (if tweaked) the internal key has an odd Y coordinate. + * + * This can be seen by looking at the secret key belonging to `combined_pk`. + * Let's define * P' := mu_0*|P_0| + ... + mu_n*|P_n| where P_i is the i-th public key * point x_i*G, mu_i is the i-th musig coefficient and |.| is a function * that normalizes a point to an even Y by negating if necessary similar to * secp256k1_extrakeys_ge_even_y. Then we have - * P := |P'| the combined xonly public key. Also, P = x*G where x = - * sum_i(b_i*mu_i*x_i) and b_i = -1 if (P != |P'| XOR P_i != |P_i|) and 1 - * otherwise. */ + * P := |P'| + t*G where t is the tweak. + * And the combined xonly public key is + * |P| = x*G + * where x = sum_i(b_i*mu_i*x_i) + b'*t + * b' = -1 if P != |P|, 1 otherwise + * b_i = -1 if (P_i != |P_i| XOR P' != |P'| XOR P != |P|) and 1 + * otherwise. + */ secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &secret); secp256k1_ge_set_gej(&p, &pj); secp256k1_fe_normalize(&p.y); - if (secp256k1_fe_is_odd(&p.y) != session->pre_session.pk_parity) { + if((secp256k1_fe_is_odd(&p.y) + + session->pre_session.pk_parity + + (session->pre_session.is_tweaked + && session->pre_session.internal_key_parity)) + % 2 == 1) { secp256k1_scalar_negate(&secret, &secret); } secp256k1_scalar_mul(&secret, &secret, &mu); @@ -502,6 +539,26 @@ int secp256k1_musig_partial_sig_combine(const secp256k1_context* ctx, const secp secp256k1_scalar_add(&s, &s, &term); } + /* If there is a tweak then add (or subtract) `msghash` times `tweak` to `s`.*/ + if (session->pre_session.is_tweaked) { + unsigned char msghash[32]; + secp256k1_scalar e, scalar_tweak; + int overflow = 0; + + secp256k1_musig_compute_messagehash(ctx, msghash, session); + secp256k1_scalar_set_b32(&e, msghash, NULL); + secp256k1_scalar_set_b32(&scalar_tweak, session->pre_session.tweak, &overflow); + if (overflow || !secp256k1_eckey_privkey_tweak_mul(&e, &scalar_tweak)) { + /* This mimics the behavior of secp256k1_ec_seckey_tweak_mul regarding + * overflow and tweak being 0. */ + return 0; + } + if (session->pre_session.pk_parity) { + secp256k1_scalar_negate(&e, &e); + } + secp256k1_scalar_add(&s, &s, &e); + } + secp256k1_xonly_pubkey_load(ctx, &noncep, &session->combined_nonce); VERIFY_CHECK(!secp256k1_fe_is_odd(&noncep.y)); secp256k1_fe_normalize(&noncep.x); @@ -548,10 +605,15 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2 if (!secp256k1_xonly_pubkey_load(ctx, &rp, &signer->nonce)) { return 0; } + /* If the MuSig-combined point has an odd Y coordinate, the signers will * sign for the negation of their individual xonly public key such that the - * combined signature is valid for the MuSig aggregated xonly key. */ - if (session->pre_session.pk_parity) { + * combined signature is valid for the MuSig aggregated xonly key. If the + * MuSig-combined point was tweaked then `e` is negated if the combined key + * has an odd Y coordinate XOR the internal key has an odd Y coordinate.*/ + if (session->pre_session.pk_parity + != (session->pre_session.is_tweaked + && session->pre_session.internal_key_parity)) { secp256k1_scalar_negate(&e, &e); } diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index bb6ef3ae..22a5d05b 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -170,6 +170,42 @@ void musig_api_tests(secp256k1_scratch_space *scratch) { CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 2) == 1); CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 2) == 1); + /** Tweaking */ + ecount = 0; + { + secp256k1_xonly_pubkey tmp_internal_pk = combined_pk; + secp256k1_pubkey tmp_output_pk; + secp256k1_musig_pre_session tmp_pre_session = pre_session; + CHECK(secp256k1_musig_pubkey_tweak_add(ctx, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, tweak) == 1); + /* Reset pre_session */ + tmp_pre_session = pre_session; + CHECK(secp256k1_musig_pubkey_tweak_add(none, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, tweak) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_musig_pubkey_tweak_add(sign, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, tweak) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, tweak) == 1); + CHECK(ecount == 2); + tmp_pre_session = pre_session; + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, NULL, &tmp_output_pk, &tmp_internal_pk, tweak) == 0); + CHECK(ecount == 3); + /* Uninitialized pre_session */ + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &pre_session_uninitialized, &tmp_output_pk, &tmp_internal_pk, tweak) == 0); + CHECK(ecount == 4); + /* Using the same pre_session twice does not work */ + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, tweak) == 1); + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, tweak) == 0); + CHECK(ecount == 5); + tmp_pre_session = pre_session; + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, NULL, &tmp_internal_pk, tweak) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, &tmp_output_pk, NULL, tweak) == 0); + CHECK(ecount == 7); + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, NULL) == 0); + CHECK(ecount == 8); + CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_pre_session, &tmp_output_pk, &tmp_internal_pk, ones) == 0); + CHECK(ecount == 8); + } + /** Session creation **/ ecount = 0; CHECK(secp256k1_musig_session_init(none, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); @@ -819,6 +855,97 @@ void sha256_tag_test(void) { CHECK(memcmp(buf, buf2, 32) == 0); } +/* Attempts to create a signature for the combined public key using given secret + * keys and pre_session. */ +void musig_tweak_test_helper(const secp256k1_xonly_pubkey* combined_pubkey, const unsigned char *sk0, const unsigned char *sk1, secp256k1_musig_pre_session *pre_session) { + secp256k1_musig_session session[2]; + secp256k1_musig_session_signer_data signers0[2]; + secp256k1_musig_session_signer_data signers1[2]; + secp256k1_xonly_pubkey pk[2]; + unsigned char session_id[2][32]; + unsigned char msg[32]; + unsigned char nonce_commitment[2][32]; + unsigned char nonce[2][32]; + const unsigned char *ncs[2]; + secp256k1_musig_partial_signature partial_sig[2]; + unsigned char final_sig[64]; + + secp256k1_testrand256(session_id[0]); + secp256k1_testrand256(session_id[1]); + secp256k1_testrand256(msg); + + CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk0) == 1); + CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk1) == 1); + + CHECK(secp256k1_musig_session_init(ctx, &session[0], signers0, nonce_commitment[0], session_id[0], msg, combined_pubkey, pre_session, 2, 0, sk0) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[1], signers1, nonce_commitment[1], session_id[1], msg, combined_pubkey, pre_session, 2, 1, sk1) == 1); + /* Set nonce commitments */ + ncs[0] = nonce_commitment[0]; + ncs[1] = nonce_commitment[1]; + CHECK(secp256k1_musig_session_get_public_nonce(ctx, &session[0], signers0, nonce[0], ncs, 2, NULL) == 1); + CHECK(secp256k1_musig_session_get_public_nonce(ctx, &session[1], signers1, nonce[1], ncs, 2, NULL) == 1); + /* Set nonces */ + CHECK(secp256k1_musig_set_nonce(ctx, &signers0[0], nonce[0]) == 1); + CHECK(secp256k1_musig_set_nonce(ctx, &signers0[1], nonce[1]) == 1); + CHECK(secp256k1_musig_set_nonce(ctx, &signers1[0], nonce[0]) == 1); + CHECK(secp256k1_musig_set_nonce(ctx, &signers1[1], nonce[1]) == 1); + CHECK(secp256k1_musig_session_combine_nonces(ctx, &session[0], signers0, 2, NULL, NULL) == 1); + CHECK(secp256k1_musig_session_combine_nonces(ctx, &session[1], signers1, 2, NULL, NULL) == 1); + CHECK(secp256k1_musig_partial_sign(ctx, &session[0], &partial_sig[0]) == 1); + CHECK(secp256k1_musig_partial_sign(ctx, &session[1], &partial_sig[1]) == 1); + CHECK(secp256k1_musig_partial_sig_verify(ctx, &session[0], &signers0[1], &partial_sig[1], &pk[1]) == 1); + CHECK(secp256k1_musig_partial_sig_verify(ctx, &session[1], &signers1[0], &partial_sig[0], &pk[0]) == 1); + CHECK(secp256k1_musig_partial_sig_combine(ctx, &session[0], final_sig, partial_sig, 2)); + CHECK(secp256k1_schnorrsig_verify(ctx, final_sig, msg, combined_pubkey) == 1); +} + +/* In this test we create a combined public key P and a commitment Q = P + + * hash(P, contract)*G. Then we test that we can sign for both public keys. In + * order to sign for Q we use the tweak32 argument of partial_sig_combine. */ +void musig_tweak_test(secp256k1_scratch_space *scratch) { + unsigned char sk[2][32]; + secp256k1_xonly_pubkey pk[2]; + secp256k1_musig_pre_session pre_session_P; + secp256k1_musig_pre_session pre_session_Q; + secp256k1_xonly_pubkey P; + unsigned char P_serialized[32]; + secp256k1_pubkey Q; + int Q_parity; + secp256k1_xonly_pubkey Q_xonly; + unsigned char Q_serialized[32]; + + secp256k1_sha256 sha; + unsigned char contract[32]; + unsigned char ec_commit_tweak[32]; + + /* Setup */ + secp256k1_testrand256(sk[0]); + secp256k1_testrand256(sk[1]); + secp256k1_testrand256(contract); + + CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk[0]) == 1); + CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk[1]) == 1); + CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &P, &pre_session_P, pk, 2) == 1); + + CHECK(secp256k1_xonly_pubkey_serialize(ctx, P_serialized, &P) == 1); + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, P_serialized, 32); + secp256k1_sha256_write(&sha, contract, 32); + secp256k1_sha256_finalize(&sha, ec_commit_tweak); + pre_session_Q = pre_session_P; + CHECK(secp256k1_musig_pubkey_tweak_add(ctx, &pre_session_Q, &Q, &P, ec_commit_tweak) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &Q_xonly, &Q_parity, &Q)); + CHECK(secp256k1_xonly_pubkey_serialize(ctx, Q_serialized, &Q_xonly)); + /* Check that musig_pubkey_tweak_add produces same result as + * xonly_pubkey_tweak_add. */ + CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, Q_serialized, Q_parity, &P, ec_commit_tweak) == 1); + + /* Test signing for P */ + musig_tweak_test_helper(&P, sk[0], sk[1], &pre_session_P); + /* Test signing for Q */ + musig_tweak_test_helper(&Q_xonly, sk[0], sk[1], &pre_session_Q); +} + void run_musig_tests(void) { int i; secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 1024 * 1024); @@ -829,8 +956,10 @@ void run_musig_tests(void) { musig_api_tests(scratch); musig_state_machine_tests(scratch); for (i = 0; i < count; i++) { - /* Run multiple times to ensure that the nonce has different y parities */ + /* Run multiple times to ensure that pk and nonce have different y + * parities */ scriptless_atomic_swap(scratch); + musig_tweak_test(scratch); } sha256_tag_test();