From 8ba5f68b1df99350aa037a644041034cf46842dc Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Wed, 7 Jan 2026 23:35:18 +0100 Subject: [PATCH] refactor, key: move `CreateMuSig2PartialSig` to `musig.{h,cpp}` module Compared to `CreateMuSig2Nonce`, creating a partial signature has a stronger link to the secret key used, but for consistency reasons it still makes sense to move all functionality that call the secp256k1 musig API functions to the `musig.{h,cpp}` module for consistency. Can be reviewed via the git option `--color-moved=dimmed-zebra`. --- src/key.cpp | 90 --------------------------------------------- src/key.h | 3 -- src/musig.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++++ src/musig.h | 1 + src/script/sign.cpp | 2 +- 5 files changed, 90 insertions(+), 94 deletions(-) diff --git a/src/key.cpp b/src/key.cpp index c61616f1207..cc03df1cd94 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -350,95 +349,6 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const return KeyPair(*this, merkle_root); } -std::optional CKey::CreateMuSig2PartialSig(const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector& pubkeys, const std::map>& pubnonces, MuSig2SecNonce& secnonce, const std::vector>& tweaks) -{ - secp256k1_keypair keypair; - if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, UCharCast(begin()))) return std::nullopt; - - // Get the keyagg cache and aggregate pubkey - secp256k1_musig_keyagg_cache keyagg_cache; - if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt; - - // Check that there are enough pubnonces - if (pubnonces.size() != pubkeys.size()) return std::nullopt; - - // Parse the pubnonces - std::vector> signers_data; - std::vector pubnonce_ptrs; - std::optional our_pubkey_idx; - CPubKey our_pubkey = GetPubKey(); - for (const CPubKey& part_pk : pubkeys) { - const auto& pn_it = pubnonces.find(part_pk); - if (pn_it == pubnonces.end()) return std::nullopt; - const std::vector pubnonce = pn_it->second; - if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt; - if (part_pk == our_pubkey) { - our_pubkey_idx = signers_data.size(); - } - - auto& [secp_pk, secp_pn] = signers_data.emplace_back(); - - if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) { - return std::nullopt; - } - - if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) { - return std::nullopt; - } - } - if (our_pubkey_idx == std::nullopt) { - return std::nullopt; - } - pubnonce_ptrs.reserve(signers_data.size()); - for (auto& [_, pn] : signers_data) { - pubnonce_ptrs.push_back(&pn); - } - - // Aggregate nonces - secp256k1_musig_aggnonce aggnonce; - if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) { - return std::nullopt; - } - - // Apply tweaks - for (const auto& [tweak, xonly] : tweaks) { - if (xonly) { - if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) { - return std::nullopt; - } - } else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) { - return std::nullopt; - } - } - - // Create musig_session - secp256k1_musig_session session; - if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) { - return std::nullopt; - } - - // Create partial signature - secp256k1_musig_partial_sig psig; - if (!secp256k1_musig_partial_sign(secp256k1_context_static, &psig, secnonce.Get(), &keypair, &keyagg_cache, &session)) { - return std::nullopt; - } - // The secnonce must be deleted after signing to prevent nonce reuse. - secnonce.Invalidate(); - - // Verify partial signature - if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &psig, &(signers_data.at(*our_pubkey_idx).second), &(signers_data.at(*our_pubkey_idx).first), &keyagg_cache, &session)) { - return std::nullopt; - } - - // Serialize - uint256 sig; - if (!secp256k1_musig_partial_sig_serialize(secp256k1_context_static, sig.data(), &psig)) { - return std::nullopt; - } - - return sig; -} - CKey GenerateRandomKey(bool compressed) noexcept { CKey key; diff --git a/src/key.h b/src/key.h index e9f34a1ad49..cd77dcd0ee2 100644 --- a/src/key.h +++ b/src/key.h @@ -7,7 +7,6 @@ #ifndef BITCOIN_KEY_H #define BITCOIN_KEY_H -#include #include #include #include @@ -223,8 +222,6 @@ public: * Merkle root of the script tree). */ KeyPair ComputeKeyPair(const uint256* merkle_root) const; - - std::optional CreateMuSig2PartialSig(const uint256& hash, const CPubKey& aggregate_pubkey, const std::vector& pubkeys, const std::map>& pubnonces, MuSig2SecNonce& secnonce, const std::vector>& tweaks); }; CKey GenerateRandomKey(bool compressed = true) noexcept; diff --git a/src/musig.cpp b/src/musig.cpp index 73089ea89a8..9a1b34421cd 100644 --- a/src/musig.cpp +++ b/src/musig.cpp @@ -161,6 +161,94 @@ std::vector CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& return out; } +std::optional CreateMuSig2PartialSig(const uint256& sighash, const CKey& our_seckey, const CPubKey& aggregate_pubkey, const std::vector& pubkeys, const std::map>& pubnonces, MuSig2SecNonce& secnonce, const std::vector>& tweaks) +{ + secp256k1_keypair keypair; + if (!secp256k1_keypair_create(GetSecp256k1SignContext(), &keypair, UCharCast(our_seckey.begin()))) return std::nullopt; + + // Get the keyagg cache and aggregate pubkey + secp256k1_musig_keyagg_cache keyagg_cache; + if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt; + + // Check that there are enough pubnonces + if (pubnonces.size() != pubkeys.size()) return std::nullopt; + + // Parse the pubnonces + std::vector> signers_data; + std::vector pubnonce_ptrs; + std::optional our_pubkey_idx; + CPubKey our_pubkey = our_seckey.GetPubKey(); + for (const CPubKey& part_pk : pubkeys) { + const auto& pn_it = pubnonces.find(part_pk); + if (pn_it == pubnonces.end()) return std::nullopt; + const std::vector pubnonce = pn_it->second; + if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt; + if (part_pk == our_pubkey) { + our_pubkey_idx = signers_data.size(); + } + + auto& [secp_pk, secp_pn] = signers_data.emplace_back(); + + if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) { + return std::nullopt; + } + + if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) { + return std::nullopt; + } + } + if (our_pubkey_idx == std::nullopt) { + return std::nullopt; + } + pubnonce_ptrs.reserve(signers_data.size()); + for (auto& [_, pn] : signers_data) { + pubnonce_ptrs.push_back(&pn); + } + + // Aggregate nonces + secp256k1_musig_aggnonce aggnonce; + if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) { + return std::nullopt; + } + + // Apply tweaks + for (const auto& [tweak, xonly] : tweaks) { + if (xonly) { + if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) { + return std::nullopt; + } + } else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) { + return std::nullopt; + } + } + + // Create musig_session + secp256k1_musig_session session; + if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) { + return std::nullopt; + } + + // Create partial signature + secp256k1_musig_partial_sig psig; + if (!secp256k1_musig_partial_sign(secp256k1_context_static, &psig, secnonce.Get(), &keypair, &keyagg_cache, &session)) { + return std::nullopt; + } + // The secnonce must be deleted after signing to prevent nonce reuse. + secnonce.Invalidate(); + + // Verify partial signature + if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &psig, &(signers_data.at(*our_pubkey_idx).second), &(signers_data.at(*our_pubkey_idx).first), &keyagg_cache, &session)) { + return std::nullopt; + } + + // Serialize + uint256 sig; + if (!secp256k1_musig_partial_sig_serialize(secp256k1_context_static, sig.data(), &psig)) { + return std::nullopt; + } + + return sig; +} std::optional> CreateMuSig2AggregateSig(const std::vector& part_pubkeys, const CPubKey& aggregate_pubkey, const std::vector>& tweaks, const uint256& sighash, const std::map>& pubnonces, const std::map& partial_sigs) { diff --git a/src/musig.h b/src/musig.h index e18d245015c..b61c2f3f49b 100644 --- a/src/musig.h +++ b/src/musig.h @@ -60,6 +60,7 @@ public: uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash); std::vector CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CKey& our_seckey, const CPubKey& aggregate_pubkey, const std::vector& pubkeys); +std::optional CreateMuSig2PartialSig(const uint256& hash, const CKey& our_seckey, const CPubKey& aggregate_pubkey, const std::vector& pubkeys, const std::map>& pubnonces, MuSig2SecNonce& secnonce, const std::vector>& tweaks); std::optional> CreateMuSig2AggregateSig(const std::vector& participants, const CPubKey& aggregate_pubkey, const std::vector>& tweaks, const uint256& sighash, const std::map>& pubnonces, const std::map& partial_sigs); #endif // BITCOIN_MUSIG_H diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 4386e473b3f..e8a0071ee96 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -161,7 +161,7 @@ bool MutableTransactionSignatureCreator::CreateMuSig2PartialSig(const SigningPro if (!secnonce || !secnonce->get().IsValid()) return false; // Compute the sig - std::optional sig = key.CreateMuSig2PartialSig(*sighash, aggregate_pubkey, pubkeys, pubnonces, *secnonce, tweaks); + std::optional sig = ::CreateMuSig2PartialSig(*sighash, key, aggregate_pubkey, pubkeys, pubnonces, *secnonce, tweaks); if (!sig) return false; partial_sig = std::move(*sig);