diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89fdd855a4..44c59d27ec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,7 @@ message("Configuring secp256k1 subtree...") set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_ECDH OFF CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) -set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE BOOL "" FORCE) +set(SECP256K1_ENABLE_MODULE_MUSIG ON CACHE BOOL "" FORCE) set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) set(SECP256K1_BUILD_EXHAUSTIVE_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) @@ -136,6 +136,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL key.cpp key_io.cpp merkleblock.cpp + musig.cpp net_permissions.cpp net_types.cpp netaddress.cpp diff --git a/src/common/messages.cpp b/src/common/messages.cpp index 5fe3e9e4d8..fc0b4a8861 100644 --- a/src/common/messages.cpp +++ b/src/common/messages.cpp @@ -116,6 +116,10 @@ bilingual_str PSBTErrorString(PSBTError err) return Untranslated("External signer failed to sign"); case PSBTError::UNSUPPORTED: return Untranslated("Signer does not support PSBT"); + case PSBTError::INCOMPLETE: + return Untranslated("Input needs additional signatures or other data"); + case PSBTError::OK: + return Untranslated("No errors"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/common/types.h b/src/common/types.h index 0d9cb67ce9..fb9ee4bf31 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -20,6 +20,8 @@ enum class PSBTError { EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_FAILED, UNSUPPORTED, + INCOMPLETE, + OK, }; } // namespace common diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index df1ced48a7..8ecb2a7429 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -207,7 +207,7 @@ public: int& num_blocks) = 0; //! Fill PSBT. - virtual std::optional fillPSBT(int sighash_type, + virtual std::optional fillPSBT(std::optional sighash_type, bool sign, bool bip32derivs, size_t* n_signed, diff --git a/src/key.cpp b/src/key.cpp index 360a1f46d3..dc01e12ee6 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -349,6 +350,127 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const return KeyPair(*this, merkle_root); } +std::vector CKey::CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& hash, const CPubKey& aggregate_pubkey, const std::vector& pubkeys) +{ + // Get the keyagg cache and aggregate pubkey + secp256k1_musig_keyagg_cache keyagg_cache; + if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) return {}; + std::optional agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); + if (!agg_key.has_value()) return {}; + if (aggregate_pubkey != *agg_key) return {}; + + // Parse participant pubkey + CPubKey our_pubkey = GetPubKey(); + secp256k1_pubkey pubkey; + if (!secp256k1_ec_pubkey_parse(secp256k1_context_sign, &pubkey, our_pubkey.data(), our_pubkey.size())) { + return {}; + } + + // Generate randomness for nonce + uint256 rand; + GetStrongRandBytes(rand); + + // Generate nonce + secp256k1_musig_pubnonce pubnonce; + if (!secp256k1_musig_nonce_gen(secp256k1_context_sign, secnonce.Get(), &pubnonce, rand.data(), UCharCast(begin()), &pubkey, hash.data(), &keyagg_cache, nullptr)) { + return {}; + } + + // Serialize nonce + std::vector out; + out.resize(66); + if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_sign, out.data(), &pubnonce)) { + return {}; + } + + return out; +} + +std::optional CKey::CreateMuSig2PartialSig(const uint256& hash, 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 (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) return std::nullopt; + std::optional agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); + if (!agg_key.has_value()) return std::nullopt; + if (aggregate_pubkey != *agg_key) return std::nullopt; + + // Parse the pubnonces + std::vector> signers_data; + std::vector pubnonce_ptrs; + size_t ours = 0; + 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() != 66) return std::nullopt; + if (part_pk == our_pubkey) { + ours = signers_data.size(); + } + + auto& [secp_pk, secp_pn] = signers_data.emplace_back(); + + if (!secp256k1_ec_pubkey_parse(secp256k1_context_sign, &secp_pk, part_pk.data(), part_pk.size())) { + return std::nullopt; + } + + if (!secp256k1_musig_pubnonce_parse(secp256k1_context_sign, &secp_pn, pubnonce.data())) { + 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_sign, &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_sign, &session, &aggnonce, hash.data(), &keyagg_cache)) { + return std::nullopt; + } + + // Create partial signature + secp256k1_musig_partial_sig psig; + if (!secp256k1_musig_partial_sign(secp256k1_context_sign, &psig, secnonce.Get(), &keypair, &keyagg_cache, &session)) { + return std::nullopt; + } + secnonce.Invalidate(); + + // Verify partial signature + if (!secp256k1_musig_partial_sig_verify(secp256k1_context_sign, &psig, &(signers_data.at(ours).second), &(signers_data.at(ours).first), &keyagg_cache, &session)) { + return std::nullopt; + } + + // Serialize + uint256 sig; + if (!secp256k1_musig_partial_sig_serialize(secp256k1_context_sign, 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 6ed6fd0fc3..dbb9c568e3 100644 --- a/src/key.h +++ b/src/key.h @@ -7,6 +7,7 @@ #ifndef BITCOIN_KEY_H #define BITCOIN_KEY_H +#include #include #include #include @@ -220,6 +221,9 @@ public: * Merkle root of the script tree). */ KeyPair ComputeKeyPair(const uint256* merkle_root) const; + + std::vector CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& hash, const CPubKey& aggregate_pubkey, const std::vector& pubkeys); + 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 new file mode 100644 index 0000000000..53683c596b --- /dev/null +++ b/src/musig.cpp @@ -0,0 +1,183 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include + +#include + +bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache) +{ + // Parse the pubkeys + std::vector secp_pubkeys; + std::vector pubkey_ptrs; + for (const CPubKey& pubkey : pubkeys) { + if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pubkeys.emplace_back(), pubkey.data(), pubkey.size())) { + return false; + } + } + pubkey_ptrs.reserve(secp_pubkeys.size()); + for (const secp256k1_pubkey& p : secp_pubkeys) { + pubkey_ptrs.push_back(&p); + } + + // Aggregate the pubkey + if (!secp256k1_musig_pubkey_agg(secp256k1_context_static, nullptr, &keyagg_cache, pubkey_ptrs.data(), pubkey_ptrs.size())) { + return false; + } + return true; +} + +std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache) +{ + // Get the plain aggregated pubkey + secp256k1_pubkey agg_pubkey; + if (!secp256k1_musig_pubkey_get(secp256k1_context_static, &agg_pubkey, &keyagg_cache)) { + return std::nullopt; + } + + // Turn into CPubKey + unsigned char ser_agg_pubkey[CPubKey::COMPRESSED_SIZE]; + size_t ser_agg_pubkey_len = CPubKey::COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, ser_agg_pubkey, &ser_agg_pubkey_len, &agg_pubkey, SECP256K1_EC_COMPRESSED); + return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len); +} + +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys) +{ + secp256k1_musig_keyagg_cache keyagg_cache; + if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) { + return std::nullopt; + } + return GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); +} + +class MuSig2SecNonceImpl +{ +private: + //! The actual secnonce itself + secure_unique_ptr m_nonce; + +public: + MuSig2SecNonceImpl() : m_nonce{make_secure_unique()} {} + + // Delete copy constructors + MuSig2SecNonceImpl(const MuSig2SecNonceImpl&) = delete; + MuSig2SecNonceImpl& operator=(const MuSig2SecNonceImpl&) = delete; + + secp256k1_musig_secnonce* Get() const { return m_nonce.get(); } + void Invalidate() { m_nonce.reset(); } + bool IsValid() { return m_nonce != nullptr; } +}; + +MuSig2SecNonce::MuSig2SecNonce() : m_impl{std::make_unique()} {} + +MuSig2SecNonce::MuSig2SecNonce(MuSig2SecNonce&&) noexcept = default; +MuSig2SecNonce& MuSig2SecNonce::operator=(MuSig2SecNonce&&) noexcept = default; + +MuSig2SecNonce::~MuSig2SecNonce() = default; + +secp256k1_musig_secnonce* MuSig2SecNonce::Get() const +{ + return m_impl->Get(); +} + +void MuSig2SecNonce::Invalidate() +{ + return m_impl->Invalidate(); +} + +bool MuSig2SecNonce::IsValid() +{ + return m_impl->IsValid(); +} + +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) +{ + if (!participants.size()) return std::nullopt; + + // Get the keyagg cache and aggregate pubkey + secp256k1_musig_keyagg_cache keyagg_cache; + if (!GetMuSig2KeyAggCache(participants, keyagg_cache)) return std::nullopt; + std::optional agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); + if (!agg_key.has_value()) return std::nullopt; + if (aggregate_pubkey != *agg_key) return std::nullopt; + + // Check if enough pubnonces and partial sigs + if (pubnonces.size() != participants.size()) return std::nullopt; + if (partial_sigs.size() != participants.size()) return std::nullopt; + + // Parse the pubnonces and partial sigs + std::vector> signers_data; + std::vector pubnonce_ptrs; + std::vector partial_sig_ptrs; + for (const CPubKey& part_pk : participants) { + 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() != 66) return std::nullopt; + const auto& it = partial_sigs.find(part_pk); + if (it == partial_sigs.end()) return std::nullopt; + const uint256& partial_sig = it->second; + + auto& [secp_pk, secp_pn, secp_ps] = 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 (!secp256k1_musig_partial_sig_parse(secp256k1_context_static, &secp_ps, partial_sig.data())) { + return std::nullopt; + } + } + pubnonce_ptrs.reserve(signers_data.size()); + partial_sig_ptrs.reserve(signers_data.size()); + for (auto& [_, pn, ps] : signers_data) { + pubnonce_ptrs.push_back(&pn); + partial_sig_ptrs.push_back(&ps); + } + + // 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; + } + + // Verify partial sigs + for (const auto& [pk, pb, ps] : signers_data) { + if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &ps, &pb, &pk, &keyagg_cache, &session)) { + return std::nullopt; + } + } + + // Aggregate partial sigs + std::vector sig; + sig.resize(64); + if (!secp256k1_musig_partial_sig_agg(secp256k1_context_static, sig.data(), &session, partial_sig_ptrs.data(), partial_sig_ptrs.size())) { + return std::nullopt; + } + + return sig; +} diff --git a/src/musig.h b/src/musig.h new file mode 100644 index 0000000000..d5dd492998 --- /dev/null +++ b/src/musig.h @@ -0,0 +1,54 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_MUSIG_H +#define BITCOIN_MUSIG_H + +#include + +#include + +struct secp256k1_musig_keyagg_cache; + +class MuSig2SecNonceImpl; +struct secp256k1_musig_secnonce; + +bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache); +std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& cache); +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys); + +/** + * MuSig2SecNonce encapsulates a secret nonce in use in a MuSig2 signing session. + * Since this nonce persists outside of libsecp256k1 signing code, we must handle + * its construction and destruction ourselves. + * The secret nonce must be kept a secret, otherwise the private key may be leaked. + * As such, it needs to be treated in the same way that CKeys are treated. + * So this class handles the secure allocation of the secp25k1_musig_secnonce object + * that libsecp256k1 uses, and only gives out references to this object to avoid + * any possibility of copies being made. Furthermore, objects of this class are not + * copyable to avoid nonce reuse. +*/ +class MuSig2SecNonce +{ +private: + std::unique_ptr m_impl; + +public: + MuSig2SecNonce(); + MuSig2SecNonce(MuSig2SecNonce&&) noexcept; + MuSig2SecNonce& operator=(MuSig2SecNonce&&) noexcept; + ~MuSig2SecNonce(); + + // Delete copy constructors + MuSig2SecNonce(const MuSig2SecNonce&) = delete; + MuSig2SecNonce& operator=(const MuSig2SecNonce&) = delete; + + secp256k1_musig_secnonce* Get() const; + void Invalidate(); + bool IsValid(); +}; + +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/node/psbt.cpp b/src/node/psbt.cpp index 51e252bffc..3933202e0c 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -64,7 +64,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, std::nullopt, &outdata) == PSBTError::OK; // Things are missing if (!complete) { @@ -124,7 +124,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, std::nullopt) != PSBTError::OK || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/psbt.cpp b/src/psbt.cpp index 19d855e4c7..ee38dfeef1 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -4,12 +4,15 @@ #include +#include #include #include #include