diff --git a/include/secp256k1_musig.h b/include/secp256k1_musig.h index c71b92ce6..cb75d52c9 100644 --- a/include/secp256k1_musig.h +++ b/include/secp256k1_musig.h @@ -495,6 +495,31 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_partial_sig_verif const secp256k1_musig_session *session ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); +/** Computes a "signature point" + * + * The signature point for a MuSig2 signer is s*G, where s is the (unique) + * partial signature of the signer. The signature point can be computed without + * knowledge of the signer's secret key. + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args ctx: pointer to a context object, initialized for verification + * Out: sig_point: signature point + * In: pubnonce: public nonce of the signer in the signing session + * pubkey: public key of the signer in the signing session + * keyagg_cache: pointer to the keyagg_cache that was output when the + * aggregate public key for this signing session + * session: pointer to the session that was created with + * `musig_nonce_process` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_partial_sig_point( + const secp256k1_context* ctx, + secp256k1_pubkey *sig_point, + const secp256k1_musig_pubnonce *pubnonce, + const secp256k1_pubkey *pubkey, + const secp256k1_musig_keyagg_cache *keyagg_cache, + const secp256k1_musig_session *session +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + /** Aggregates partial signatures * * Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 91420731d..11567c4da 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -696,6 +696,70 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2 return secp256k1_gej_is_infinity(&tmp); } +/* TODO: abstract parts of the code that are shared with verify */ +int secp256k1_musig_partial_sig_point(const secp256k1_context* ctx, secp256k1_pubkey *sig_point, const secp256k1_musig_pubnonce *pubnonce, const secp256k1_pubkey *pubkey, const secp256k1_musig_keyagg_cache *keyagg_cache, const secp256k1_musig_session *session) { + secp256k1_keyagg_cache_internal cache_i; + secp256k1_musig_session_internal session_i; + secp256k1_scalar mu, e; + secp256k1_gej pkj; + secp256k1_ge nonce_pt[2]; + secp256k1_gej rj; + secp256k1_gej tmpj; + secp256k1_ge tmp; + secp256k1_ge pkp; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubnonce != NULL); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(keyagg_cache != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_musig_session_load(ctx, &session_i, session)) { + return 0; + } + + /* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */ + /* TODO: use multiexp to compute -s*G + e*mu*pubkey + aggnonce[0] + b*aggnonce[1] */ + if (!secp256k1_musig_pubnonce_load(ctx, nonce_pt, pubnonce)) { + return 0; + } + secp256k1_gej_set_ge(&rj, &nonce_pt[1]); + secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL); + secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pt[0], NULL); + + if (!secp256k1_pubkey_load(ctx, &pkp, pubkey)) { + return 0; + } + if (!secp256k1_keyagg_cache_load(ctx, &cache_i, keyagg_cache)) { + return 0; + } + /* Multiplying the challenge by the KeyAgg coefficient is equivalent + * to multiplying the signer's public key by the coefficient, except + * much easier to do. */ + secp256k1_musig_keyaggcoef(&mu, &cache_i, &pkp); + secp256k1_scalar_mul(&e, &session_i.challenge, &mu); + + /* Negate e if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc. + * This corresponds to the line "Let g' = g⋅gacc mod n" and the multiplication "g'⋅e" + * in the specification. */ + if (secp256k1_fe_is_odd(&cache_i.pk.y) + != cache_i.parity_acc) { + secp256k1_scalar_negate(&e, &e); + } + + /* Compute -s*G + e*pkj + rj (e already includes the keyagg coefficient mu) */ + secp256k1_gej_set_ge(&pkj, &pkp); + secp256k1_ecmult(&tmpj, &pkj, &e, NULL); + if (session_i.fin_nonce_parity) { + secp256k1_gej_neg(&rj, &rj); + } + secp256k1_gej_add_var(&tmpj, &tmpj, &rj, NULL); + secp256k1_ge_set_gej(&tmp, &tmpj); + secp256k1_pubkey_save(sig_point, &tmp); + + return 1; +} + int secp256k1_musig_partial_sig_agg(const secp256k1_context* ctx, unsigned char *sig64, const secp256k1_musig_session *session, const secp256k1_musig_partial_sig * const* partial_sigs, size_t n_sigs) { size_t i; secp256k1_musig_session_internal session_i; diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index ff6637d0a..88b6e5d9a 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -1295,6 +1295,60 @@ void musig_test_vectors_sigagg(void) { } } +void musig_sig_point_test(secp256k1_scratch_space *scratch) { + unsigned char sk[2][32]; + secp256k1_keypair keypair[2]; + secp256k1_musig_pubnonce pubnonce[2]; + const secp256k1_musig_pubnonce *pubnonce_ptr[2]; + secp256k1_musig_aggnonce aggnonce; + unsigned char msg[32]; + secp256k1_xonly_pubkey agg_pk; + secp256k1_musig_keyagg_cache keyagg_cache; + unsigned char session_id[2][32]; + secp256k1_musig_secnonce secnonce[2]; + secp256k1_pubkey pk[2]; + const secp256k1_pubkey *pk_ptr[2]; + secp256k1_musig_partial_sig partial_sig[2]; + const secp256k1_musig_partial_sig *partial_sig_ptr[2]; + unsigned char final_sig[64]; + secp256k1_musig_session session; + int i; + + secp256k1_testrand256(msg); + for (i = 0; i < 2; i++) { + secp256k1_testrand256(session_id[i]); + secp256k1_testrand256(sk[i]); + pk_ptr[i] = &pk[i]; + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + + CHECK(create_keypair_and_pk(&keypair[i], &pk[i], sk[i])); + CHECK(secp256k1_musig_nonce_gen(ctx, &secnonce[i], &pubnonce[i], session_id[i], sk[i], &pk[i], NULL, NULL, NULL) == 1); + } + + CHECK(secp256k1_musig_pubkey_agg(ctx, scratch, &agg_pk, &keyagg_cache, pk_ptr, 2) == 1); + CHECK(secp256k1_musig_nonce_agg(ctx, &aggnonce, pubnonce_ptr, 2) == 1); + CHECK(secp256k1_musig_nonce_process(ctx, &session, &aggnonce, msg, &keyagg_cache, NULL) == 1); + + for (i = 0; i < 2; i++) { + secp256k1_pubkey sigp; + secp256k1_ge sigp_ge; + secp256k1_gej sigp_expected_gej; + secp256k1_scalar s; + CHECK(secp256k1_musig_partial_sign(ctx, &partial_sig[i], &secnonce[i], &keypair[i], &keyagg_cache, &session) == 1); + CHECK(secp256k1_musig_partial_sig_verify(ctx, &partial_sig[i], &pubnonce[i], &pk[i], &keyagg_cache, &session) == 1); + + CHECK(secp256k1_musig_partial_sig_load(ctx, &s, &partial_sig[i])); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &sigp_expected_gej, &s); + CHECK(secp256k1_musig_partial_sig_point(ctx, &sigp, &pubnonce[i], &pk[i], &keyagg_cache, &session)); + CHECK(secp256k1_pubkey_load(ctx, &sigp_ge, &sigp)); + ge_equals_gej(&sigp_ge, &sigp_expected_gej); + } + + CHECK(secp256k1_musig_partial_sig_agg(ctx, final_sig, &session, partial_sig_ptr, 2) == 1); + CHECK(secp256k1_schnorrsig_verify(ctx, final_sig, msg, sizeof(msg), &agg_pk) == 1); +} + void run_musig_tests(void) { int i; secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 1024 * 1024); @@ -1309,6 +1363,7 @@ void run_musig_tests(void) { * parities */ scriptless_atomic_swap(scratch); musig_tweak_test(scratch); + musig_sig_point_test(scratch); } sha256_tag_test(); musig_test_vectors_keyagg();