Merge bitcoin/bitcoin#29675: wallet: Be able to receive and spend inputs involving MuSig2 aggregate keys

ac599c4a9cb3b2d424932d3fd91f9eed17426827 test: Test MuSig2 in the wallet (Ava Chow)
68ef954c4c59802a6810a462eaa8dd61728ba820 wallet: Keep secnonces in DescriptorScriptPubKeyMan (Ava Chow)
4a273edda0ec10f0c5ae5d94b9925fa334d1c6e6 sign: Create MuSig2 signatures for known MuSig2 aggregate keys (Ava Chow)
258db938899409c8ee1cef04e16ba1795ea0038d sign: Add CreateMuSig2AggregateSig (Ava Chow)
bf69442b3f5004dc3df5a1b1d752114ba68fa5f4 sign: Add CreateMuSig2PartialSig (Ava Chow)
512b17fc56eac3a2e2b9ba489b5423d098cce0db sign: Add CreateMuSig2Nonce (Ava Chow)
82ea67c607cde6187d7082429d27b927dc21c0c6 musig: Add MuSig2AggregatePubkeys variant that validates the aggregate (Ava Chow)
d99a081679e16668458512aba2fd13a3e1bdb09f psbt: MuSig2 data in Fill/FromSignatureData (Ava Chow)
4d8b4f53363f013ed3972997f0b05b9c19e9db9d signingprovider: Add musig2 secnonces (Ava Chow)
c06a1dc86ff2347538e95041ab7b97af25342958 Add MuSig2SecNonce class for secure allocation of musig nonces (Ava Chow)
9baff05e494443cd82708490f384aa3034ad43bd sign: Include taproot output key's KeyOriginInfo in sigdata (Ava Chow)
4b24bfeab9d6732aae3e69efd33105792ef1198f pubkey: Return tweaks from BIP32 derivation (Ava Chow)
f14876213aad0e67088b75cae24323db9f2576d8 musig: Move synthetic xpub construction to its own function (Ava Chow)
fb8720f1e09f4e41802f07be53fb220d6f6c127f sign: Refactor Schnorr sighash computation out of CreateSchnorrSig (Ava Chow)
a4cfddda644f1fc9a815b2d16c997716cd63554a tests: Clarify why musig derivation adds a pubkey and xpub (Ava Chow)
39a63bf2e7e38dd3f30b5d1a8f6b2fff0e380d12 descriptors: Add a doxygen comment for has_hardened output_parameter (Ava Chow)
2320184d0ea87279558a8e6cbb3bccf5ba1bb781 descriptors: Fix meaning of any_key_parsed (Ava Chow)

Pull request description:

  This PR implements MuSig2 signing so that the wallet can receive and spend from imported `musig(0` descriptors.

  The libsecp musig module is enabled so that it can be used for all of the MuSig2 cryptography.

  Secnonces are handled in a separate class which holds the libsecp secnonce object in a `secure_unique_ptr`. Since secnonces must not be used, this class has no serialization and will only live in memory. A restart of the software will require a restart of the MuSig2 signing process.

ACKs for top commit:
  fjahr:
    tACK ac599c4a9cb3b2d424932d3fd91f9eed17426827
  rkrux:
    lgtm tACK ac599c4a9cb3b2d424932d3fd91f9eed17426827
  theStack:
    Code-review ACK ac599c4a9cb3b2d424932d3fd91f9eed17426827 🗝️

Tree-SHA512: 626b9adc42ed2403e2f4405321eb9ce009a829c07d968e95ab288fe4940b195b0af35ca279a4a7fa51af76e55382bad6f63a23bca14a84140559b3c667e7041e
This commit is contained in:
merge-script 2025-10-14 16:25:52 -04:00
commit 48aa0e98d0
No known key found for this signature in database
GPG Key ID: BA03F4DBE0C63FB4
18 changed files with 977 additions and 48 deletions

View File

@ -13,6 +13,7 @@
#include <secp256k1.h>
#include <secp256k1_ellswift.h>
#include <secp256k1_extrakeys.h>
#include <secp256k1_musig.h>
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>
@ -349,6 +350,128 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const
return KeyPair(*this, merkle_root);
}
std::vector<uint8_t> CKey::CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& 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<uint8_t> out;
out.resize(MUSIG2_PUBNONCE_SIZE);
if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_static, out.data(), &pubnonce)) {
return {};
}
return out;
}
std::optional<uint256> CKey::CreateMuSig2PartialSig(const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, MuSig2SecNonce& secnonce, const std::vector<std::pair<uint256, bool>>& 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<std::pair<secp256k1_pubkey, secp256k1_musig_pubnonce>> signers_data;
std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs;
std::optional<size_t> 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<uint8_t> 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;

View File

@ -7,6 +7,7 @@
#ifndef BITCOIN_KEY_H
#define BITCOIN_KEY_H
#include <musig.h>
#include <pubkey.h>
#include <serialize.h>
#include <support/allocators/secure.h>
@ -220,6 +221,9 @@ public:
* Merkle root of the script tree).
*/
KeyPair ComputeKeyPair(const uint256* merkle_root) const;
std::vector<uint8_t> CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys);
std::optional<uint256> CreateMuSig2PartialSig(const uint256& hash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, MuSig2SecNonce& secnonce, const std::vector<std::pair<uint256, bool>>& tweaks);
};
CKey GenerateRandomKey(bool compressed = true) noexcept;

View File

@ -3,10 +3,11 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <musig.h>
#include <support/allocators/secure.h>
#include <secp256k1_musig.h>
bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
{
// Parse the pubkeys
std::vector<secp256k1_pubkey> secp_pubkeys;
@ -28,7 +29,7 @@ bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_k
return true;
}
std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
static std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
{
// Get the plain aggregated pubkey
secp256k1_pubkey agg_pubkey;
@ -43,11 +44,163 @@ std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_ca
return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len);
}
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys)
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate)
{
secp256k1_musig_keyagg_cache keyagg_cache;
if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) {
return std::nullopt;
}
return GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache);
std::optional<CPubKey> 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<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& 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<secp256k1_musig_secnonce> m_nonce;
public:
MuSig2SecNonceImpl() : m_nonce{make_secure_unique<secp256k1_musig_secnonce>()} {}
// 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<MuSig2SecNonceImpl>()} {}
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<std::vector<uint8_t>> CreateMuSig2AggregateSig(const std::vector<CPubKey>& part_pubkeys, const CPubKey& aggregate_pubkey, const std::vector<std::pair<uint256, bool>>& tweaks, const uint256& sighash, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, const std::map<CPubKey, uint256>& 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<std::tuple<secp256k1_pubkey, secp256k1_musig_pubnonce, secp256k1_musig_partial_sig>> signers_data;
std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs;
std::vector<const secp256k1_musig_partial_sig*> 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<uint8_t> 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<uint8_t> 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;
}

View File

@ -11,6 +11,8 @@
#include <vector>
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<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache);
//! Retrieve the full aggregate pubkey from the secp256k1_musig_keyagg_cache
std::optional<CPubKey> 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<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate);
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& 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<MuSig2SecNonceImpl> 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<std::vector<uint8_t>> CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, const CPubKey& aggregate_pubkey, const std::vector<std::pair<uint256, bool>>& tweaks, const uint256& sighash, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, const std::map<CPubKey, uint256>& partial_sigs);
#endif // BITCOIN_MUSIG_H

View File

@ -149,6 +149,13 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
for (const auto& [hash, preimage] : hash256_preimages) {
sigdata.hash256_preimages.emplace(std::vector<unsigned char>(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)

View File

@ -794,7 +794,7 @@ struct PSBTInput
std::vector<uint8_t> 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");
}

View File

@ -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<unsigned char>::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<unsigned char>& vchSig) {

View File

@ -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

View File

@ -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<BIP32PubkeyProvider>(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false);
} else {
m_aggregate_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, m_aggregate_pubkey.value(), /*xonly=*/false);
@ -1647,6 +1641,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> 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<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath, bool& has_hardened)
@ -1843,20 +1838,20 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t& key_exp_index
bool any_ranged = false;
bool all_bip32 = true;
std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> 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<std::unique_ptr<PubkeyProvider>> 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 {};
}

View File

@ -7,8 +7,10 @@
#include <consensus/amount.h>
#include <key.h>
#include <musig.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <random.h>
#include <script/keyorigin.h>
#include <script/miniscript.h>
#include <script/script.h>
@ -59,17 +61,14 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
return true;
}
bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const
std::optional<uint256> MutableTransactionSignatureCreator::ComputeSchnorrSignatureHash(const uint256* leaf_hash, SigVersion sigversion) const
{
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
CKey key;
if (!provider.GetKeyByXOnly(pubkey, key)) return false;
// BIP341/BIP342 signing needs lots of precomputed transaction data. While some
// (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset
// of data present, for now, only support signing when everything is provided.
if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return false;
if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return std::nullopt;
ScriptExecutionData execdata;
execdata.m_annex_init = true;
@ -77,19 +76,134 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider&
if (sigversion == SigVersion::TAPSCRIPT) {
execdata.m_codeseparator_pos_init = true;
execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now.
if (!leaf_hash) return false; // BIP342 signing needs leaf hash.
if (!leaf_hash) return std::nullopt; // BIP342 signing needs leaf hash.
execdata.m_tapleaf_hash_init = true;
execdata.m_tapleaf_hash = *leaf_hash;
}
uint256 hash;
if (!SignatureHashSchnorr(hash, execdata, m_txto, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false;
if (!SignatureHashSchnorr(hash, execdata, m_txto, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return std::nullopt;
return hash;
}
bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const
{
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
CKey key;
if (!provider.GetKeyByXOnly(pubkey, key)) return false;
std::optional<uint256> hash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
if (!hash.has_value()) return false;
sig.resize(64);
// Use uint256{} as aux_rnd for now.
if (!key.SignSchnorr(hash, sig, merkle_root, {})) return false;
if (!key.SignSchnorr(*hash, sig, merkle_root, {})) return false;
if (nHashType) sig.push_back(nHashType);
return true;
}
std::vector<uint8_t> MutableTransactionSignatureCreator::CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const
{
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
// Retrieve the private key
CKey key;
if (!provider.GetKey(part_pubkey.GetID(), key)) return {};
// Retrieve participant pubkeys
auto it = sigdata.musig2_pubkeys.find(aggregate_pubkey);
if (it == sigdata.musig2_pubkeys.end()) return {};
const std::vector<CPubKey>& pubkeys = it->second;
if (std::find(pubkeys.begin(), pubkeys.end(), part_pubkey) == pubkeys.end()) return {};
// Compute sighash
std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
if (!sighash.has_value()) return {};
MuSig2SecNonce secnonce;
std::vector<uint8_t> out = key.CreateMuSig2Nonce(secnonce, *sighash, aggregate_pubkey, pubkeys);
if (out.empty()) return {};
// Store the secnonce in the SigningProvider
provider.SetMuSig2SecNonce(MuSig2SessionID(script_pubkey, part_pubkey, *sighash), std::move(secnonce));
return out;
}
bool MutableTransactionSignatureCreator::CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const
{
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
// Retrieve private key
CKey key;
if (!provider.GetKey(part_pubkey.GetID(), key)) return false;
// Retrieve participant pubkeys
auto it = sigdata.musig2_pubkeys.find(aggregate_pubkey);
if (it == sigdata.musig2_pubkeys.end()) return false;
const std::vector<CPubKey>& pubkeys = it->second;
if (std::find(pubkeys.begin(), pubkeys.end(), part_pubkey) == pubkeys.end()) return {};
// Retrieve pubnonces
auto this_leaf_aggkey = std::make_pair(script_pubkey, leaf_hash ? *leaf_hash : uint256());
auto pubnonce_it = sigdata.musig2_pubnonces.find(this_leaf_aggkey);
if (pubnonce_it == sigdata.musig2_pubnonces.end()) return false;
const std::map<CPubKey, std::vector<uint8_t>>& pubnonces = pubnonce_it->second;
// Check if enough pubnonces
if (pubnonces.size() != pubkeys.size()) return false;
// Compute sighash
std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
if (!sighash.has_value()) return false;
// Retrieve the secnonce
uint256 session_id = MuSig2SessionID(script_pubkey, part_pubkey, *sighash);
std::optional<std::reference_wrapper<MuSig2SecNonce>> secnonce = provider.GetMuSig2SecNonce(session_id);
if (!secnonce || !secnonce->get().IsValid()) return false;
// Compute the sig
std::optional<uint256> sig = key.CreateMuSig2PartialSig(*sighash, aggregate_pubkey, pubkeys, pubnonces, *secnonce, tweaks);
if (!sig) return false;
partial_sig = std::move(*sig);
// Delete the secnonce now that we're done with it
assert(!secnonce->get().IsValid());
provider.DeleteMuSig2Session(session_id);
return true;
}
bool MutableTransactionSignatureCreator::CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const
{
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
if (!participants.size()) return false;
// Retrieve pubnonces and partial sigs
auto this_leaf_aggkey = std::make_pair(script_pubkey, leaf_hash ? *leaf_hash : uint256());
auto pubnonce_it = sigdata.musig2_pubnonces.find(this_leaf_aggkey);
if (pubnonce_it == sigdata.musig2_pubnonces.end()) return false;
const std::map<CPubKey, std::vector<uint8_t>>& pubnonces = pubnonce_it->second;
auto partial_sigs_it = sigdata.musig2_partial_sigs.find(this_leaf_aggkey);
if (partial_sigs_it == sigdata.musig2_partial_sigs.end()) return false;
const std::map<CPubKey, uint256>& partial_sigs = partial_sigs_it->second;
// Check if enough pubnonces and partial sigs
if (pubnonces.size() != participants.size()) return false;
if (partial_sigs.size() != participants.size()) return false;
// Compute sighash
std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
if (!sighash.has_value()) return false;
std::optional<std::vector<uint8_t>> res = ::CreateMuSig2AggregateSig(participants, aggregate_pubkey, tweaks, *sighash, pubnonces, partial_sigs);
if (!res) return false;
sig = res.value();
if (nHashType) sig.push_back(nHashType);
return true;
}
static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script)
{
if (provider.GetCScript(scriptid, script)) {
@ -151,6 +265,100 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat
return false;
}
static bool SignMuSig2(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& script_pubkey, const uint256* merkle_root, const uint256* leaf_hash, SigVersion sigversion)
{
Assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
// Lookup derivation paths for the script pubkey
KeyOriginInfo agg_info;
auto misc_pk_it = sigdata.taproot_misc_pubkeys.find(script_pubkey);
if (misc_pk_it != sigdata.taproot_misc_pubkeys.end()) {
agg_info = misc_pk_it->second.second;
}
for (const auto& [agg_pub, part_pks] : sigdata.musig2_pubkeys) {
if (part_pks.empty()) continue;
// Fill participant derivation path info
for (const auto& part_pk : part_pks) {
KeyOriginInfo part_info;
if (provider.GetKeyOrigin(part_pk.GetID(), part_info)) {
XOnlyPubKey xonly_part(part_pk);
auto it = sigdata.taproot_misc_pubkeys.find(xonly_part);
if (it == sigdata.taproot_misc_pubkeys.end()) {
it = sigdata.taproot_misc_pubkeys.emplace(xonly_part, std::make_pair(std::set<uint256>(), part_info)).first;
}
if (leaf_hash) it->second.first.insert(*leaf_hash);
}
}
// The pubkey in the script may not be the actual aggregate of the participants, but derived from it.
// Check the derivation, and compute the BIP 32 derivation tweaks
std::vector<std::pair<uint256, bool>> tweaks;
CPubKey plain_pub = agg_pub;
if (XOnlyPubKey(agg_pub) != script_pubkey) {
if (agg_info.path.empty()) continue;
// Compute and compare fingerprint
CKeyID keyid = agg_pub.GetID();
if (!std::equal(agg_info.fingerprint, agg_info.fingerprint + sizeof(agg_info.fingerprint), keyid.data())) {
continue;
}
// Get the BIP32 derivation tweaks
CExtPubKey extpub = CreateMuSig2SyntheticXpub(agg_pub);
for (const int i : agg_info.path) {
auto& [t, xonly] = tweaks.emplace_back();
xonly = false;
if (!extpub.Derive(extpub, i, &t)) {
return false;
}
}
Assert(XOnlyPubKey(extpub.pubkey) == script_pubkey);
plain_pub = extpub.pubkey;
}
// Add the merkle root tweak
if (sigversion == SigVersion::TAPROOT && merkle_root) {
tweaks.emplace_back(script_pubkey.ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root), true);
std::optional<std::pair<XOnlyPubKey, bool>> tweaked = script_pubkey.CreateTapTweak(merkle_root->IsNull() ? nullptr : merkle_root);
if (!Assume(tweaked)) return false;
plain_pub = tweaked->first.GetCPubKeys().at(tweaked->second ? 1 : 0);
}
// First try to aggregate
if (creator.CreateMuSig2AggregateSig(part_pks, sig_out, agg_pub, plain_pub, leaf_hash, tweaks, sigversion, sigdata)) {
if (sigversion == SigVersion::TAPROOT) {
sigdata.taproot_key_path_sig = sig_out;
} else {
auto lookup_key = std::make_pair(script_pubkey, leaf_hash ? *leaf_hash : uint256());
sigdata.taproot_script_sigs[lookup_key] = sig_out;
}
continue;
}
// Cannot aggregate, try making partial sigs for every participant
auto pub_key_leaf_hash = std::make_pair(plain_pub, leaf_hash ? *leaf_hash : uint256());
for (const CPubKey& part_pk : part_pks) {
uint256 partial_sig;
if (creator.CreateMuSig2PartialSig(provider, partial_sig, agg_pub, plain_pub, part_pk, leaf_hash, tweaks, sigversion, sigdata) && Assume(!partial_sig.IsNull())) {
sigdata.musig2_partial_sigs[pub_key_leaf_hash].emplace(part_pk, partial_sig);
}
}
// If there are any partial signatures, exit early
auto partial_sigs_it = sigdata.musig2_partial_sigs.find(pub_key_leaf_hash);
if (partial_sigs_it != sigdata.musig2_partial_sigs.end() && !partial_sigs_it->second.empty()) {
continue;
}
// No partial sigs, try to make pubnonces
std::map<CPubKey, std::vector<uint8_t>>& pubnonces = sigdata.musig2_pubnonces[pub_key_leaf_hash];
for (const CPubKey& part_pk : part_pks) {
if (pubnonces.contains(part_pk)) continue;
std::vector<uint8_t> pubnonce = creator.CreateMuSig2Nonce(provider, agg_pub, plain_pub, part_pk, leaf_hash, merkle_root, sigversion, sigdata);
if (pubnonce.empty()) continue;
pubnonces[part_pk] = std::move(pubnonce);
}
}
return true;
}
static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion)
{
KeyOriginInfo info;
@ -169,11 +377,14 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur
sig_out = it->second;
return true;
}
if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) {
sigdata.taproot_script_sigs[lookup_key] = sig_out;
return true;
} else if (!SignMuSig2(creator, sigdata, provider, sig_out, pubkey, /*merkle_root=*/nullptr, &leaf_hash, sigversion)) {
return false;
}
return false;
return sigdata.taproot_script_sigs.contains(lookup_key);
}
template<typename M, typename K, typename V>
@ -342,27 +553,45 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea
if (provider.GetTaprootBuilder(output, builder)) {
sigdata.tr_builder = builder;
}
if (auto agg_keys = provider.GetAllMuSig2ParticipantPubkeys(); !agg_keys.empty()) {
sigdata.musig2_pubkeys.insert(agg_keys.begin(), agg_keys.end());
}
// Try key path spending.
{
KeyOriginInfo info;
if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, info)) {
KeyOriginInfo internal_key_info;
if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, internal_key_info)) {
auto it = sigdata.taproot_misc_pubkeys.find(sigdata.tr_spenddata.internal_key);
if (it == sigdata.taproot_misc_pubkeys.end()) {
sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set<uint256>(), info));
sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set<uint256>(), internal_key_info));
}
}
std::vector<unsigned char> sig;
if (sigdata.taproot_key_path_sig.size() == 0) {
if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.merkle_root, SigVersion::TAPROOT)) {
sigdata.taproot_key_path_sig = sig;
KeyOriginInfo output_key_info;
if (provider.GetKeyOriginByXOnly(output, output_key_info)) {
auto it = sigdata.taproot_misc_pubkeys.find(output);
if (it == sigdata.taproot_misc_pubkeys.end()) {
sigdata.taproot_misc_pubkeys.emplace(output, std::make_pair(std::set<uint256>(), output_key_info));
}
}
if (sigdata.taproot_key_path_sig.size() == 0) {
if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) {
auto make_keypath_sig = [&](const XOnlyPubKey& pk, const uint256* merkle_root) {
std::vector<unsigned char> sig;
if (creator.CreateSchnorrSig(provider, sig, pk, nullptr, merkle_root, SigVersion::TAPROOT)) {
sigdata.taproot_key_path_sig = sig;
} else {
SignMuSig2(creator, sigdata, provider, sig, pk, merkle_root, /*leaf_hash=*/nullptr, SigVersion::TAPROOT);
}
};
// First try signing with internal key
if (sigdata.taproot_key_path_sig.size() == 0) {
make_keypath_sig(sigdata.tr_spenddata.internal_key, &sigdata.tr_spenddata.merkle_root);
}
// Try signing with output key if still no signature
if (sigdata.taproot_key_path_sig.size() == 0) {
make_keypath_sig(output, nullptr);
}
if (sigdata.taproot_key_path_sig.size()) {
result = Vector(sigdata.taproot_key_path_sig);
@ -737,6 +966,22 @@ public:
sig.assign(64, '\000');
return true;
}
std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const override
{
std::vector<uint8_t> out;
out.assign(MUSIG2_PUBNONCE_SIZE, '\000');
return out;
}
bool CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override
{
partial_sig = uint256::ONE;
return true;
}
bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override
{
sig.assign(64, '\000');
return true;
}
};
}

View File

@ -23,6 +23,7 @@ class SigningProvider;
struct bilingual_str;
struct CMutableTransaction;
struct SignatureData;
/** Interface for signature creators. */
class BaseSignatureCreator {
@ -33,6 +34,9 @@ public:
/** Create a singular (non-script) signature. */
virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0;
virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0;
virtual std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const =0;
virtual bool CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const =0;
virtual bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const =0;
};
/** A signature creator for transactions. */
@ -45,12 +49,17 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator
const MutableTransactionSignatureChecker checker;
const PrecomputedTransactionData* m_txdata;
std::optional<uint256> ComputeSchnorrSignatureHash(const uint256* leaf_hash, SigVersion sigversion) const;
public:
MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, int hash_type);
MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type);
const BaseSignatureChecker& Checker() const override { return checker; }
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override;
std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const override;
bool CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override;
bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override;
};
/** A signature checker that accepts all signatures */
@ -78,7 +87,7 @@ struct SignatureData {
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys;
std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash.
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes).
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal and output keys (may have no leaf script hashes).
std::map<CKeyID, XOnlyPubKey> tap_pubkeys; ///< Misc Taproot pubkeys involved in this input, by hash. (Equivalent of misc_pubkeys but for Taproot.)
std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found
@ -88,6 +97,12 @@ struct SignatureData {
std::map<std::vector<uint8_t>, std::vector<uint8_t>> hash256_preimages; ///< Mapping from a HASH256 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> ripemd160_preimages; ///< Mapping from a RIPEMD160 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> hash160_preimages; ///< Mapping from a HASH160 hash to its preimage provided to solve a Script
//! Map MuSig2 aggregate pubkeys to its participants
std::map<CPubKey, std::vector<CPubKey>> musig2_pubkeys;
//! Mapping from pair of MuSig2 aggregate pubkey, and tapleaf hash to map of MuSig2 participant pubkeys to MuSig2 public nonce
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, std::vector<uint8_t>>> musig2_pubnonces;
//! Mapping from pair of MuSig2 aggregate pubkey, and tapleaf hash to map of MuSig2 participant pubkeys to MuSig2 partial signature
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, uint256>> musig2_partial_sigs;
SignatureData() = default;
explicit SignatureData(const CScript& script) : scriptSig(script) {}

View File

@ -58,6 +58,26 @@ std::vector<CPubKey> HidingSigningProvider::GetMuSig2ParticipantPubkeys(const CP
return m_provider->GetMuSig2ParticipantPubkeys(pubkey);
}
std::map<CPubKey, std::vector<CPubKey>> HidingSigningProvider::GetAllMuSig2ParticipantPubkeys() const
{
return m_provider->GetAllMuSig2ParticipantPubkeys();
}
void HidingSigningProvider::SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const
{
m_provider->SetMuSig2SecNonce(id, std::move(nonce));
}
std::optional<std::reference_wrapper<MuSig2SecNonce>> HidingSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
{
return m_provider->GetMuSig2SecNonce(session_id);
}
void HidingSigningProvider::DeleteMuSig2Session(const uint256& session_id) const
{
m_provider->DeleteMuSig2Session(session_id);
}
bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const
@ -94,6 +114,31 @@ std::vector<CPubKey> FlatSigningProvider::GetMuSig2ParticipantPubkeys(const CPub
return participant_pubkeys;
}
std::map<CPubKey, std::vector<CPubKey>> FlatSigningProvider::GetAllMuSig2ParticipantPubkeys() const
{
return aggregate_pubkeys;
}
void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const
{
if (!Assume(musig2_secnonces)) return;
musig2_secnonces->emplace(session_id, std::move(nonce));
}
std::optional<std::reference_wrapper<MuSig2SecNonce>> FlatSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
{
if (!Assume(musig2_secnonces)) return std::nullopt;
const auto& it = musig2_secnonces->find(session_id);
if (it == musig2_secnonces->end()) return std::nullopt;
return it->second;
}
void FlatSigningProvider::DeleteMuSig2Session(const uint256& session_id) const
{
if (!Assume(musig2_secnonces)) return;
musig2_secnonces->erase(session_id);
}
FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
{
scripts.merge(b.scripts);
@ -102,6 +147,8 @@ FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
origins.merge(b.origins);
tr_trees.merge(b.tr_trees);
aggregate_pubkeys.merge(b.aggregate_pubkeys);
// We shouldn't be merging 2 different sessions, just overwrite with b's sessions.
if (!musig2_secnonces) musig2_secnonces = b.musig2_secnonces;
return *this;
}

View File

@ -9,11 +9,15 @@
#include <addresstype.h>
#include <attributes.h>
#include <key.h>
#include <musig.h>
#include <pubkey.h>
#include <script/keyorigin.h>
#include <script/script.h>
#include <sync.h>
#include <functional>
#include <optional>
struct ShortestVectorFirstComparator
{
bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const
@ -162,6 +166,10 @@ public:
virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; }
virtual bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; }
virtual std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const { return {}; }
virtual std::map<CPubKey, std::vector<CPubKey>> GetAllMuSig2ParticipantPubkeys() const {return {}; }
virtual void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const {}
virtual std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const { return std::nullopt; }
virtual void DeleteMuSig2Session(const uint256& session_id) const {}
bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const
{
@ -206,6 +214,10 @@ public:
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
std::map<CPubKey, std::vector<CPubKey>> GetAllMuSig2ParticipantPubkeys() const override;
void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override;
std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const override;
void DeleteMuSig2Session(const uint256& session_id) const override;
};
struct FlatSigningProvider final : public SigningProvider
@ -216,6 +228,7 @@ struct FlatSigningProvider final : public SigningProvider
std::map<CKeyID, CKey> keys;
std::map<XOnlyPubKey, TaprootBuilder> tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */
std::map<CPubKey, std::vector<CPubKey>> aggregate_pubkeys; /** MuSig2 aggregate pubkeys */
std::map<uint256, MuSig2SecNonce>* musig2_secnonces{nullptr};
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
@ -225,6 +238,10 @@ struct FlatSigningProvider final : public SigningProvider
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
std::map<CPubKey, std::vector<CPubKey>> GetAllMuSig2ParticipantPubkeys() const override;
void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override;
std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const override;
void DeleteMuSig2Session(const uint256& session_id) const override;
FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
};

View File

@ -52,7 +52,7 @@ constexpr int XONLY_KEYS = 1 << 6; // X-only pubkeys are in use (and thus inferr
constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available, so ToPrivateString will fail.
constexpr int SIGNABLE_FAILS = 1 << 8; // We can sign with this descriptor, but actually trying to sign will fail
constexpr int MUSIG = 1 << 9; // This is a MuSig so key counts will have an extra key
constexpr int MUSIG_DERIVATION = 1 << 10; // MuSig with derivation from the aggregate key
constexpr int MUSIG_DERIVATION = 1 << 10; // MuSig with BIP 328 derivation from the aggregate key
constexpr int MIXED_MUSIG = 1 << 11; // Both MuSig and normal key expressions are present
constexpr int UNIQUE_XPUBS = 1 << 12; // Whether the xpub count should be of unique xpubs
@ -315,6 +315,7 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
size_t num_xpubs = CountXpubs(pub1);
size_t num_unique_xpubs = CountUniqueXpubs(pub1);
if (flags & MUSIG_DERIVATION) {
// Deriving from the aggregate will include the synthetic xpub of the aggregate in the caches and SigningProviders.
num_xpubs++;
num_unique_xpubs++;
}

View File

@ -1256,6 +1256,10 @@ std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvid
FlatSigningProvider master_provider;
master_provider.keys = GetKeys();
m_wallet_descriptor.descriptor->ExpandPrivate(index, master_provider, *out_keys);
// Always include musig_secnonces as this descriptor may have a participant private key
// but not a musig() descriptor
out_keys->musig2_secnonces = &m_musig2_secnonces;
}
return out_keys;

View File

@ -10,6 +10,7 @@
#include <common/signmessage.h>
#include <common/types.h>
#include <logging.h>
#include <musig.h>
#include <node/types.h>
#include <psbt.h>
#include <script/descriptor.h>
@ -293,6 +294,19 @@ private:
//! Number of pre-generated keys/scripts (part of the look-ahead process, used to detect payments)
int64_t m_keypool_size GUARDED_BY(cs_desc_man){DEFAULT_KEYPOOL_SIZE};
/** Map of a session id to MuSig2 secnonce
*
* Stores MuSig2 secnonces while the MuSig2 signing session is still ongoing.
* Note that these secnonces must not be reused. In order to avoid being tricked into
* reusing a nonce, this map is held only in memory and must not be written to disk.
* The side effect is that signing sessions cannot persist across restarts, but this
* must be done in order to prevent nonce reuse.
*
* The session id is an arbitrary value set by the signer in order for the signing logic
* to find ongoing signing sessions. It is the SHA256 of aggregate xonly key, + participant pubkey + sighash.
*/
mutable std::map<uint256, MuSig2SecNonce> m_musig2_secnonces;
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);

View File

@ -348,6 +348,7 @@ BASE_SCRIPTS = [
'feature_coinstatsindex.py',
'feature_coinstatsindex_compatibility.py',
'wallet_orphanedreward.py',
'wallet_musig.py',
'wallet_timelock.py',
'p2p_permissions.py',
'feature_blocksdir.py',

242
test/functional/wallet_musig.py Executable file
View File

@ -0,0 +1,242 @@
#!/usr/bin/env python3
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import re
from test_framework.descriptors import descsum_create
from test_framework.key import H_POINT
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
)
PRIVKEY_RE = re.compile(r"^tr\((.+?)/.+\)#.{8}$")
PUBKEY_RE = re.compile(r"^tr\((\[.+?\].+?)/.+\)#.{8}$")
ORIGIN_PATH_RE = re.compile(r"^\[\w{8}(/.*)\].*$")
MULTIPATH_TWO_RE = re.compile(r"<(\d+);(\d+)>")
MUSIG_RE = re.compile(r"musig\((.*?)\)")
PLACEHOLDER_RE = re.compile(r"\$\d")
class WalletMuSigTest(BitcoinTestFramework):
WALLET_NUM = 0
def set_test_params(self):
self.num_nodes = 1
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def do_test(self, comment, pattern, sighash_type=None, scriptpath=False, nosign_wallets=None, only_one_musig_wallet=False):
self.log.info(f"Testing {comment}")
has_internal = MULTIPATH_TWO_RE.search(pattern) is not None
wallets = []
keys = []
pat = pattern.replace("$H", H_POINT)
# Figure out how many wallets are needed and create them
expected_pubnonces = 0
expected_partial_sigs = 0
for musig in MUSIG_RE.findall(pat):
musig_partial_sigs = 0
for placeholder in PLACEHOLDER_RE.findall(musig):
wallet_index = int(placeholder[1:])
if nosign_wallets is None or wallet_index not in nosign_wallets:
expected_pubnonces += 1
else:
musig_partial_sigs = None
if musig_partial_sigs is not None:
musig_partial_sigs += 1
if wallet_index < len(wallets):
continue
wallet_name = f"musig_{self.WALLET_NUM}"
self.WALLET_NUM += 1
self.nodes[0].createwallet(wallet_name)
wallet = self.nodes[0].get_wallet_rpc(wallet_name)
wallets.append(wallet)
for priv_desc in wallet.listdescriptors(True)["descriptors"]:
desc = priv_desc["desc"]
if not desc.startswith("tr("):
continue
privkey = PRIVKEY_RE.search(desc).group(1)
break
for pub_desc in wallet.listdescriptors()["descriptors"]:
desc = pub_desc["desc"]
if not desc.startswith("tr("):
continue
pubkey = PUBKEY_RE.search(desc).group(1)
# Since the pubkey is derived from the private key that we have, we need
# to extract and insert the origin path from the pubkey as well.
privkey += ORIGIN_PATH_RE.search(pubkey).group(1)
break
keys.append((privkey, pubkey))
if musig_partial_sigs is not None:
expected_partial_sigs += musig_partial_sigs
# Construct and import each wallet's musig descriptor that
# contains the private key from that wallet and pubkeys of the others
for i, wallet in enumerate(wallets):
if only_one_musig_wallet and i > 0:
continue
desc = pat
import_descs = []
for j, (priv, pub) in enumerate(keys):
if j == i:
desc = desc.replace(f"${i}", priv)
else:
desc = desc.replace(f"${j}", pub)
import_descs.append({
"desc": descsum_create(desc),
"active": True,
"timestamp": "now",
})
res = wallet.importdescriptors(import_descs)
for r in res:
assert_equal(r["success"], True)
# Check that the wallets agree on the same musig address
addr = None
change_addr = None
for i, wallet in enumerate(wallets):
if only_one_musig_wallet and i > 0:
continue
if addr is None:
addr = wallet.getnewaddress(address_type="bech32m")
else:
assert_equal(addr, wallet.getnewaddress(address_type="bech32m"))
if has_internal:
if change_addr is None:
change_addr = wallet.getrawchangeaddress(address_type="bech32m")
else:
assert_equal(change_addr, wallet.getrawchangeaddress(address_type="bech32m"))
# Fund that address
self.def_wallet.sendtoaddress(addr, 10)
self.generate(self.nodes[0], 1)
# Spend that UTXO
utxo = None
for i, wallet in enumerate(wallets):
if only_one_musig_wallet and i > 0:
continue
if utxo is None:
utxo = wallet.listunspent()[0]
else:
assert_equal(utxo, wallet.listunspent()[0])
psbt = wallets[0].walletcreatefundedpsbt(outputs=[{self.def_wallet.getnewaddress(): 5}], inputs=[utxo], change_type="bech32m", changePosition=1)["psbt"]
dec_psbt = self.nodes[0].decodepsbt(psbt)
assert_equal(len(dec_psbt["inputs"]), 1)
assert_equal(len(dec_psbt["inputs"][0]["musig2_participant_pubkeys"]), pattern.count("musig("))
if has_internal:
assert_equal(len(dec_psbt["outputs"][1]["musig2_participant_pubkeys"]), pattern.count("musig("))
# Check all participant pubkeys in the input and change output
psbt_maps = [dec_psbt["inputs"][0]]
if has_internal:
psbt_maps.append(dec_psbt["outputs"][1])
for psbt_map in psbt_maps:
part_pks = set()
for agg in psbt_map["musig2_participant_pubkeys"]:
for part_pub in agg["participant_pubkeys"]:
part_pks.add(part_pub[2:])
# Check that there are as many participants as we expected
assert_equal(len(part_pks), len(keys))
# Check that each participant has a derivation path
for deriv_path in psbt_map["taproot_bip32_derivs"]:
if deriv_path["pubkey"] in part_pks:
part_pks.remove(deriv_path["pubkey"])
assert_equal(len(part_pks), 0)
# Add pubnonces
nonce_psbts = []
for i, wallet in enumerate(wallets):
if nosign_wallets and i in nosign_wallets:
continue
proc = wallet.walletprocesspsbt(psbt=psbt, sighashtype=sighash_type)
assert_equal(proc["complete"], False)
nonce_psbts.append(proc["psbt"])
comb_nonce_psbt = self.nodes[0].combinepsbt(nonce_psbts)
dec_psbt = self.nodes[0].decodepsbt(comb_nonce_psbt)
assert_equal(len(dec_psbt["inputs"][0]["musig2_pubnonces"]), expected_pubnonces)
for pn in dec_psbt["inputs"][0]["musig2_pubnonces"]:
pubkey = pn["aggregate_pubkey"][2:]
if pubkey in dec_psbt["inputs"][0]["witness_utxo"]["scriptPubKey"]["hex"]:
continue
elif "taproot_scripts" in dec_psbt["inputs"][0]:
for leaf_scripts in dec_psbt["inputs"][0]["taproot_scripts"]:
if pubkey in leaf_scripts["script"]:
break
else:
assert False, "Aggregate pubkey for pubnonce not seen as output key, or in any scripts"
else:
assert False, "Aggregate pubkey for pubnonce not seen as output key or internal key"
# Add partial sigs
psig_psbts = []
for i, wallet in enumerate(wallets):
if nosign_wallets and i in nosign_wallets:
continue
proc = wallet.walletprocesspsbt(psbt=comb_nonce_psbt, sighashtype=sighash_type)
assert_equal(proc["complete"], False)
psig_psbts.append(proc["psbt"])
comb_psig_psbt = self.nodes[0].combinepsbt(psig_psbts)
dec_psbt = self.nodes[0].decodepsbt(comb_psig_psbt)
assert_equal(len(dec_psbt["inputs"][0]["musig2_partial_sigs"]), expected_partial_sigs)
for ps in dec_psbt["inputs"][0]["musig2_partial_sigs"]:
pubkey = ps["aggregate_pubkey"][2:]
if pubkey in dec_psbt["inputs"][0]["witness_utxo"]["scriptPubKey"]["hex"]:
continue
elif "taproot_scripts" in dec_psbt["inputs"][0]:
for leaf_scripts in dec_psbt["inputs"][0]["taproot_scripts"]:
if pubkey in leaf_scripts["script"]:
break
else:
assert False, "Aggregate pubkey for partial sig not seen as output key or in any scripts"
else:
assert False, "Aggregate pubkey for partial sig not seen as output key"
# Non-participant aggregates partial sigs and send
finalized = self.nodes[0].finalizepsbt(psbt=comb_psig_psbt, extract=False)
assert_equal(finalized["complete"], True)
witness = self.nodes[0].decodepsbt(finalized["psbt"])["inputs"][0]["final_scriptwitness"]
if scriptpath:
assert_greater_than(len(witness), 1)
else:
assert_equal(len(witness), 1)
finalized = self.nodes[0].finalizepsbt(comb_psig_psbt)
assert "hex" in finalized
self.nodes[0].sendrawtransaction(finalized["hex"])
def run_test(self):
self.def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.do_test("rawtr(musig(keys/*))", "rawtr(musig($0/<0;1>/*,$1/<1;2>/*,$2/<2;3>/*))")
self.do_test("rawtr(musig(keys/*)) with ALL|ANYONECANPAY", "rawtr(musig($0/<0;1>/*,$1/<1;2>/*,$2/<2;3>/*))", "ALL|ANYONECANPAY")
self.do_test("tr(musig(keys/*)) no multipath", "tr(musig($0/0/*,$1/1/*,$2/2/*))")
self.do_test("tr(musig(keys/*)) 2 index multipath", "tr(musig($0/<0;1>/*,$1/<1;2>/*,$2/<2;3>/*))")
self.do_test("tr(musig(keys/*)) 3 index multipath", "tr(musig($0/<0;1;2>/*,$1/<1;2;3>/*,$2/<2;3;4>/*))")
self.do_test("rawtr(musig/*)", "rawtr(musig($0,$1,$2)/<0;1>/*)")
self.do_test("tr(musig/*)", "tr(musig($0,$1,$2)/<0;1>/*)")
self.do_test("rawtr(musig(keys/*)) without all wallets importing", "rawtr(musig($0/<0;1>/*,$1/<0;1>/*,$2/<0;1>/*))", only_one_musig_wallet=True)
self.do_test("tr(musig(keys/*)) without all wallets importing", "tr(musig($0/<0;1>/*,$1/<0;1>/*,$2/<0;1>/*))", only_one_musig_wallet=True)
self.do_test("tr(H, pk(musig(keys/*)))", "tr($H,pk(musig($0/<0;1>/*,$1/<1;2>/*,$2/<2;3>/*)))", scriptpath=True)
self.do_test("tr(H,pk(musig/*))", "tr($H,pk(musig($0,$1,$2)/<0;1>/*))", scriptpath=True)
self.do_test("tr(H,{pk(musig/*), pk(musig/*)})", "tr($H,{pk(musig($0,$1,$2)/<0;1>/*),pk(musig($3,$4,$5)/0/*)})", scriptpath=True)
self.do_test("tr(H,{pk(musig/*), pk(same keys different musig/*)})", "tr($H,{pk(musig($0,$1,$2)/<0;1>/*),pk(musig($1,$2)/0/*)})", scriptpath=True)
self.do_test("tr(musig/*,{pk(partial keys diff musig-1/*),pk(partial keys diff musig-2/*)})}", "tr(musig($0,$1,$2)/<3;4>/*,{pk(musig($0,$1)/<5;6>/*),pk(musig($1,$2)/7/*)})")
self.do_test("tr(musig/*,{pk(partial keys diff musig-1/*),pk(partial keys diff musig-2/*)})} script-path", "tr(musig($0,$1,$2)/<3;4>/*,{pk(musig($0,$1)/<5;6>/*),pk(musig($1,$2)/7/*)})", scriptpath=True, nosign_wallets=[0])
if __name__ == '__main__':
WalletMuSigTest(__file__).main()