diff --git a/src/key.cpp b/src/key.cpp index 9504468fdf5..f17a286750e 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -349,6 +350,128 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const return KeyPair(*this, merkle_root); } +std::vector CKey::CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector& pubkeys) +{ + // Get the keyagg cache and aggregate pubkey + secp256k1_musig_keyagg_cache keyagg_cache; + if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return {}; + + // Parse participant pubkey + CPubKey our_pubkey = GetPubKey(); + secp256k1_pubkey pubkey; + if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &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, sighash.data(), &keyagg_cache, nullptr)) { + return {}; + } + + // Serialize pubnonce + std::vector out; + out.resize(MUSIG2_PUBNONCE_SIZE); + if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_static, out.data(), &pubnonce)) { + return {}; + } + + return out; +} + +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 22f96880b7b..f35f3cc0156 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& sighash, 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 index b3329543127..686ec5e869a 100644 --- a/src/musig.cpp +++ b/src/musig.cpp @@ -3,10 +3,11 @@ // 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) +static bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache) { // Parse the pubkeys std::vector secp_pubkeys; @@ -28,7 +29,7 @@ bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_k return true; } -std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache) +static std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache) { // Get the plain aggregated pubkey secp256k1_pubkey agg_pubkey; @@ -43,11 +44,163 @@ std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_ca return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len); } -std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys) +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional& expected_aggregate) { - secp256k1_musig_keyagg_cache keyagg_cache; if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) { return std::nullopt; } - return GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); + std::optional agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); + if (!agg_key.has_value()) return std::nullopt; + if (expected_aggregate.has_value() && expected_aggregate != agg_key) return std::nullopt; + return agg_key; +} + +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys) +{ + secp256k1_musig_keyagg_cache keyagg_cache; + return MuSig2AggregatePubkeys(pubkeys, keyagg_cache, std::nullopt); +} + +CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey) +{ + CExtPubKey extpub; + extpub.nDepth = 0; + std::memset(extpub.vchFingerprint, 0, 4); + extpub.nChild = 0; + extpub.chaincode = MUSIG_CHAINCODE; + extpub.pubkey = pubkey; + return extpub; +} + +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(); +} + +uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash) +{ + HashWriter hasher; + hasher << script_pubkey << part_pubkey << sighash; + return hasher.GetSHA256(); +} + +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) +{ + if (!part_pubkeys.size()) return std::nullopt; + + // Get the keyagg cache and aggregate pubkey + secp256k1_musig_keyagg_cache keyagg_cache; + if (!MuSig2AggregatePubkeys(part_pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt; + + // Check if enough pubnonces and partial sigs + if (pubnonces.size() != part_pubkeys.size()) return std::nullopt; + if (partial_sigs.size() != part_pubkeys.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 : part_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; + 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 index 69c82ddda37..10234568087 100644 --- a/src/musig.h +++ b/src/musig.h @@ -11,6 +11,8 @@ #include struct secp256k1_musig_keyagg_cache; +class MuSig2SecNonceImpl; +struct secp256k1_musig_secnonce; //! MuSig2 chaincode as defined by BIP 328 using namespace util::hex_literals; @@ -21,11 +23,50 @@ constexpr uint256 MUSIG_CHAINCODE{ -//! Create a secp256k1_musig_keyagg_cache from the pubkeys in their current order. This is necessary for most MuSig2 operations -bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache); -//! Retrieve the full aggregate pubkey from the secp256k1_musig_keyagg_cache -std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& cache); -//! Compute the full aggregate pubkey from the given participant pubkeys in their current order +constexpr size_t MUSIG2_PUBNONCE_SIZE{66}; + +//! Compute the full aggregate pubkey from the given participant pubkeys in their current order. +//! Outputs the secp256k1_musig_keyagg_cache and validates that the computed aggregate pubkey matches an expected aggregate pubkey. +//! This is necessary for most MuSig2 operations. +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional& expected_aggregate); std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys); +//! Construct the BIP 328 synthetic xpub for a pubkey +CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey); + +/** + * 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 secp256k1_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(); +}; + +uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash); + +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/psbt.cpp b/src/psbt.cpp index 28ccdcb1eaa..92ddcba6a59 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -149,6 +149,13 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const for (const auto& [hash, preimage] : hash256_preimages) { sigdata.hash256_preimages.emplace(std::vector(hash.begin(), hash.end()), preimage); } + sigdata.musig2_pubkeys.insert(m_musig2_participants.begin(), m_musig2_participants.end()); + for (const auto& [agg_key_lh, pubnonces] : m_musig2_pubnonces) { + sigdata.musig2_pubnonces[agg_key_lh].insert(pubnonces.begin(), pubnonces.end()); + } + for (const auto& [agg_key_lh, psigs] : m_musig2_partial_sigs) { + sigdata.musig2_partial_sigs[agg_key_lh].insert(psigs.begin(), psigs.end()); + } } void PSBTInput::FromSignatureData(const SignatureData& sigdata) @@ -196,6 +203,13 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata) for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { m_tap_bip32_paths.emplace(pubkey, leaf_origin); } + m_musig2_participants.insert(sigdata.musig2_pubkeys.begin(), sigdata.musig2_pubkeys.end()); + for (const auto& [agg_key_lh, pubnonces] : sigdata.musig2_pubnonces) { + m_musig2_pubnonces[agg_key_lh].insert(pubnonces.begin(), pubnonces.end()); + } + for (const auto& [agg_key_lh, psigs] : sigdata.musig2_partial_sigs) { + m_musig2_partial_sigs[agg_key_lh].insert(psigs.begin(), psigs.end()); + } } void PSBTInput::Merge(const PSBTInput& input) @@ -223,6 +237,13 @@ void PSBTInput::Merge(const PSBTInput& input) if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig; if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key; if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root; + m_musig2_participants.insert(input.m_musig2_participants.begin(), input.m_musig2_participants.end()); + for (const auto& [agg_key_lh, pubnonces] : input.m_musig2_pubnonces) { + m_musig2_pubnonces[agg_key_lh].insert(pubnonces.begin(), pubnonces.end()); + } + for (const auto& [agg_key_lh, psigs] : input.m_musig2_partial_sigs) { + m_musig2_partial_sigs[agg_key_lh].insert(psigs.begin(), psigs.end()); + } } void PSBTOutput::FillSignatureData(SignatureData& sigdata) const @@ -252,6 +273,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey); } + sigdata.musig2_pubkeys.insert(m_musig2_participants.begin(), m_musig2_participants.end()); } void PSBTOutput::FromSignatureData(const SignatureData& sigdata) @@ -274,6 +296,7 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata) for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { m_tap_bip32_paths.emplace(pubkey, leaf_origin); } + m_musig2_participants.insert(sigdata.musig2_pubkeys.begin(), sigdata.musig2_pubkeys.end()); } bool PSBTOutput::IsNull() const @@ -291,6 +314,7 @@ void PSBTOutput::Merge(const PSBTOutput& output) if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key; if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree; + m_musig2_participants.insert(output.m_musig2_participants.begin(), output.m_musig2_participants.end()); } bool PSBTInputSigned(const PSBTInput& input) diff --git a/src/psbt.h b/src/psbt.h index f8098b04503..f0de079f7b4 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -794,7 +794,7 @@ struct PSBTInput std::vector pubnonce; s >> pubnonce; - if (pubnonce.size() != 66) { + if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) { throw std::ios_base::failure("Input musig2 pubnonce value is not 66 bytes"); } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 6041c89e7f1..264f861bc7a 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -338,13 +338,16 @@ bool CPubKey::Decompress() { return true; } -bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const { +bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc, uint256* bip32_tweak_out) const { assert(IsValid()); assert((nChild >> 31) == 0); assert(size() == COMPRESSED_SIZE); unsigned char out[64]; BIP32Hash(cc, nChild, *begin(), begin()+1, out); memcpy(ccChild.begin(), out+32, 32); + if (bip32_tweak_out) { + memcpy(bip32_tweak_out->begin(), out, 32); + } secp256k1_pubkey pubkey; if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, vch, size())) { return false; @@ -409,13 +412,13 @@ void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VE Decode(&code[4]); } -bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const { +bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild, uint256* bip32_tweak_out) const { if (nDepth == std::numeric_limits::max()) return false; out.nDepth = nDepth + 1; CKeyID id = pubkey.GetID(); memcpy(out.vchFingerprint, &id, 4); out.nChild = _nChild; - return pubkey.Derive(out.pubkey, out.chaincode, _nChild, chaincode); + return pubkey.Derive(out.pubkey, out.chaincode, _nChild, chaincode, bip32_tweak_out); } /* static */ bool CPubKey::CheckLowS(const std::vector& vchSig) { diff --git a/src/pubkey.h b/src/pubkey.h index 442dc2d6431..5ae7f75dde9 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -224,7 +224,7 @@ public: bool Decompress(); //! Derive BIP32 child pubkey. - [[nodiscard]] bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; + [[nodiscard]] bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc, uint256* bip32_tweak_out = nullptr) const; }; class XOnlyPubKey @@ -379,7 +379,7 @@ struct CExtPubKey { void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const; void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]); - [[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild) const; + [[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild, uint256* bip32_tweak_out = nullptr) const; }; #endif // BITCOIN_PUBKEY_H diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index bd819d365ae..d0436702124 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -641,13 +641,7 @@ public: // Make our pubkey provider if (IsRangedDerivation() || !m_path.empty()) { // Make the synthetic xpub and construct the BIP32PubkeyProvider - CExtPubKey extpub; - extpub.nDepth = 0; - std::memset(extpub.vchFingerprint, 0, 4); - extpub.nChild = 0; - extpub.chaincode = MUSIG_CHAINCODE; - extpub.pubkey = m_aggregate_pubkey.value(); - + CExtPubKey extpub = CreateMuSig2SyntheticXpub(m_aggregate_pubkey.value()); m_aggregate_provider = std::make_unique(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false); } else { m_aggregate_provider = std::make_unique(m_expr_index, m_aggregate_pubkey.value(), /*xonly=*/false); @@ -1647,6 +1641,7 @@ std::optional ParseKeyPathNum(std::span elem, bool& apostr * @param[out] apostrophe only updated if hardened derivation is found * @param[out] error parsing error message * @param[in] allow_multipath Allows the parsed path to use the multipath specifier + * @param[out] has_hardened Records whether the path contains any hardened derivation * @returns false if parsing failed **/ [[nodiscard]] bool ParseKeyPath(const std::vector>& split, std::vector& out, bool& apostrophe, std::string& error, bool allow_multipath, bool& has_hardened) @@ -1843,20 +1838,20 @@ std::vector> ParsePubkey(uint32_t& key_exp_index bool any_ranged = false; bool all_bip32 = true; std::vector>> providers; - bool any_key_parsed = true; + bool any_key_parsed = false; size_t max_multipath_len = 0; while (expr.size()) { - if (!any_key_parsed && !Const(",", expr)) { + if (any_key_parsed && !Const(",", expr)) { error = strprintf("musig(): expected ',', got '%c'", expr[0]); return {}; } - any_key_parsed = false; auto arg = Expr(expr); auto pk = ParsePubkey(key_exp_index, arg, ParseScriptContext::MUSIG, out, error); if (pk.empty()) { error = strprintf("musig(): %s", error); return {}; } + any_key_parsed = true; any_ranged = any_ranged || pk.at(0)->IsRange(); all_bip32 = all_bip32 && pk.at(0)->IsBIP32(); @@ -1866,7 +1861,7 @@ std::vector> ParsePubkey(uint32_t& key_exp_index providers.emplace_back(std::move(pk)); key_exp_index++; } - if (any_key_parsed) { + if (!any_key_parsed) { error = "musig(): Must contain key expressions"; return {}; } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 33cbc38be41..8103e318c51 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include #include