0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-01 09:35:52 -05:00
This commit is contained in:
Ava Chow 2025-01-31 21:49:37 +01:00 committed by GitHub
commit cf09e81867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 2173 additions and 221 deletions

View file

@ -38,7 +38,7 @@ message("Configuring secp256k1 subtree...")
set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE) set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_ECDH OFF 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_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_BENCHMARK OFF CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_EXHAUSTIVE_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.cpp
key_io.cpp key_io.cpp
merkleblock.cpp merkleblock.cpp
musig.cpp
net_permissions.cpp net_permissions.cpp
net_types.cpp net_types.cpp
netaddress.cpp netaddress.cpp

View file

@ -116,6 +116,10 @@ bilingual_str PSBTErrorString(PSBTError err)
return Untranslated("External signer failed to sign"); return Untranslated("External signer failed to sign");
case PSBTError::UNSUPPORTED: case PSBTError::UNSUPPORTED:
return Untranslated("Signer does not support PSBT"); 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 // no default case, so the compiler can warn about missing cases
} }
assert(false); assert(false);

View file

@ -20,6 +20,8 @@ enum class PSBTError {
EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_NOT_FOUND,
EXTERNAL_SIGNER_FAILED, EXTERNAL_SIGNER_FAILED,
UNSUPPORTED, UNSUPPORTED,
INCOMPLETE,
OK,
}; };
} // namespace common } // namespace common

View file

@ -207,7 +207,7 @@ public:
int& num_blocks) = 0; int& num_blocks) = 0;
//! Fill PSBT. //! Fill PSBT.
virtual std::optional<common::PSBTError> fillPSBT(int sighash_type, virtual std::optional<common::PSBTError> fillPSBT(std::optional<int> sighash_type,
bool sign, bool sign,
bool bip32derivs, bool bip32derivs,
size_t* n_signed, size_t* n_signed,

View file

@ -13,6 +13,7 @@
#include <secp256k1.h> #include <secp256k1.h>
#include <secp256k1_ellswift.h> #include <secp256k1_ellswift.h>
#include <secp256k1_extrakeys.h> #include <secp256k1_extrakeys.h>
#include <secp256k1_musig.h>
#include <secp256k1_recovery.h> #include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h> #include <secp256k1_schnorrsig.h>
@ -349,6 +350,127 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const
return KeyPair(*this, merkle_root); return KeyPair(*this, merkle_root);
} }
std::vector<uint8_t> CKey::CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& hash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys)
{
// Get the keyagg cache and aggregate pubkey
secp256k1_musig_keyagg_cache keyagg_cache;
if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) return {};
std::optional<CPubKey> 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<uint8_t> out;
out.resize(66);
if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_sign, out.data(), &pubnonce)) {
return {};
}
return out;
}
std::optional<uint256> CKey::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)
{
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<CPubKey> 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<std::pair<secp256k1_pubkey, secp256k1_musig_pubnonce>> signers_data;
std::vector<const secp256k1_musig_pubnonce*> 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<uint8_t> 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 GenerateRandomKey(bool compressed) noexcept
{ {
CKey key; CKey key;

View file

@ -7,6 +7,7 @@
#ifndef BITCOIN_KEY_H #ifndef BITCOIN_KEY_H
#define BITCOIN_KEY_H #define BITCOIN_KEY_H
#include <musig.h>
#include <pubkey.h> #include <pubkey.h>
#include <serialize.h> #include <serialize.h>
#include <support/allocators/secure.h> #include <support/allocators/secure.h>
@ -220,6 +221,9 @@ public:
* Merkle root of the script tree). * Merkle root of the script tree).
*/ */
KeyPair ComputeKeyPair(const uint256* merkle_root) const; KeyPair ComputeKeyPair(const uint256* merkle_root) const;
std::vector<uint8_t> CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& hash, 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; CKey GenerateRandomKey(bool compressed = true) noexcept;

183
src/musig.cpp Normal file
View file

@ -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 <musig.h>
#include <support/allocators/secure.h>
#include <secp256k1_musig.h>
bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
{
// Parse the pubkeys
std::vector<secp256k1_pubkey> secp_pubkeys;
std::vector<const secp256k1_pubkey*> 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<CPubKey> 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<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& 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<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();
}
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)
{
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<CPubKey> 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<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 : participants) {
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() != 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<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;
}

54
src/musig.h Normal file
View file

@ -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 <pubkey.h>
#include <vector>
struct secp256k1_musig_keyagg_cache;
class MuSig2SecNonceImpl;
struct secp256k1_musig_secnonce;
bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache);
std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& cache);
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& 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<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();
};
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

@ -64,7 +64,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Figure out what is missing // Figure out what is missing
SignatureData outdata; 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 // Things are missing
if (!complete) { if (!complete) {
@ -124,7 +124,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
PSBTInput& input = psbtx.inputs[i]; PSBTInput& input = psbtx.inputs[i];
Coin newcoin; 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; success = false;
break; break;
} else { } else {

View file

@ -4,12 +4,15 @@
#include <psbt.h> #include <psbt.h>
#include <common/types.h>
#include <node/types.h> #include <node/types.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <script/signingprovider.h> #include <script/signingprovider.h>
#include <util/check.h> #include <util/check.h>
#include <util/strencodings.h> #include <util/strencodings.h>
using common::PSBTError;
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx) PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{ {
inputs.resize(tx.vin.size()); inputs.resize(tx.vin.size());
@ -146,6 +149,9 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
for (const auto& [hash, preimage] : hash256_preimages) { for (const auto& [hash, preimage] : hash256_preimages) {
sigdata.hash256_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage); 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());
sigdata.musig2_pubnonces.insert(m_musig2_pubnonces.begin(), m_musig2_pubnonces.end());
sigdata.musig2_partial_sigs.insert(m_musig2_partial_sigs.begin(), m_musig2_partial_sigs.end());
} }
void PSBTInput::FromSignatureData(const SignatureData& sigdata) void PSBTInput::FromSignatureData(const SignatureData& sigdata)
@ -193,6 +199,13 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata)
for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) {
m_tap_bip32_paths.emplace(pubkey, leaf_origin); 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) void PSBTInput::Merge(const PSBTInput& input)
@ -220,6 +233,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_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_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; 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 void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
@ -249,6 +269,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey); 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) void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
@ -271,6 +292,7 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) {
m_tap_bip32_paths.emplace(pubkey, leaf_origin); m_tap_bip32_paths.emplace(pubkey, leaf_origin);
} }
m_musig2_participants.insert(sigdata.musig2_pubkeys.begin(), sigdata.musig2_pubkeys.end());
} }
bool PSBTOutput::IsNull() const bool PSBTOutput::IsNull() const
@ -288,6 +310,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; 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_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; 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) bool PSBTInputSigned(const PSBTInput& input)
@ -372,13 +395,13 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
return txdata; return txdata;
} }
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize) PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, std::optional<int> sighash, SignatureData* out_sigdata, bool finalize)
{ {
PSBTInput& input = psbt.inputs.at(index); PSBTInput& input = psbt.inputs.at(index);
const CMutableTransaction& tx = *psbt.tx; const CMutableTransaction& tx = *psbt.tx;
if (PSBTInputSignedAndVerified(psbt, index, txdata)) { if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
return true; return PSBTError::OK;
} }
// Fill SignatureData with input info // Fill SignatureData with input info
@ -393,10 +416,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout. // If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
COutPoint prevout = tx.vin[index].prevout; COutPoint prevout = tx.vin[index].prevout;
if (prevout.n >= input.non_witness_utxo->vout.size()) { if (prevout.n >= input.non_witness_utxo->vout.size()) {
return false; return PSBTError::MISSING_INPUTS;
} }
if (input.non_witness_utxo->GetHash() != prevout.hash) { if (input.non_witness_utxo->GetHash() != prevout.hash) {
return false; return PSBTError::MISSING_INPUTS;
} }
utxo = input.non_witness_utxo->vout[prevout.n]; utxo = input.non_witness_utxo->vout[prevout.n];
} else if (!input.witness_utxo.IsNull()) { } else if (!input.witness_utxo.IsNull()) {
@ -407,19 +430,38 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
// a witness signature in this situation. // a witness signature in this situation.
require_witness_sig = true; require_witness_sig = true;
} else { } else {
return false; return PSBTError::MISSING_INPUTS;
} }
// Get the sighash type
// If both the field and the paramter are provided, they must match
// If only the psbt field is provided, refuse to sign. For user safety, the desired sighash must
// be provided if the PSBT wants something other than DEFAULT
// If only the paramter is provider, use it and add it to the PSBT if it is other than SIGHASH_DEFAULT
// for all input types, and not SIGHASH_ALL for non-taproot input types.
// If neither are provided, use SIGHASH_DEFAULT
if (!sighash) sighash = utxo.scriptPubKey.IsPayToTaproot() ? SIGHASH_DEFAULT : SIGHASH_ALL;
if (input.sighash_type && input.sighash_type != sighash) {
return PSBTError::SIGHASH_MISMATCH;
} else {
if ((!utxo.scriptPubKey.IsPayToTaproot() && (sighash != SIGHASH_ALL && sighash != SIGHASH_DEFAULT)) ||
(utxo.scriptPubKey.IsPayToTaproot() && sighash != SIGHASH_DEFAULT)
) {
input.sighash_type = sighash;
}
}
Assert(sighash.has_value());
sigdata.witness = false; sigdata.witness = false;
bool sig_complete; bool sig_complete;
if (txdata == nullptr) { if (txdata == nullptr) {
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
} else { } else {
MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, sighash); MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, *sighash);
sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
} }
// Verify that a witness signature was produced in case one was required. // Verify that a witness signature was produced in case one was required.
if (require_witness_sig && !sigdata.witness) return false; if (require_witness_sig && !sigdata.witness) return PSBTError::INCOMPLETE;
// If we are not finalizing, set sigdata.complete to false to not set the scriptWitness // If we are not finalizing, set sigdata.complete to false to not set the scriptWitness
if (!finalize && sigdata.complete) sigdata.complete = false; if (!finalize && sigdata.complete) sigdata.complete = false;
@ -442,39 +484,44 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
out_sigdata->missing_witness_script = sigdata.missing_witness_script; out_sigdata->missing_witness_script = sigdata.missing_witness_script;
} }
return sig_complete; return sig_complete ? PSBTError::OK : PSBTError::INCOMPLETE;
} }
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type) void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx)
{ {
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY // Figure out if any non_witness_utxos should be dropped
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) { std::vector<unsigned int> to_drop;
// Figure out if any non_witness_utxos should be dropped for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
std::vector<unsigned int> to_drop; const auto& input = psbtx.inputs.at(i);
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { int wit_ver;
const auto& input = psbtx.inputs.at(i); std::vector<unsigned char> wit_prog;
int wit_ver; if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
std::vector<unsigned char> wit_prog; // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) { to_drop.clear();
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos break;
to_drop.clear(); }
break; if (wit_ver == 0) {
} // Segwit v0, so we cannot drop any non_witness_utxos
if (wit_ver == 0) { to_drop.clear();
// Segwit v0, so we cannot drop any non_witness_utxos break;
to_drop.clear(); }
break; // non_witness_utxos can only be dropped if the sighash type does not include SIGHASH_ANYONECANPAY
} // Since callers should have called SignPSBTInput which updates the sighash type in the PSBT, we only
if (input.non_witness_utxo) { // need to look at that field. If it is not present, then we can assume SIGHASH_DEFAULT or SIGHASH_ALL.
to_drop.push_back(i); if (input.sighash_type != std::nullopt && (*input.sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
} to_drop.clear();
break;
} }
// Drop the non_witness_utxos that we can drop if (input.non_witness_utxo) {
for (unsigned int i : to_drop) { to_drop.push_back(i);
psbtx.inputs.at(i).non_witness_utxo = nullptr;
} }
} }
// Drop the non_witness_utxos that we can drop
for (unsigned int i : to_drop) {
psbtx.inputs.at(i).non_witness_utxo = nullptr;
}
} }
bool FinalizePSBT(PartiallySignedTransaction& psbtx) bool FinalizePSBT(PartiallySignedTransaction& psbtx)
@ -486,7 +533,8 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
bool complete = true; bool complete = true;
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true); PSBTInput& input = psbtx.inputs.at(i);
complete &= (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, input.sighash_type, nullptr, true) == PSBTError::OK);
} }
return complete; return complete;

View file

@ -5,6 +5,7 @@
#ifndef BITCOIN_PSBT_H #ifndef BITCOIN_PSBT_H
#define BITCOIN_PSBT_H #define BITCOIN_PSBT_H
#include <common/types.h>
#include <node/transaction.h> #include <node/transaction.h>
#include <policy/feerate.h> #include <policy/feerate.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
@ -21,6 +22,8 @@ namespace node {
enum class TransactionError; enum class TransactionError;
} // namespace node } // namespace node
using common::PSBTError;
// Magic bytes // Magic bytes
static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff};
@ -50,6 +53,9 @@ static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15;
static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16; static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16;
static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17; static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17;
static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18; static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18;
static constexpr uint8_t PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a;
static constexpr uint8_t PSBT_IN_MUSIG2_PUB_NONCE = 0x1b;
static constexpr uint8_t PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c;
static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC; static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC;
// Output types // Output types
@ -59,6 +65,7 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05; static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06; static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06;
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07; static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
static constexpr uint8_t PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08;
static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC; static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC;
// The separator is 0x00. Reading this in means that the unserializer can interpret it // The separator is 0x00. Reading this in means that the unserializer can interpret it
@ -217,6 +224,11 @@ struct PSBTInput
XOnlyPubKey m_tap_internal_key; XOnlyPubKey m_tap_internal_key;
uint256 m_tap_merkle_root; uint256 m_tap_merkle_root;
// MuSig2 fields
std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, std::vector<uint8_t>>> m_musig2_pubnonces;
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, uint256>> m_musig2_partial_sigs;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary; std::set<PSBTProprietary> m_proprietary;
std::optional<int> sighash_type; std::optional<int> sighash_type;
@ -337,6 +349,43 @@ struct PSBTInput
SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT); SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT);
SerializeToVector(s, m_tap_merkle_root); SerializeToVector(s, m_tap_merkle_root);
} }
// Write MuSig2 Participants
for (const auto& [agg_key, part_pubs] : m_musig2_participants) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS), Span{agg_key});
std::vector<unsigned char> value;
VectorWriter s_value{value, 0};
for (auto& pk : part_pubs) {
s_value << Span{pk};
}
s << value;
}
// Write MuSig2 pubnonces
for (const auto& [agg_key_leaf_hash, pubnonces] : m_musig2_pubnonces) {
const auto& [agg_key, leaf_hash] = agg_key_leaf_hash;
for (const auto& [pubkey, pubnonce] : pubnonces) {
if (leaf_hash.IsNull()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PUB_NONCE), Span{pubkey}, Span{agg_key});
} else {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PUB_NONCE), Span{pubkey}, Span{agg_key}, leaf_hash);
}
s << pubnonce;
}
}
// Write MuSig2 partial signatures
for (const auto& [agg_key_leaf_hash, psigs] : m_musig2_partial_sigs) {
const auto& [agg_key, leaf_hash] = agg_key_leaf_hash;
for (const auto& [pubkey, psig] : psigs) {
if (leaf_hash.IsNull()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PARTIAL_SIG), Span{pubkey}, Span{agg_key});
} else {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PARTIAL_SIG), Span{pubkey}, Span{agg_key}, leaf_hash);
}
SerializeToVector(s, psig);
}
}
} }
// Write script sig // Write script sig
@ -672,6 +721,83 @@ struct PSBTInput
UnserializeFromVector(s, m_tap_merkle_root); UnserializeFromVector(s, m_tap_merkle_root);
break; break;
} }
case PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input participant pubkeys for an aggregate key already provided");
} else if (key.size() != 34) {
throw std::ios_base::failure("Input musig2 participants pubkeys aggregate key is not 34 bytes");
}
std::array<unsigned char, 33> agg_key_bytes;
skey >> AsWritableBytes(Span{agg_key_bytes});
CPubKey agg_key(agg_key_bytes);
std::vector<CPubKey> participants;
std::vector<unsigned char> val;
s >> val;
SpanReader s_val{val};
while (s_val.size() >= 33) {
std::array<unsigned char, 33> part_key_bytes;
s_val >> AsWritableBytes(Span{part_key_bytes});
participants.emplace_back(Span{part_key_bytes});
}
if (!s_val.empty()) {
throw std::ios_base::failure("Input musig2 particiapnts pubkeys value size is not a multiple of 33");
}
m_musig2_participants.emplace(agg_key, participants);
break;
}
case PSBT_IN_MUSIG2_PUB_NONCE:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input musig2 pubnonce already provided");
} else if (key.size() != 67 && key.size() != 99) {
throw std::ios_base::failure("Input musig2 pubnonce key is not expected size");
}
std::array<unsigned char, 33> part_pubkey_bytes;
std::array<unsigned char, 33> agg_pubkey_bytes;
uint256 leaf_hash;
skey >> AsWritableBytes(Span{part_pubkey_bytes}) >> AsWritableBytes(Span{agg_pubkey_bytes});
CPubKey agg_pub{Span{agg_pubkey_bytes}};
CPubKey part_pub{Span{part_pubkey_bytes}};
if (!skey.empty()) {
skey >> leaf_hash;
}
std::vector<uint8_t> pubnonce;
s >> pubnonce;
m_musig2_pubnonces[std::make_pair(agg_pub, leaf_hash)].emplace(part_pub, pubnonce);
break;
}
case PSBT_IN_MUSIG2_PARTIAL_SIG:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input musig2 partial sig already provided");
} else if (key.size() != 67 && key.size() != 99) {
throw std::ios_base::failure("Input musig2 partial sig key is not expected size");
}
std::array<unsigned char, 33> part_pubkey_bytes;
std::array<unsigned char, 33> agg_pubkey_bytes;
uint256 leaf_hash;
skey >> AsWritableBytes(Span{part_pubkey_bytes}) >> AsWritableBytes(Span{agg_pubkey_bytes});
CPubKey agg_pub{Span{agg_pubkey_bytes}};
CPubKey part_pub{Span{part_pubkey_bytes}};
if (!skey.empty()) {
skey >> leaf_hash;
}
uint256 partial_sig;
UnserializeFromVector(s, partial_sig);
m_musig2_partial_sigs[std::make_pair(agg_pub, leaf_hash)].emplace(part_pub, partial_sig);
break;
}
case PSBT_IN_PROPRIETARY: case PSBT_IN_PROPRIETARY:
{ {
PSBTProprietary this_prop; PSBTProprietary this_prop;
@ -719,6 +845,7 @@ struct PSBTOutput
XOnlyPubKey m_tap_internal_key; XOnlyPubKey m_tap_internal_key;
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree; std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree;
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary; std::set<PSBTProprietary> m_proprietary;
@ -781,6 +908,17 @@ struct PSBTOutput
s << value; s << value;
} }
// Write MuSig2 Participants
for (const auto& [agg_key, part_pubs] : m_musig2_participants) {
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS), Span{agg_key});
std::vector<unsigned char> value;
VectorWriter s_value{value, 0};
for (auto& pk : part_pubs) {
s_value << Span{pk};
}
s << value;
}
// Write unknown things // Write unknown things
for (auto& entry : unknown) { for (auto& entry : unknown) {
s << entry.first; s << entry.first;
@ -907,6 +1045,33 @@ struct PSBTOutput
m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len)));
break; break;
} }
case PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output participant pubkeys for an aggregate key already provided");
} else if (key.size() != 34) {
throw std::ios_base::failure("Output musig2 participants pubkeys aggregate key is not 34 bytes");
}
std::array<unsigned char, 33> agg_key_bytes;
skey.read(AsWritableBytes(Span{agg_key_bytes}));
CPubKey agg_key(agg_key_bytes);
std::vector<CPubKey> participants;
std::vector<unsigned char> val;
s >> val;
SpanReader s_val{val};
while (s_val.size() >= 33) {
std::array<unsigned char, 33> part_key_bytes;
s_val.read(AsWritableBytes(Span{part_key_bytes}));
participants.emplace_back(Span{part_key_bytes});
}
if (!s_val.empty()) {
throw std::ios_base::failure("Input musig2 particiapnts pubkeys value size is not a multiple of 33");
}
m_musig2_participants.emplace(agg_key, participants);
break;
}
case PSBT_OUT_PROPRIETARY: case PSBT_OUT_PROPRIETARY:
{ {
PSBTProprietary this_prop; PSBTProprietary this_prop;
@ -1236,10 +1401,10 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
* txdata should be the output of PrecomputePSBTData (which can be shared across * txdata should be the output of PrecomputePSBTData (which can be shared across
* multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
**/ **/
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true); [[nodiscard]] PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, std::optional<int> sighash = std::nullopt, SignatureData* out_sigdata = nullptr, bool finalize = true);
/** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */ /** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type); void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx);
/** Counts the unsigned inputs of a PSBT. */ /** Counts the unsigned inputs of a PSBT. */
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);

View file

@ -197,20 +197,29 @@ constexpr XOnlyPubKey XOnlyPubKey::NUMS_H{
[]() consteval { return XOnlyPubKey{"50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"_hex_u8}; }(), []() consteval { return XOnlyPubKey{"50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"_hex_u8}; }(),
}; };
std::vector<CPubKey> XOnlyPubKey::GetCPubKeys() const
{
std::vector<CPubKey> out;
unsigned char b[33] = {0x02};
std::copy(m_keydata.begin(), m_keydata.end(), b + 1);
CPubKey fullpubkey;
fullpubkey.Set(b, b + 33);
out.push_back(fullpubkey);
b[0] = 0x03;
fullpubkey.Set(b, b + 33);
out.push_back(fullpubkey);
return out;
}
std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const
{ {
std::vector<CKeyID> out; std::vector<CKeyID> out;
// For now, use the old full pubkey-based key derivation logic. As it is indexed by // For now, use the old full pubkey-based key derivation logic. As it is indexed by
// Hash160(full pubkey), we need to return both a version prefixed with 0x02, and one // Hash160(full pubkey), we need to return both a version prefixed with 0x02, and one
// with 0x03. // with 0x03.
unsigned char b[33] = {0x02}; for (const CPubKey& pk : GetCPubKeys()) {
std::copy(m_keydata.begin(), m_keydata.end(), b + 1); out.push_back(pk.GetID());
CPubKey fullpubkey; }
fullpubkey.Set(b, b + 33);
out.push_back(fullpubkey.GetID());
b[0] = 0x03;
fullpubkey.Set(b, b + 33);
out.push_back(fullpubkey.GetID());
return out; return out;
} }
@ -332,13 +341,14 @@ bool CPubKey::Decompress() {
return true; 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& tweak_out) const {
assert(IsValid()); assert(IsValid());
assert((nChild >> 31) == 0); assert((nChild >> 31) == 0);
assert(size() == COMPRESSED_SIZE); assert(size() == COMPRESSED_SIZE);
unsigned char out[64]; unsigned char out[64];
BIP32Hash(cc, nChild, *begin(), begin()+1, out); BIP32Hash(cc, nChild, *begin(), begin()+1, out);
memcpy(ccChild.begin(), out+32, 32); memcpy(ccChild.begin(), out+32, 32);
memcpy(tweak_out.begin(), out, 32);
secp256k1_pubkey pubkey; secp256k1_pubkey pubkey;
if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, vch, size())) { if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, vch, size())) {
return false; return false;
@ -404,12 +414,17 @@ void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VE
} }
bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const { bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const {
uint256 tweak;
return Derive(out, _nChild, tweak);
}
bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild, uint256& tweak_out) const {
if (nDepth == std::numeric_limits<unsigned char>::max()) return false; if (nDepth == std::numeric_limits<unsigned char>::max()) return false;
out.nDepth = nDepth + 1; out.nDepth = nDepth + 1;
CKeyID id = pubkey.GetID(); CKeyID id = pubkey.GetID();
memcpy(out.vchFingerprint, &id, 4); memcpy(out.vchFingerprint, &id, 4);
out.nChild = _nChild; out.nChild = _nChild;
return pubkey.Derive(out.pubkey, out.chaincode, _nChild, chaincode); return pubkey.Derive(out.pubkey, out.chaincode, _nChild, chaincode, tweak_out);
} }
/* static */ bool CPubKey::CheckLowS(const std::vector<unsigned char>& vchSig) { /* static */ bool CPubKey::CheckLowS(const std::vector<unsigned char>& vchSig) {

View file

@ -224,7 +224,7 @@ public:
bool Decompress(); bool Decompress();
//! Derive BIP32 child pubkey. //! 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& tweak_out) const;
}; };
class XOnlyPubKey class XOnlyPubKey
@ -286,6 +286,7 @@ public:
* This is needed for key lookups since keys are indexed by CKeyID. * This is needed for key lookups since keys are indexed by CKeyID.
*/ */
std::vector<CKeyID> GetKeyIDs() const; std::vector<CKeyID> GetKeyIDs() const;
std::vector<CPubKey> GetCPubKeys() const;
CPubKey GetEvenCorrespondingCPubKey() const; CPubKey GetEvenCorrespondingCPubKey() const;
@ -376,6 +377,7 @@ struct CExtPubKey {
void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const; void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const;
void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]); 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) const;
[[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild, uint256& tweak_out) const;
}; };
#endif // BITCOIN_PUBKEY_H #endif // BITCOIN_PUBKEY_H

View file

@ -59,7 +59,7 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness. bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
if (m_wallet_model) { if (m_wallet_model) {
size_t n_could_sign; size_t n_could_sign;
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete)}; const auto err{m_wallet_model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete)};
if (err) { if (err) {
showStatus(tr("Failed to load transaction: %1") showStatus(tr("Failed to load transaction: %1")
.arg(QString::fromStdString(PSBTErrorString(*err).translated)), .arg(QString::fromStdString(PSBTErrorString(*err).translated)),
@ -83,7 +83,7 @@ void PSBTOperationsDialog::signTransaction()
WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock()); WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock());
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)}; const auto err{m_wallet_model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)};
if (err) { if (err) {
showStatus(tr("Failed to sign transaction: %1") showStatus(tr("Failed to sign transaction: %1")
@ -251,7 +251,7 @@ size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &p
size_t n_signed; size_t n_signed;
bool complete; bool complete;
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete)}; const auto err{m_wallet_model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete)};
if (err) { if (err) {
return 0; return 0;

View file

@ -446,7 +446,7 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) { bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
std::optional<PSBTError> err; std::optional<PSBTError> err;
try { try {
err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); err = model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
return false; return false;
@ -503,7 +503,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
// Fill without signing // Fill without signing
const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)}; const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
assert(!complete); assert(!complete);
assert(!err); assert(!err);
@ -519,7 +519,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool complete = false; bool complete = false;
// Always fill without signing first. This prevents an external signer // Always fill without signing first. This prevents an external signer
// from being called prematurely and is not expensive. // from being called prematurely and is not expensive.
const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)}; const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
assert(!complete); assert(!complete);
assert(!err); assert(!err);
send_failure = !signWithExternalSigner(psbtx, mtx, complete); send_failure = !signWithExternalSigner(psbtx, mtx, complete);

View file

@ -535,7 +535,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
// "Create Unsigned" clicked // "Create Unsigned" clicked
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const auto err{wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)}; const auto err{wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)};
if (err || complete) { if (err || complete) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction.")); QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
return false; return false;

View file

@ -163,7 +163,7 @@ static std::vector<RPCArg> CreateTxDoc()
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors. // Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
// Optionally, sign the inputs that we can using information from the descriptors. // Optionally, sign the inputs that we can using information from the descriptors.
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize) PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, std::optional<int> sighash_type, bool finalize)
{ {
// Unserialize the transactions // Unserialize the transactions
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
@ -235,7 +235,10 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
// We only actually care about those if our signing provider doesn't hide private // We only actually care about those if our signing provider doesn't hide private
// information, as is the case with `descriptorprocesspsbt` // information, as is the case with `descriptorprocesspsbt`
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize); // Only error for mismatching sighash types as it is critical that the sighash to sign with matches the PSBT's
if (SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize) == common::PSBTError::SIGHASH_MISMATCH) {
throw JSONRPCPSBTError(common::PSBTError::SIGHASH_MISMATCH);
}
} }
// Update script/keypath information using descriptor data. // Update script/keypath information using descriptor data.
@ -243,7 +246,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
UpdatePSBTOutput(provider, psbtx, i); UpdatePSBTOutput(provider, psbtx, i);
} }
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1); RemoveUnnecessaryTransactions(psbtx);
return psbtx; return psbtx;
} }
@ -916,6 +919,37 @@ const RPCResult decodepsbt_inputs{
}}, }},
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
{RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"}, {RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"},
{RPCResult::Type::ARR, "musig2_participant_pubkeys", /*optional=*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which the participants create."},
{RPCResult::Type::ARR, "participant_pubkeys", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "The plain public keys that are aggregated for aggregate_pubkey."},
}},
}},
}},
{RPCResult::Type::ARR, "musig2_pubnonces", /*optional=*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "participant_pubkey", "The plain public key of the participant that created this pubnonce."},
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which this pubnonce is for."},
{RPCResult::Type::STR_HEX, "leaf_hash", /*optional=*/true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
{RPCResult::Type::STR_HEX, "pubnonce", "The public nonce itself."},
}},
}},
{RPCResult::Type::ARR, "musig2_partial_sigs", /*optional=*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "participant_pubkey", "The plain public key of the participant that created this partial signature."},
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which this partial signature is for."},
{RPCResult::Type::STR_HEX, "leaf_hash", /*optional=*/true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
{RPCResult::Type::STR_HEX, "partial_sig", "The partial signature itself."},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields",
{ {
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@ -983,6 +1017,17 @@ const RPCResult decodepsbt_outputs{
}}, }},
}}, }},
}}, }},
{RPCResult::Type::ARR, "musig2_participant_pubkeys", /*optional=*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which the participants create."},
{RPCResult::Type::ARR, "participant_pubkeys", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "The plain public keys that are aggregated for aggregate_pubkey."},
}},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields", {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields",
{ {
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@ -1304,6 +1349,52 @@ static RPCHelpMan decodepsbt()
in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root)); in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root));
} }
// Write MuSig2 fields
if (!input.m_musig2_participants.empty()) {
UniValue musig_keys(UniValue::VARR);
for (const auto& [agg, parts] : input.m_musig2_participants) {
UniValue musig_part(UniValue::VOBJ);
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
UniValue part_keys(UniValue::VARR);
for (const auto& pub : parts) {
part_keys.push_back(HexStr(pub));
}
musig_part.pushKV("participant_pubkeys", part_keys);
musig_keys.push_back(musig_part);
}
in.pushKV("musig2_participant_pubkeys", musig_keys);
}
if (!input.m_musig2_pubnonces.empty()) {
UniValue musig_pubnonces(UniValue::VARR);
for (const auto& [agg_lh, part_pubnonce] : input.m_musig2_pubnonces) {
const auto& [agg, lh] = agg_lh;
for (const auto& [part, pubnonce] : part_pubnonce) {
UniValue info(UniValue::VOBJ);
info.pushKV("participant_pubkey", HexStr(part));
info.pushKV("aggregate_pubkey", HexStr(agg));
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
info.pushKV("pubnonce", HexStr(pubnonce));
musig_pubnonces.push_back(info);
}
}
in.pushKV("musig2_pubnonces", musig_pubnonces);
}
if (!input.m_musig2_partial_sigs.empty()) {
UniValue musig_partial_sigs(UniValue::VARR);
for (const auto& [agg_lh, part_psig] : input.m_musig2_partial_sigs) {
const auto& [agg, lh] = agg_lh;
for (const auto& [part, psig] : part_psig) {
UniValue info(UniValue::VOBJ);
info.pushKV("participant_pubkey", HexStr(part));
info.pushKV("aggregate_pubkey", HexStr(agg));
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
info.pushKV("partial_sig", HexStr(psig));
musig_partial_sigs.push_back(info);
}
}
in.pushKV("musig2_partial_sigs", musig_partial_sigs);
}
// Proprietary // Proprietary
if (!input.m_proprietary.empty()) { if (!input.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR); UniValue proprietary(UniValue::VARR);
@ -1399,6 +1490,22 @@ static RPCHelpMan decodepsbt()
out.pushKV("taproot_bip32_derivs", std::move(keypaths)); out.pushKV("taproot_bip32_derivs", std::move(keypaths));
} }
// Write MuSig2 fields
if (!output.m_musig2_participants.empty()) {
UniValue musig_keys(UniValue::VARR);
for (const auto& [agg, parts] : output.m_musig2_participants) {
UniValue musig_part(UniValue::VOBJ);
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
UniValue part_keys(UniValue::VARR);
for (const auto& pub : parts) {
part_keys.push_back(HexStr(pub));
}
musig_part.pushKV("participant_pubkeys", part_keys);
musig_keys.push_back(musig_part);
}
out.pushKV("musig2_participant_pubkeys", musig_keys);
}
// Proprietary // Proprietary
if (!output.m_proprietary.empty()) { if (!output.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR); UniValue proprietary(UniValue::VARR);
@ -1689,7 +1796,7 @@ static RPCHelpMan utxoupdatepsbt()
request.params[0].get_str(), request.params[0].get_str(),
request.context, request.context,
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false), HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
/*sighash_type=*/SIGHASH_ALL, /*sighash_type=*/std::nullopt,
/*finalize=*/false); /*finalize=*/false);
DataStream ssTx{}; DataStream ssTx{};
@ -1956,7 +2063,7 @@ RPCHelpMan descriptorprocesspsbt()
EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true); EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true);
} }
int sighash_type = ParseSighashString(request.params[2]); std::optional<int> sighash_type = ParseSighashString(request.params[2]);
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool(); bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();

View file

@ -304,12 +304,15 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FlatSigningProvider* keystore,
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result) void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result)
{ {
int nHashType = ParseSighashString(hashType); std::optional<int> nHashType = ParseSighashString(hashType);
if (!nHashType) {
nHashType = SIGHASH_DEFAULT;
}
// Script verification errors // Script verification errors
std::map<int, bilingual_str> input_errors; std::map<int, bilingual_str> input_errors;
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors); bool complete = SignTransaction(mtx, keystore, coins, *nHashType, input_errors);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result); SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
} }

View file

@ -378,10 +378,10 @@ UniValue DescribeAddress(const CTxDestination& dest)
* *
* @pre The sighash argument should be string or null. * @pre The sighash argument should be string or null.
*/ */
int ParseSighashString(const UniValue& sighash) std::optional<int> ParseSighashString(const UniValue& sighash)
{ {
if (sighash.isNull()) { if (sighash.isNull()) {
return SIGHASH_DEFAULT; return std::nullopt;
} }
const auto result{SighashFromStr(sighash.get_str())}; const auto result{SighashFromStr(sighash.get_str())};
if (!result) { if (!result) {

View file

@ -138,7 +138,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
UniValue DescribeAddress(const CTxDestination& dest); UniValue DescribeAddress(const CTxDestination& dest);
/** Parse a sighash string representation and raise an RPC error if it is invalid. */ /** Parse a sighash string representation and raise an RPC error if it is invalid. */
int ParseSighashString(const UniValue& sighash); std::optional<int> ParseSighashString(const UniValue& sighash);
//! Parse a confirm target option and raise an RPC error if it is invalid. //! Parse a confirm target option and raise an RPC error if it is invalid.
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target); unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target);

View file

@ -7,6 +7,7 @@
#include <hash.h> #include <hash.h>
#include <key_io.h> #include <key_io.h>
#include <pubkey.h> #include <pubkey.h>
#include <musig.h>
#include <script/miniscript.h> #include <script/miniscript.h>
#include <script/parsing.h> #include <script/parsing.h>
#include <script/script.h> #include <script/script.h>
@ -174,12 +175,10 @@ public:
* Used by the Miniscript descriptors to check for duplicate keys in the script. * Used by the Miniscript descriptors to check for duplicate keys in the script.
*/ */
bool operator<(PubkeyProvider& other) const { bool operator<(PubkeyProvider& other) const {
CPubKey a, b; FlatSigningProvider dummy;
SigningProvider dummy;
KeyOriginInfo dummy_info;
GetPubKey(0, dummy, a, dummy_info); std::optional<CPubKey> a = GetPubKey(0, dummy, dummy);
other.GetPubKey(0, dummy, b, dummy_info); std::optional<CPubKey> b = other.GetPubKey(0, dummy, dummy);
return a < b; return a < b;
} }
@ -189,7 +188,7 @@ public:
* write_cache is the cache to write keys to (if not nullptr) * write_cache is the cache to write keys to (if not nullptr)
* Caches are not exclusive but this is not tested. Currently we use them exclusively * Caches are not exclusive but this is not tested. Currently we use them exclusively
*/ */
virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const = 0; virtual std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const = 0;
/** Whether this represent multiple public keys at different positions. */ /** Whether this represent multiple public keys at different positions. */
virtual bool IsRange() const = 0; virtual bool IsRange() const = 0;
@ -213,8 +212,8 @@ public:
*/ */
virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const = 0; virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const = 0;
/** Derive a private key, if private data is available in arg. */ /** Derive a private key, if private data is available in arg and put it into out. */
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0; virtual void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const = 0;
/** Return the non-extended public key for this PubkeyProvider, if it has one. */ /** Return the non-extended public key for this PubkeyProvider, if it has one. */
virtual std::optional<CPubKey> GetRootPubKey() const = 0; virtual std::optional<CPubKey> GetRootPubKey() const = 0;
@ -223,6 +222,9 @@ public:
/** Make a deep copy of this PubkeyProvider */ /** Make a deep copy of this PubkeyProvider */
virtual std::unique_ptr<PubkeyProvider> Clone() const = 0; virtual std::unique_ptr<PubkeyProvider> Clone() const = 0;
/** Whether this PubkeyProvider can be a BIP 32 extended key that can be derived from */
virtual bool IsBIP32() const = 0;
}; };
class OriginPubkeyProvider final : public PubkeyProvider class OriginPubkeyProvider final : public PubkeyProvider
@ -240,15 +242,20 @@ class OriginPubkeyProvider final : public PubkeyProvider
public: public:
OriginPubkeyProvider(uint32_t exp_index, KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider, bool apostrophe) : PubkeyProvider(exp_index), m_origin(std::move(info)), m_provider(std::move(provider)), m_apostrophe(apostrophe) {} OriginPubkeyProvider(uint32_t exp_index, KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider, bool apostrophe) : PubkeyProvider(exp_index), m_origin(std::move(info)), m_provider(std::move(provider)), m_apostrophe(apostrophe) {}
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{ {
if (!m_provider->GetPubKey(pos, arg, key, info, read_cache, write_cache)) return false; std::optional<CPubKey> pub = m_provider->GetPubKey(pos, arg, out, read_cache, write_cache);
std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint); if (!pub) return std::nullopt;
info.path.insert(info.path.begin(), m_origin.path.begin(), m_origin.path.end()); Assert(out.pubkeys.contains(pub->GetID()));
return true; auto& [pubkey, suborigin] = out.origins[pub->GetID()];
Assert(pubkey == *pub); // All subproviders must be inserting a valid origin already
std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), suborigin.fingerprint);
suborigin.path.insert(suborigin.path.begin(), m_origin.path.begin(), m_origin.path.end());
return pub;
} }
bool IsRange() const override { return m_provider->IsRange(); } bool IsRange() const override { return m_provider->IsRange(); }
size_t GetSize() const override { return m_provider->GetSize(); } size_t GetSize() const override { return m_provider->GetSize(); }
bool IsBIP32() const override { return m_provider->IsBIP32(); }
std::string ToString(StringType type) const override { return "[" + OriginString(type) + "]" + m_provider->ToString(type); } std::string ToString(StringType type) const override { return "[" + OriginString(type) + "]" + m_provider->ToString(type); }
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
{ {
@ -272,9 +279,9 @@ public:
} }
return true; return true;
} }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
{ {
return m_provider->GetPrivKey(pos, arg, key); m_provider->GetPrivKey(pos, arg, out);
} }
std::optional<CPubKey> GetRootPubKey() const override std::optional<CPubKey> GetRootPubKey() const override
{ {
@ -296,24 +303,34 @@ class ConstPubkeyProvider final : public PubkeyProvider
CPubKey m_pubkey; CPubKey m_pubkey;
bool m_xonly; bool m_xonly;
std::optional<CKey> GetPrivKey(const SigningProvider& arg) const
{
CKey key;
if (!(m_xonly ? arg.GetKeyByXOnly(XOnlyPubKey(m_pubkey), key) :
arg.GetKey(m_pubkey.GetID(), key))) return std::nullopt;
return key;
}
public: public:
ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {} ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {}
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{ {
key = m_pubkey; KeyOriginInfo info;
info.path.clear();
CKeyID keyid = m_pubkey.GetID(); CKeyID keyid = m_pubkey.GetID();
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint); std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
return true; out.origins.emplace(keyid, std::make_pair(m_pubkey, info));
out.pubkeys.emplace(keyid, m_pubkey);
return m_pubkey;
} }
bool IsRange() const override { return false; } bool IsRange() const override { return false; }
size_t GetSize() const override { return m_pubkey.size(); } size_t GetSize() const override { return m_pubkey.size(); }
bool IsBIP32() const override { return false; }
std::string ToString(StringType type) const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); } std::string ToString(StringType type) const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); }
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
{ {
CKey key; std::optional<CKey> key = GetPrivKey(arg);
if (!GetPrivKey(/*pos=*/0, arg, key)) return false; if (!key) return false;
ret = EncodeSecret(key); ret = EncodeSecret(*key);
return true; return true;
} }
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
@ -321,10 +338,11 @@ public:
ret = ToString(StringType::PUBLIC); ret = ToString(StringType::PUBLIC);
return true; return true;
} }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
{ {
return m_xonly ? arg.GetKeyByXOnly(XOnlyPubKey(m_pubkey), key) : std::optional<CKey> key = GetPrivKey(arg);
arg.GetKey(m_pubkey.GetID(), key); if (!key) return;
out.keys.emplace(key->GetPubKey().GetID(), *key);
} }
std::optional<CPubKey> GetRootPubKey() const override std::optional<CPubKey> GetRootPubKey() const override
{ {
@ -394,18 +412,18 @@ public:
BIP32PubkeyProvider(uint32_t exp_index, const CExtPubKey& extkey, KeyPath path, DeriveType derive, bool apostrophe) : PubkeyProvider(exp_index), m_root_extkey(extkey), m_path(std::move(path)), m_derive(derive), m_apostrophe(apostrophe) {} BIP32PubkeyProvider(uint32_t exp_index, const CExtPubKey& extkey, KeyPath path, DeriveType derive, bool apostrophe) : PubkeyProvider(exp_index), m_root_extkey(extkey), m_path(std::move(path)), m_derive(derive), m_apostrophe(apostrophe) {}
bool IsRange() const override { return m_derive != DeriveType::NO; } bool IsRange() const override { return m_derive != DeriveType::NO; }
size_t GetSize() const override { return 33; } size_t GetSize() const override { return 33; }
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key_out, KeyOriginInfo& final_info_out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override bool IsBIP32() const override { return true; }
std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{ {
// Info of parent of the to be derived pubkey // Info of parent of the to be derived pubkey
KeyOriginInfo parent_info; KeyOriginInfo info;
CKeyID keyid = m_root_extkey.pubkey.GetID(); CKeyID keyid = m_root_extkey.pubkey.GetID();
std::copy(keyid.begin(), keyid.begin() + sizeof(parent_info.fingerprint), parent_info.fingerprint); std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
parent_info.path = m_path; info.path = m_path;
// Info of the derived key itself which is copied out upon successful completion // Info of the derived key itself which is copied out upon successful completion
KeyOriginInfo final_info_out_tmp = parent_info; if (m_derive == DeriveType::UNHARDENED) info.path.push_back((uint32_t)pos);
if (m_derive == DeriveType::UNHARDENED) final_info_out_tmp.path.push_back((uint32_t)pos); if (m_derive == DeriveType::HARDENED) info.path.push_back(((uint32_t)pos) | 0x80000000L);
if (m_derive == DeriveType::HARDENED) final_info_out_tmp.path.push_back(((uint32_t)pos) | 0x80000000L);
// Derive keys or fetch them from cache // Derive keys or fetch them from cache
CExtPubKey final_extkey = m_root_extkey; CExtPubKey final_extkey = m_root_extkey;
@ -414,16 +432,16 @@ public:
bool der = true; bool der = true;
if (read_cache) { if (read_cache) {
if (!read_cache->GetCachedDerivedExtPubKey(m_expr_index, pos, final_extkey)) { if (!read_cache->GetCachedDerivedExtPubKey(m_expr_index, pos, final_extkey)) {
if (m_derive == DeriveType::HARDENED) return false; if (m_derive == DeriveType::HARDENED) return std::nullopt;
// Try to get the derivation parent // Try to get the derivation parent
if (!read_cache->GetCachedParentExtPubKey(m_expr_index, parent_extkey)) return false; if (!read_cache->GetCachedParentExtPubKey(m_expr_index, parent_extkey)) return std::nullopt;
final_extkey = parent_extkey; final_extkey = parent_extkey;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos); if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
} }
} else if (IsHardened()) { } else if (IsHardened()) {
CExtKey xprv; CExtKey xprv;
CExtKey lh_xprv; CExtKey lh_xprv;
if (!GetDerivedExtKey(arg, xprv, lh_xprv)) return false; if (!GetDerivedExtKey(arg, xprv, lh_xprv)) return std::nullopt;
parent_extkey = xprv.Neuter(); parent_extkey = xprv.Neuter();
if (m_derive == DeriveType::UNHARDENED) der = xprv.Derive(xprv, pos); if (m_derive == DeriveType::UNHARDENED) der = xprv.Derive(xprv, pos);
if (m_derive == DeriveType::HARDENED) der = xprv.Derive(xprv, pos | 0x80000000UL); if (m_derive == DeriveType::HARDENED) der = xprv.Derive(xprv, pos | 0x80000000UL);
@ -433,16 +451,16 @@ public:
} }
} else { } else {
for (auto entry : m_path) { for (auto entry : m_path) {
if (!parent_extkey.Derive(parent_extkey, entry)) return false; if (!parent_extkey.Derive(parent_extkey, entry)) return std::nullopt;
} }
final_extkey = parent_extkey; final_extkey = parent_extkey;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos); if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
assert(m_derive != DeriveType::HARDENED); assert(m_derive != DeriveType::HARDENED);
} }
if (!der) return false; if (!der) return std::nullopt;
final_info_out = final_info_out_tmp; out.origins.emplace(final_extkey.pubkey.GetID(), std::make_pair(final_extkey.pubkey, info));
key_out = final_extkey.pubkey; out.pubkeys.emplace(final_extkey.pubkey.GetID(), final_extkey.pubkey);
if (write_cache) { if (write_cache) {
// Only cache parent if there is any unhardened derivation // Only cache parent if there is any unhardened derivation
@ -452,12 +470,12 @@ public:
if (last_hardened_extkey.pubkey.IsValid()) { if (last_hardened_extkey.pubkey.IsValid()) {
write_cache->CacheLastHardenedExtPubKey(m_expr_index, last_hardened_extkey); write_cache->CacheLastHardenedExtPubKey(m_expr_index, last_hardened_extkey);
} }
} else if (final_info_out.path.size() > 0) { } else if (info.path.size() > 0) {
write_cache->CacheDerivedExtPubKey(m_expr_index, pos, final_extkey); write_cache->CacheDerivedExtPubKey(m_expr_index, pos, final_extkey);
} }
} }
return true; return final_extkey.pubkey;
} }
std::string ToString(StringType type, bool normalized) const std::string ToString(StringType type, bool normalized) const
{ {
@ -543,15 +561,14 @@ public:
} }
return true; return true;
} }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
{ {
CExtKey extkey; CExtKey extkey;
CExtKey dummy; CExtKey dummy;
if (!GetDerivedExtKey(arg, extkey, dummy)) return false; if (!GetDerivedExtKey(arg, extkey, dummy)) return;
if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return false; if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return;
if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return false; if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return;
key = extkey.key; out.keys.emplace(extkey.key.GetPubKey().GetID(), extkey.key);
return true;
} }
std::optional<CPubKey> GetRootPubKey() const override std::optional<CPubKey> GetRootPubKey() const override
{ {
@ -567,6 +584,219 @@ public:
} }
}; };
/** PubkeyProvider for a musig() expression */
class MuSigPubkeyProvider final : public PubkeyProvider
{
private:
//! PubkeyProvider for the participants
const std::vector<std::unique_ptr<PubkeyProvider>> m_participants;
//! Derivation path if this is ranged
const KeyPath m_path;
//! PubkeyProvider for the aggregate pubkey if it can be cached (i.e. participants are not ranged)
mutable std::unique_ptr<PubkeyProvider> m_aggregate_provider;
mutable std::optional<CPubKey> m_aggregate_pubkey;
const DeriveType m_derive;
bool IsRangedDerivation() const { return m_derive != DeriveType::NO; }
bool IsRangedParticipants() const
{
for (const auto& pubkey : m_participants) {
if (pubkey->IsRange()) return true;
}
return false;
}
public:
MuSigPubkeyProvider(
uint32_t exp_index,
std::vector<std::unique_ptr<PubkeyProvider>> providers,
KeyPath path,
DeriveType derive
)
: PubkeyProvider(exp_index),
m_participants(std::move(providers)),
m_path(std::move(path)),
m_derive(derive)
{}
std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{
// If the participants are not ranged, we can compute and cache the aggregate pubkey by creating a PubkeyProvider for it
if (!m_aggregate_provider && !IsRangedParticipants()) {
// Retrieve the pubkeys from the providers
std::vector<CPubKey> pubkeys;
for (const auto& prov : m_participants) {
FlatSigningProvider dummy;
std::optional<CPubKey> pubkey = prov->GetPubKey(0, arg, dummy, read_cache, write_cache);
if (!pubkey.has_value()) {
return std::nullopt;
}
pubkeys.push_back(pubkey.value());
}
std::sort(pubkeys.begin(), pubkeys.end());
// Aggregate the pubkey
m_aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
Assert(m_aggregate_pubkey.has_value());
// Make our pubkey provider
if (m_derive != DeriveType::NO || !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 = uint256::FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086").value();
extpub.pubkey = 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);
}
}
// Retrieve all participant pubkeys
std::vector<CPubKey> pubkeys;
for (const auto& prov : m_participants) {
std::optional<CPubKey> pub = prov->GetPubKey(pos, arg, out, read_cache, write_cache);
if (!pub) return std::nullopt;
pubkeys.emplace_back(*pub);
}
std::sort(pubkeys.begin(), pubkeys.end());
CPubKey pubout;
if (m_aggregate_provider) {
// When we have a cached aggregate key, we are either returning it or deriving from it
// Either way, we can passthrough to it's GetPubKey
std::optional<CPubKey> pub = m_aggregate_provider->GetPubKey(pos, arg, out, read_cache, write_cache);
if (!pub) return std::nullopt;
pubout = *pub;
out.aggregate_pubkeys.emplace(m_aggregate_pubkey.value(), pubkeys);
} else if (IsRangedParticipants()) {
// Derive participants and compute new aggregate key
std::optional<CPubKey> aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
if (!aggregate_pubkey) return std::nullopt;
pubout = *aggregate_pubkey;
KeyOriginInfo info;
CKeyID keyid = aggregate_pubkey->GetID();
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
out.origins.emplace(keyid, std::make_pair(*aggregate_pubkey, info));
out.pubkeys.emplace(aggregate_pubkey->GetID(), *aggregate_pubkey);
out.aggregate_pubkeys.emplace(pubout, pubkeys);
}
Assert(pubout.IsValid());
return pubout;
}
bool IsRange() const override { return IsRangedDerivation() || IsRangedParticipants(); }
// musig() expressions can only be used in tr() contexts which have 32 byte xonly pubkeys
size_t GetSize() const override { return 32; }
std::string ToString(StringType type=StringType::PUBLIC) const override
{
std::string out = "musig(";
for (size_t i = 0; i < m_participants.size(); ++i) {
const auto& pubkey = m_participants.at(i);
if (i) out += ",";
std::string tmp;
switch (type) {
case StringType::PUBLIC:
tmp = pubkey->ToString();
break;
case StringType::COMPAT:
tmp = pubkey->ToString(PubkeyProvider::StringType::COMPAT);
break;
}
out += tmp;
}
out += ")";
out += FormatHDKeypath(m_path, /*apostrophe=*/true);
if (IsRangedDerivation()) {
out += "/*";
}
return out;
}
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
bool any_privkeys = false;
out = "musig(";
for (size_t i = 0; i < m_participants.size(); ++i) {
const auto& pubkey = m_participants.at(i);
if (i) out += ",";
std::string tmp;
if (pubkey->ToPrivateString(arg, tmp)) {
any_privkeys = true;
out += tmp;
} else {
out += pubkey->ToString();
}
}
out += ")";
out += FormatHDKeypath(m_path, /*apostrophe=*/true);
if (IsRangedDerivation()) {
out += "/*";
}
if (!any_privkeys) out.clear();
return any_privkeys;
}
bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const override
{
out = "musig(";
for (size_t i = 0; i < m_participants.size(); ++i) {
const auto& pubkey = m_participants.at(i);
if (i) out += ",";
std::string tmp;
if (!pubkey->ToNormalizedString(arg, tmp)) {
return false;
}
out += tmp;
}
out += ")";
out += FormatHDKeypath(m_path, /*apostrophe=*/true);
if (IsRangedDerivation()) {
out += "/*";
}
return true;
}
void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
{
// Get the private keys for all participants
// If there is participant derivation, it will be done.
// If there is not, then the participant privkeys will be included directly
for (const auto& prov : m_participants) {
prov->GetPrivKey(pos, arg, out);
}
}
std::optional<CPubKey> GetRootPubKey() const override
{
return std::nullopt;
}
std::optional<CExtPubKey> GetRootExtPubKey() const override
{
return std::nullopt;
}
std::unique_ptr<PubkeyProvider> Clone() const override
{
std::vector<std::unique_ptr<PubkeyProvider>> providers;
providers.reserve(m_participants.size());
for (const std::unique_ptr<PubkeyProvider>& p : m_participants) {
providers.emplace_back(p->Clone());
}
return std::make_unique<MuSigPubkeyProvider>(m_expr_index, std::move(providers), m_path, m_derive);
}
bool IsBIP32() const override
{
// musig() can only be a BIP 32 key if all participants are bip32 too
bool all_bip32 = true;
for (const auto& pk : m_participants) {
all_bip32 &= pk->IsBIP32();
}
return all_bip32;
}
};
/** Base class for all Descriptor implementations. */ /** Base class for all Descriptor implementations. */
class DescriptorImpl : public Descriptor class DescriptorImpl : public Descriptor
{ {
@ -700,16 +930,17 @@ public:
// NOLINTNEXTLINE(misc-no-recursion) // NOLINTNEXTLINE(misc-no-recursion)
bool ExpandHelper(int pos, const SigningProvider& arg, const DescriptorCache* read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache) const bool ExpandHelper(int pos, const SigningProvider& arg, const DescriptorCache* read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache) const
{ {
std::vector<std::pair<CPubKey, KeyOriginInfo>> entries; FlatSigningProvider subprovider;
entries.reserve(m_pubkey_args.size()); std::vector<CPubKey> pubkeys;
pubkeys.reserve(m_pubkey_args.size());
// Construct temporary data in `entries`, `subscripts`, and `subprovider` to avoid producing output in case of failure. // Construct temporary data in `pubkeys`, `subscripts`, and `subprovider` to avoid producing output in case of failure.
for (const auto& p : m_pubkey_args) { for (const auto& p : m_pubkey_args) {
entries.emplace_back(); std::optional<CPubKey> pubkey = p->GetPubKey(pos, arg, subprovider, read_cache, write_cache);
if (!p->GetPubKey(pos, arg, entries.back().first, entries.back().second, read_cache, write_cache)) return false; if (!pubkey) return false;
pubkeys.push_back(pubkey.value());
} }
std::vector<CScript> subscripts; std::vector<CScript> subscripts;
FlatSigningProvider subprovider;
for (const auto& subarg : m_subdescriptor_args) { for (const auto& subarg : m_subdescriptor_args) {
std::vector<CScript> outscripts; std::vector<CScript> outscripts;
if (!subarg->ExpandHelper(pos, arg, read_cache, outscripts, subprovider, write_cache)) return false; if (!subarg->ExpandHelper(pos, arg, read_cache, outscripts, subprovider, write_cache)) return false;
@ -718,13 +949,6 @@ public:
} }
out.Merge(std::move(subprovider)); out.Merge(std::move(subprovider));
std::vector<CPubKey> pubkeys;
pubkeys.reserve(entries.size());
for (auto& entry : entries) {
pubkeys.push_back(entry.first);
out.origins.emplace(entry.first.GetID(), std::make_pair<CPubKey, KeyOriginInfo>(CPubKey(entry.first), std::move(entry.second)));
}
output_scripts = MakeScripts(pubkeys, Span{subscripts}, out); output_scripts = MakeScripts(pubkeys, Span{subscripts}, out);
return true; return true;
} }
@ -744,8 +968,7 @@ public:
{ {
for (const auto& p : m_pubkey_args) { for (const auto& p : m_pubkey_args) {
CKey key; CKey key;
if (!p->GetPrivKey(pos, provider, key)) continue; p->GetPrivKey(pos, provider, out);
out.keys.emplace(key.GetPubKey().GetID(), key);
} }
for (const auto& arg : m_subdescriptor_args) { for (const auto& arg : m_subdescriptor_args) {
arg->ExpandPrivate(pos, provider, out); arg->ExpandPrivate(pos, provider, out);
@ -800,6 +1023,7 @@ public:
return OutputTypeFromDestination(m_destination); return OutputTypeFromDestination(m_destination);
} }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; } bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); } std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); }
@ -827,6 +1051,7 @@ public:
return OutputTypeFromDestination(dest); return OutputTypeFromDestination(dest);
} }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; } bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
std::optional<int64_t> ScriptSize() const override { return m_script.size(); } std::optional<int64_t> ScriptSize() const override { return m_script.size(); }
@ -855,6 +1080,7 @@ protected:
public: public:
PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {} PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return true; }
std::optional<int64_t> ScriptSize() const override { std::optional<int64_t> ScriptSize() const override {
return 1 + (m_xonly ? 32 : m_pubkey_args[0]->GetSize()) + 1; return 1 + (m_xonly ? 32 : m_pubkey_args[0]->GetSize()) + 1;
@ -884,13 +1110,13 @@ protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
{ {
CKeyID id = keys[0].GetID(); CKeyID id = keys[0].GetID();
out.pubkeys.emplace(id, keys[0]);
return Vector(GetScriptForDestination(PKHash(id))); return Vector(GetScriptForDestination(PKHash(id)));
} }
public: public:
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {} PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; } std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return true; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 1 + 20 + 1 + 1; } std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 1 + 20 + 1 + 1; }
@ -918,13 +1144,13 @@ protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
{ {
CKeyID id = keys[0].GetID(); CKeyID id = keys[0].GetID();
out.pubkeys.emplace(id, keys[0]);
return Vector(GetScriptForDestination(WitnessV0KeyHash(id))); return Vector(GetScriptForDestination(WitnessV0KeyHash(id)));
} }
public: public:
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {} WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return true; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; } std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; }
@ -953,7 +1179,6 @@ protected:
{ {
std::vector<CScript> ret; std::vector<CScript> ret;
CKeyID id = keys[0].GetID(); CKeyID id = keys[0].GetID();
out.pubkeys.emplace(id, keys[0]);
ret.emplace_back(GetScriptForRawPubKey(keys[0])); // P2PK ret.emplace_back(GetScriptForRawPubKey(keys[0])); // P2PK
ret.emplace_back(GetScriptForDestination(PKHash(id))); // P2PKH ret.emplace_back(GetScriptForDestination(PKHash(id))); // P2PKH
if (keys[0].IsCompressed()) { if (keys[0].IsCompressed()) {
@ -971,6 +1196,7 @@ public:
{ {
return std::make_unique<ComboDescriptor>(m_pubkey_args.at(0)->Clone()); return std::make_unique<ComboDescriptor>(m_pubkey_args.at(0)->Clone());
} }
bool IsSingleKey() const final { return true; }
}; };
/** A parsed multi(...) or sortedmulti(...) descriptor */ /** A parsed multi(...) or sortedmulti(...) descriptor */
@ -991,6 +1217,7 @@ protected:
public: public:
MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {} MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size(); const auto n_keys = m_pubkey_args.size();
@ -1042,6 +1269,7 @@ protected:
public: public:
MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {} MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size(); const auto n_keys = m_pubkey_args.size();
@ -1088,6 +1316,7 @@ public:
return OutputType::LEGACY; return OutputType::LEGACY;
} }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return m_subdescriptor_args[0]->IsSingleKey(); }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20 + 1; } std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20 + 1; }
@ -1129,6 +1358,7 @@ public:
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {} WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return m_subdescriptor_args[0]->IsSingleKey(); }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; } std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
@ -1175,7 +1405,6 @@ protected:
builder.Finalize(xpk); builder.Finalize(xpk);
WitnessV1Taproot output = builder.GetOutput(); WitnessV1Taproot output = builder.GetOutput();
out.tr_trees[output] = builder; out.tr_trees[output] = builder;
out.pubkeys.emplace(keys[0].GetID(), keys[0]);
return Vector(GetScriptForDestination(output)); return Vector(GetScriptForDestination(output));
} }
bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override
@ -1207,6 +1436,7 @@ public:
} }
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; } std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; } std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
@ -1334,6 +1564,7 @@ public:
bool IsSolvable() const override { return true; } bool IsSolvable() const override { return true; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); } std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); }
@ -1373,6 +1604,7 @@ public:
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {} RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; } std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; } std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
@ -1402,14 +1634,19 @@ enum class ParseScriptContext {
P2WPKH, //!< Inside wpkh() (no script, pubkey only) P2WPKH, //!< Inside wpkh() (no script, pubkey only)
P2WSH, //!< Inside wsh() (script becomes v0 witness script) P2WSH, //!< Inside wsh() (script becomes v0 witness script)
P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf) P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
MUSIG, //!< Inside musig() (implies P2TR, cannot have nested musig())
}; };
std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe, std::string& error) std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe, std::string& error, bool allow_hardened)
{ {
bool hardened = false; bool hardened = false;
if (elem.size() > 0) { if (elem.size() > 0) {
const char last = elem[elem.size() - 1]; const char last = elem[elem.size() - 1];
if (last == '\'' || last == 'h') { if (last == '\'' || last == 'h') {
if (!allow_hardened) {
error = "cannot have hardened derivation steps";
return std::nullopt;
}
elem = elem.first(elem.size() - 1); elem = elem.first(elem.size() - 1);
hardened = true; hardened = true;
apostrophe = last == '\''; apostrophe = last == '\'';
@ -1437,7 +1674,7 @@ std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe,
* @param[in] allow_multipath Allows the parsed path to use the multipath specifier * @param[in] allow_multipath Allows the parsed path to use the multipath specifier
* @returns false if parsing failed * @returns false if parsing failed
**/ **/
[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath) [[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath, bool allow_hardened = true)
{ {
KeyPath path; KeyPath path;
std::optional<size_t> multipath_segment_index; std::optional<size_t> multipath_segment_index;
@ -1466,7 +1703,7 @@ std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe,
} }
for (const auto& num : nums) { for (const auto& num : nums) {
const auto& op_num = ParseKeyPathNum(num, apostrophe, error); const auto& op_num = ParseKeyPathNum(num, apostrophe, error, allow_hardened);
if (!op_num) return false; if (!op_num) return false;
auto [_, inserted] = seen_multipath.insert(*op_num); auto [_, inserted] = seen_multipath.insert(*op_num);
if (!inserted) { if (!inserted) {
@ -1479,7 +1716,7 @@ std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe,
path.emplace_back(); // Placeholder for multipath segment path.emplace_back(); // Placeholder for multipath segment
multipath_segment_index = path.size()-1; multipath_segment_index = path.size()-1;
} else { } else {
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error); const auto& op_num = ParseKeyPathNum(elem, apostrophe, error, allow_hardened);
if (!op_num) return false; if (!op_num) return false;
path.emplace_back(*op_num); path.emplace_back(*op_num);
} }
@ -1578,9 +1815,152 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
} }
/** Parse a public key including origin information (if enabled). */ /** Parse a public key including origin information (if enabled). */
std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) // NOLINTNEXTLINE(misc-no-recursion)
std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t& key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
{ {
std::vector<std::unique_ptr<PubkeyProvider>> ret; std::vector<std::unique_ptr<PubkeyProvider>> ret;
using namespace script;
// musig cannot be nested inside of an origin
Span<const char> span = sp;
if (Const("musig(", span, /*skip=*/false)) {
if (ctx != ParseScriptContext::P2TR) {
error = "musig() is only allowed in tr()";
return {};
}
auto split = Split(span, ')');
if (split.size() > 2) {
error = "Too many ')' in musig() expression";
return {};
}
// Make a span that includes the end parentheses so that Expr is happy
Span<const char> sp_musig(split.at(0).begin(), split.at(0).end() + 1);
auto expr = Expr(sp_musig);
if (!Func("musig", expr)) {
error = "Invalid musig() expression";
return {};
}
// Parse the participant pubkeys
bool any_ranged = false;
bool all_bip32 = true;
std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> providers;
bool first = true;
size_t max_providers_len = 0;
while (expr.size()) {
if (!first && !Const(",", expr)) {
error = strprintf("musig(): expected ',', got '%c'", expr[0]);
return {};
}
first = 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_ranged |= pk.at(0)->IsRange();
all_bip32 &= pk.at(0)->IsBIP32();
max_providers_len = std::max(max_providers_len, pk.size());
providers.emplace_back(std::move(pk));
key_exp_index++;
}
if (first) {
error = "musig(): Must contain key expressions";
return {};
}
// Parse any derivation
DeriveType deriv_type = DeriveType::NO;
std::vector<KeyPath> paths;
if (split.size() == 2 && Const("/", split.at(1), /*skip=*/false)) {
if (!all_bip32) {
error = "musig(): Ranged musig() requires all participants to be xpubs";
return {};
}
auto deriv_split = Split(split.at(1), '/');
if (std::ranges::equal(deriv_split.back(), Span{"*"}.first(1))) {
deriv_split.pop_back();
deriv_type = DeriveType::UNHARDENED;
if (any_ranged) {
error = "musig(): Cannot have ranged participant keys if musig() is also ranged";
return {};
}
} else if (std::ranges::equal(deriv_split.back(), Span{"*'"}.first(2)) || std::ranges::equal(deriv_split.back(), Span{"*h"}.first(2))) {
error = "musig(): Cannot have hardened child derivation";
return {};
}
bool dummy = false;
if (!ParseKeyPath(deriv_split, paths, dummy, error, /*allow_multipath=*/true, /*allow_hardened=*/false)) {
error = "musig(): " + error;
return {};
}
} else {
paths.emplace_back();
}
// Makes sure that all providers vectors in providers are the given length, or exactly length 1
// Length 1 vectors have the single provider cloned until it matches the given length.
const auto& clone_providers = [&providers](size_t length) -> bool {
for (auto& vec : providers) {
if (vec.size() == 1) {
for (size_t i = 1; i < length; ++i) {
vec.emplace_back(vec.at(0)->Clone());
}
} else if (vec.size() != length) {
return false;
}
}
return true;
};
// Emplace the final MuSigPubkeyProvider into ret with the pubkey providers from the specified provider vectors index
// and the path from the specified path index
const auto& emplace_final_provider = [&ret, &key_exp_index, &deriv_type, &paths, &providers](size_t vec_idx, size_t path_idx) -> void {
KeyPath& path = paths.at(path_idx);
std::vector<std::unique_ptr<PubkeyProvider>> pubs;
pubs.reserve(providers.size());
for (auto& vec : providers) {
pubs.emplace_back(std::move(vec.at(vec_idx)));
}
ret.emplace_back(std::make_unique<MuSigPubkeyProvider>(key_exp_index, std::move(pubs), path, deriv_type));
};
if (max_providers_len > 1 && paths.size() > 1) {
error = "musig(): Cannot have multipath participant keys if musig() is also multipath";
return {};
} else if (max_providers_len > 1) {
if (!clone_providers(max_providers_len)) {
error = strprintf("musig(): Multipath derivation paths have mismatched lengths");
return {};
}
for (size_t i = 0; i < max_providers_len; ++i) {
// Final MuSigPubkeyProvider use participant pubkey providers at each multipath position, and the first (and only) path
emplace_final_provider(i, 0);
}
} else if (paths.size() > 1) {
// All key provider vectors should be length 1. Clone them until they have the same length as paths
if (!clone_providers(paths.size())) {
error = "musig(): Multipath derivation path with multipath participants is disallowed"; // This error is unreachable due to earlier check
return {};
}
for (size_t i = 0; i < paths.size(); ++i) {
// Final MuSigPubkeyProvider uses cloned participant pubkey providers, and the multipath derivation paths
emplace_final_provider(i, i);
}
} else {
// No multipath derivation MuSigPubkeyProvider uses the first (and only) participant pubkey providers, and the first (and only) path
emplace_final_provider(0, 0);
}
return ret;
}
auto origin_split = Split(sp, ']'); auto origin_split = Split(sp, ']');
if (origin_split.size() > 2) { if (origin_split.size() > 2) {
error = "Multiple ']' characters found for a single pubkey"; error = "Multiple ']' characters found for a single pubkey";
@ -1691,7 +2071,8 @@ struct KeyParser {
{ {
assert(m_out); assert(m_out);
Key key = m_keys.size(); Key key = m_keys.size();
auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error); uint32_t exp_index = m_offset + key;
auto pk = ParsePubkey(exp_index, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
if (pk.empty()) return {}; if (pk.empty()) return {};
m_keys.emplace_back(std::move(pk)); m_keys.emplace_back(std::move(pk));
return key; return key;

View file

@ -111,6 +111,11 @@ struct Descriptor {
/** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */ /** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
virtual bool IsSingleType() const = 0; virtual bool IsSingleType() const = 0;
/** Whether this descriptor only produces single key scripts (i.e. pk(), pkh(), wpkh(), sh() and wsh() nested of those, and combo()
* TODO: Remove this method once legacy wallets are removed as it is only necessary for importmulti.
*/
virtual bool IsSingleKey() const = 0;
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */ /** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0; virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;

View file

@ -12,10 +12,10 @@
namespace script { namespace script {
bool Const(const std::string& str, Span<const char>& sp) bool Const(const std::string& str, Span<const char>& sp, bool skip)
{ {
if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) { if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
sp = sp.subspan(str.size()); if (skip) sp = sp.subspan(str.size());
return true; return true;
} }
return false; return false;

View file

@ -13,10 +13,10 @@ namespace script {
/** Parse a constant. /** Parse a constant.
* *
* If sp's initial part matches str, sp is updated to skip that part, and true is returned. * If sp's initial part matches str, sp is optionally updated to skip that part, and true is returned.
* Otherwise sp is unmodified and false is returned. * Otherwise sp is unmodified and false is returned.
*/ */
bool Const(const std::string& str, Span<const char>& sp); bool Const(const std::string& str, Span<const char>& sp, bool skip = true);
/** Parse a function call. /** Parse a function call.
* *

View file

@ -238,6 +238,13 @@ bool CScript::IsPayToWitnessScriptHash() const
(*this)[1] == 0x20); (*this)[1] == 0x20);
} }
bool CScript::IsPayToTaproot() const
{
return (this->size() == 34 &&
(*this)[0] == OP_1 &&
(*this)[1] == 0x20);
}
// A witness program is any valid CScript that consists of a 1-byte push opcode // A witness program is any valid CScript that consists of a 1-byte push opcode
// followed by a data push between 2 and 40 bytes. // followed by a data push between 2 and 40 bytes.
bool CScript::IsWitnessProgram(int& version, std::vector<unsigned char>& program) const bool CScript::IsWitnessProgram(int& version, std::vector<unsigned char>& program) const

View file

@ -556,6 +556,8 @@ public:
bool IsPayToWitnessScriptHash() const; bool IsPayToWitnessScriptHash() const;
bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const; bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const;
bool IsPayToTaproot() const;
/** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */
bool IsPushOnly(const_iterator pc) const; bool IsPushOnly(const_iterator pc) const;
bool IsPushOnly() const; bool IsPushOnly() const;

View file

@ -7,8 +7,10 @@
#include <consensus/amount.h> #include <consensus/amount.h>
#include <key.h> #include <key.h>
#include <musig.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <random.h>
#include <script/keyorigin.h> #include <script/keyorigin.h>
#include <script/miniscript.h> #include <script/miniscript.h>
#include <script/script.h> #include <script/script.h>
@ -59,17 +61,14 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
return true; 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); 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 // BIP341/BIP342 signing needs lots of precomputed transaction data. While some
// (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset // (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. // 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; ScriptExecutionData execdata;
execdata.m_annex_init = true; execdata.m_annex_init = true;
@ -77,19 +76,134 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider&
if (sigversion == SigVersion::TAPSCRIPT) { if (sigversion == SigVersion::TAPSCRIPT) {
execdata.m_codeseparator_pos_init = true; execdata.m_codeseparator_pos_init = true;
execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now. 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_init = true;
execdata.m_tapleaf_hash = *leaf_hash; execdata.m_tapleaf_hash = *leaf_hash;
} }
uint256 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); sig.resize(64);
// Use uint256{} as aux_rnd for now. // 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); if (nHashType) sig.push_back(nHashType);
return true; 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;
}
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
std::vector<CPubKey> pubkeys = provider.GetAggregateParticipantPubkeys(aggregate_pubkey);
if (!pubkeys.size()) return false;
// 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
HashWriter hasher;
hasher << script_pubkey << part_pubkey << *sighash;
uint256 session_id = hasher.GetSHA256();
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
provider.DeleteMuSig2Session(session_id);
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
std::vector<CPubKey> pubkeys = provider.GetAggregateParticipantPubkeys(aggregate_pubkey);
if (!pubkeys.size()) 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
HashWriter hasher;
hasher << script_pubkey << part_pubkey << *sighash;
uint256 id = hasher.GetSHA256();
provider.SetMuSig2SecNonce(id, std::move(secnonce));
return out;
}
static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script) static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script)
{ {
if (provider.GetCScript(scriptid, script)) { if (provider.GetCScript(scriptid, script)) {
@ -169,11 +283,93 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur
sig_out = it->second; sig_out = it->second;
return true; return true;
} }
if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) {
sigdata.taproot_script_sigs[lookup_key] = sig_out; sigdata.taproot_script_sigs[lookup_key] = sig_out;
return true; } else {
auto misc_pk_it = sigdata.taproot_misc_pubkeys.find(pubkey);
if (misc_pk_it != sigdata.taproot_misc_pubkeys.end()) {
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()) {
sigdata.taproot_misc_pubkeys.emplace(xonly_part, std::make_pair(std::set<uint256>({leaf_hash}), part_info));
} else {
it->second.first.insert(leaf_hash);
}
}
}
std::vector<std::pair<uint256, bool>> tweaks;
CPubKey plain_pub = agg_pub;
if (XOnlyPubKey(agg_pub) != pubkey) {
if (info.path.size() > 0) {
// Compute and compare fingerprint
CKeyID keyid = agg_pub.GetID();
if (std::memcmp(keyid.data(), info.fingerprint, sizeof(info.fingerprint)) != 0) {
continue;
}
// Get the BIP32 derivation tweaks
CExtPubKey extpub;
extpub.nDepth = 0;
std::memset(extpub.vchFingerprint, 0, 4);
extpub.nChild = 0;
extpub.chaincode = uint256::FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086").value();
extpub.pubkey = agg_pub;
for (const int i : info.path) {
auto& [tweak, xonly] = tweaks.emplace_back();
xonly = false;
if (!extpub.Derive(extpub, i, tweak)) {
return false;
}
}
Assert(XOnlyPubKey(extpub.pubkey) == pubkey);
plain_pub = extpub.pubkey;
} else {
continue;
}
}
// We know this is musig, try musig signing
// First try to aggregate
if (creator.CreateMuSig2AggregateSig(part_pks, sig_out, agg_pub, plain_pub, &leaf_hash, tweaks, sigversion, sigdata)) {
sigdata.taproot_script_sigs[lookup_key] = sig_out;
return true;
}
// Cannot aggregate, try making partial sigs for every participant
auto pub_key_leaf_hash = std::make_pair(plain_pub, leaf_hash);
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()) {
return false;
}
// 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, nullptr, sigversion, sigdata);
if (pubnonce.empty()) continue;
pubnonces[part_pk] = std::move(pubnonce);
}
}
} }
return false;
return sigdata.taproot_script_sigs.contains(lookup_key);
} }
template<typename M, typename K, typename V> template<typename M, typename K, typename V>
@ -342,27 +538,133 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea
if (provider.GetTaprootBuilder(output, builder)) { if (provider.GetTaprootBuilder(output, builder)) {
sigdata.tr_builder = builder; sigdata.tr_builder = builder;
} }
if (auto agg_keys = provider.GetAllAggregateParticipantPubkeys(); !agg_keys.empty()) {
sigdata.musig2_pubkeys.merge(agg_keys);
}
// Try key path spending. // Try key path spending.
{ {
KeyOriginInfo info; KeyOriginInfo internal_key_info;
if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.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); auto it = sigdata.taproot_misc_pubkeys.find(sigdata.tr_spenddata.internal_key);
if (it == sigdata.taproot_misc_pubkeys.end()) { 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; KeyOriginInfo output_key_info;
if (sigdata.taproot_key_path_sig.size() == 0) { if (provider.GetKeyOriginByXOnly(output, output_key_info)) {
if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.merkle_root, SigVersion::TAPROOT)) { auto it = sigdata.taproot_misc_pubkeys.find(output);
sigdata.taproot_key_path_sig = sig; 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; sigdata.taproot_key_path_sig = sig;
} else {
// Lookup derivation paths for this key
KeyOriginInfo info;
auto misc_pk_it = sigdata.taproot_misc_pubkeys.find(pk);
if (misc_pk_it != sigdata.taproot_misc_pubkeys.end()) {
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 info;
if (provider.GetKeyOrigin(part_pk.GetID(), info)) {
XOnlyPubKey xonly_part(part_pk);
auto it = sigdata.taproot_misc_pubkeys.find(xonly_part);
if (it == sigdata.taproot_misc_pubkeys.end()) {
sigdata.taproot_misc_pubkeys.emplace(xonly_part, std::make_pair(std::set<uint256>(), info));
}
}
}
std::vector<std::pair<uint256, bool>> tweaks;
CPubKey plain_pub = agg_pub;
if (XOnlyPubKey(agg_pub) != pk) {
if (info.path.size() > 0) {
// Compute and compare fingerprint
CKeyID keyid = agg_pub.GetID();
if (!std::equal(info.fingerprint, info.fingerprint + sizeof(info.fingerprint), keyid.data())) {
continue;
}
// Get the BIP32 derivation tweaks
CExtPubKey extpub;
extpub.nDepth = 0;
std::memset(extpub.vchFingerprint, 0, 4);
extpub.nChild = 0;
extpub.chaincode = uint256::FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086").value();
extpub.pubkey = agg_pub;
for (const int i : info.path) {
auto& [t, xonly] = tweaks.emplace_back();
xonly = false;
if (!extpub.Derive(extpub, i, t)) {
return;
}
}
Assert(XOnlyPubKey(extpub.pubkey) == pk);
plain_pub = extpub.pubkey;
} else {
continue;
}
}
// Add the merkle root tweak
if (merkle_root) {
tweaks.emplace_back(pk.ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root), true);
std::optional<std::pair<XOnlyPubKey, bool>> tweaked = pk.CreateTapTweak(merkle_root->IsNull() ? nullptr : merkle_root);
if (!Assume(tweaked)) return;
plain_pub = tweaked->first.GetCPubKeys().at(tweaked->second ? 1 : 0);
}
// We know this is musig, try musig signing
// First try to aggregate
if (creator.CreateMuSig2AggregateSig(part_pks, sig, agg_pub, plain_pub, nullptr, tweaks, SigVersion::TAPROOT, sigdata)) {
sigdata.taproot_key_path_sig = sig;
return;
}
// Cannot aggregate, try making partial sigs for every participant
auto pub_key_leaf_hash = std::make_pair(plain_pub, uint256());
for (const CPubKey& part_pk : part_pks) {
uint256 partial_sig;
if (creator.CreateMuSig2PartialSig(provider, partial_sig, agg_pub, plain_pub, part_pk, nullptr, tweaks, SigVersion::TAPROOT, 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()) {
return;
}
// 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, nullptr, merkle_root, SigVersion::TAPROOT, sigdata);
if (pubnonce.empty()) continue;
pubnonces[part_pk] = std::move(pubnonce);
}
break;
}
} }
};
// 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()) { if (sigdata.taproot_key_path_sig.size()) {
result = Vector(sigdata.taproot_key_path_sig); result = Vector(sigdata.taproot_key_path_sig);
@ -737,6 +1039,22 @@ public:
sig.assign(64, '\000'); sig.assign(64, '\000');
return true; 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;
}
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;
}
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(66, '\000');
return out;
}
}; };
} }

View file

@ -23,6 +23,7 @@ class SigningProvider;
struct bilingual_str; struct bilingual_str;
struct CMutableTransaction; struct CMutableTransaction;
struct SignatureData;
/** Interface for signature creators. */ /** Interface for signature creators. */
class BaseSignatureCreator { class BaseSignatureCreator {
@ -33,6 +34,9 @@ public:
/** Create a singular (non-script) signature. */ /** 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 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 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 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;
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 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;
}; };
/** A signature creator for transactions. */ /** A signature creator for transactions. */
@ -45,12 +49,17 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator
const MutableTransactionSignatureChecker checker; const MutableTransactionSignatureChecker checker;
const PrecomputedTransactionData* m_txdata; const PrecomputedTransactionData* m_txdata;
std::optional<uint256> ComputeSchnorrSignatureHash(const uint256* leaf_hash, SigVersion sigversion) const;
public: 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, int hash_type);
MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, 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; } 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 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; 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;
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;
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;
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;
}; };
/** A signature checker that accepts all signatures */ /** A signature checker that accepts all signatures */
@ -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>> 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>> 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 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; SignatureData() = default;
explicit SignatureData(const CScript& script) : scriptSig(script) {} explicit SignatureData(const CScript& script) : scriptSig(script) {}

View file

@ -52,6 +52,31 @@ bool HidingSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, Tap
{ {
return m_provider->GetTaprootBuilder(output_key, builder); return m_provider->GetTaprootBuilder(output_key, builder);
} }
std::vector<CPubKey> HidingSigningProvider::GetAggregateParticipantPubkeys(const CPubKey& pubkey) const
{
if (m_hide_origin) return {};
return m_provider->GetAggregateParticipantPubkeys(pubkey);
}
std::map<CPubKey, std::vector<CPubKey>> HidingSigningProvider::GetAllAggregateParticipantPubkeys() const
{
return m_provider->GetAllAggregateParticipantPubkeys();
}
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::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::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
@ -82,6 +107,36 @@ bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, Tapro
return LookupHelper(tr_trees, output_key, builder); return LookupHelper(tr_trees, output_key, builder);
} }
std::vector<CPubKey> FlatSigningProvider::GetAggregateParticipantPubkeys(const CPubKey& pubkey) const
{
const auto& it = aggregate_pubkeys.find(pubkey);
if (it == aggregate_pubkeys.end()) return {};
return it->second;
}
std::map<CPubKey, std::vector<CPubKey>> FlatSigningProvider::GetAllAggregateParticipantPubkeys() const
{
return aggregate_pubkeys;
}
void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const
{
if (!musig2_secnonces) return;
musig2_secnonces->emplace(session_id, std::move(nonce));
}
std::optional<std::reference_wrapper<MuSig2SecNonce>> FlatSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
{
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
{
musig2_secnonces->erase(session_id);
}
FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b) FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
{ {
scripts.merge(b.scripts); scripts.merge(b.scripts);
@ -89,6 +144,8 @@ FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
keys.merge(b.keys); keys.merge(b.keys);
origins.merge(b.origins); origins.merge(b.origins);
tr_trees.merge(b.tr_trees); tr_trees.merge(b.tr_trees);
aggregate_pubkeys.merge(b.aggregate_pubkeys);
if (!musig2_secnonces) musig2_secnonces = b.musig2_secnonces;
return *this; return *this;
} }

View file

@ -9,11 +9,15 @@
#include <addresstype.h> #include <addresstype.h>
#include <attributes.h> #include <attributes.h>
#include <key.h> #include <key.h>
#include <musig.h>
#include <pubkey.h> #include <pubkey.h>
#include <script/keyorigin.h> #include <script/keyorigin.h>
#include <script/script.h> #include <script/script.h>
#include <sync.h> #include <sync.h>
#include <functional>
#include <optional>
struct ShortestVectorFirstComparator struct ShortestVectorFirstComparator
{ {
bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const
@ -161,6 +165,11 @@ public:
virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; }
virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } 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 bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; }
virtual std::vector<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const { return {}; }
virtual std::map<CPubKey, std::vector<CPubKey>> GetAllAggregateParticipantPubkeys() 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 bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const
{ {
@ -204,6 +213,11 @@ public:
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
std::vector<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override;
std::map<CPubKey, std::vector<CPubKey>> GetAllAggregateParticipantPubkeys() 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 struct FlatSigningProvider final : public SigningProvider
@ -213,6 +227,8 @@ struct FlatSigningProvider final : public SigningProvider
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins;
std::map<CKeyID, CKey> keys; 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<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 GetCScript(const CScriptID& scriptid, CScript& script) const override;
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
@ -221,6 +237,11 @@ struct FlatSigningProvider final : public SigningProvider
bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override;
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
std::vector<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override;
std::map<CPubKey, std::vector<CPubKey>> GetAllAggregateParticipantPubkeys() 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; FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
}; };

View file

@ -40,15 +40,17 @@ void CheckInferRaw(const CScript& script)
} }
constexpr int DEFAULT = 0; constexpr int DEFAULT = 0;
constexpr int RANGE = 1; // Expected to be ranged descriptor constexpr int RANGE = 1 << 0; // Expected to be ranged descriptor
constexpr int HARDENED = 2; // Derivation needs access to private keys constexpr int HARDENED = 1 << 1; // Derivation needs access to private keys
constexpr int UNSOLVABLE = 4; // This descriptor is not expected to be solvable constexpr int UNSOLVABLE = 1 << 2; // This descriptor is not expected to be solvable
constexpr int SIGNABLE = 8; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code) constexpr int SIGNABLE = 1 << 3; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code)
constexpr int DERIVE_HARDENED = 16; // The final derivation is hardened, i.e. ends with *' or *h constexpr int DERIVE_HARDENED = 1 << 4; // The final derivation is hardened, i.e. ends with *' or *h
constexpr int MIXED_PUBKEYS = 32; constexpr int MIXED_PUBKEYS = 1 << 5;
constexpr int XONLY_KEYS = 64; // X-only pubkeys are in use (and thus inferring/caching may swap parity of pubkeys/keyids) constexpr int XONLY_KEYS = 1 << 6; // X-only pubkeys are in use (and thus inferring/caching may swap parity of pubkeys/keyids)
constexpr int MISSING_PRIVKEYS = 128; // Not all private keys are available, so ToPrivateString will fail. constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available, so ToPrivateString will fail.
constexpr int SIGNABLE_FAILS = 256; // We can sign with this descriptor, but actually trying to sign 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
/** Compare two descriptors. If only one of them has a checksum, the checksum is ignored. */ /** Compare two descriptors. If only one of them has a checksum, the checksum is ignored. */
bool EqualDescriptor(std::string a, std::string b) bool EqualDescriptor(std::string a, std::string b)
@ -121,8 +123,18 @@ std::set<CPubKey> GetKeyData(const FlatSigningProvider& provider, int flags) {
} }
std::set<std::pair<CPubKey, KeyOriginInfo>> GetKeyOriginData(const FlatSigningProvider& provider, int flags) { std::set<std::pair<CPubKey, KeyOriginInfo>> GetKeyOriginData(const FlatSigningProvider& provider, int flags) {
std::set<CKeyID> ignored;
if (flags & MUSIG) {
for (const auto& [_, part_pks] : provider.aggregate_pubkeys) {
for (const auto& pk : part_pks) {
ignored.insert(pk.GetID());
}
}
}
std::set<std::pair<CPubKey, KeyOriginInfo>> ret; std::set<std::pair<CPubKey, KeyOriginInfo>> ret;
for (const auto& [_, data] : provider.origins) { for (const auto& [keyid, data] : provider.origins) {
if (ignored.contains(keyid)) continue;
if (flags & XONLY_KEYS) { if (flags & XONLY_KEYS) {
unsigned char bytes[33]; unsigned char bytes[33];
BOOST_CHECK_EQUAL(data.first.size(), 33); BOOST_CHECK_EQUAL(data.first.size(), 33);
@ -283,7 +295,10 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
// Check whether keys are in the cache // Check whether keys are in the cache
const auto& der_xpub_cache = desc_cache.GetCachedDerivedExtPubKeys(); const auto& der_xpub_cache = desc_cache.GetCachedDerivedExtPubKeys();
const auto& parent_xpub_cache = desc_cache.GetCachedParentExtPubKeys(); const auto& parent_xpub_cache = desc_cache.GetCachedParentExtPubKeys();
const size_t num_xpubs = CountXpubs(pub1); size_t num_xpubs = CountXpubs(pub1);
if (flags & MUSIG_DERIVATION) {
num_xpubs++;
}
if ((flags & RANGE) && !(flags & (DERIVE_HARDENED))) { if ((flags & RANGE) && !(flags & (DERIVE_HARDENED))) {
// For ranged, unhardened derivation, None of the keys in origins should appear in the cache but the cache should have parent keys // For ranged, unhardened derivation, None of the keys in origins should appear in the cache but the cache should have parent keys
// But we can derive one level from each of those parent keys and find them all // But we can derive one level from each of those parent keys and find them all
@ -301,16 +316,22 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
const CPubKey& pk = origin_pair.second.first; const CPubKey& pk = origin_pair.second.first;
count_pks += pubkeys.count(pk); count_pks += pubkeys.count(pk);
} }
if (flags & MIXED_PUBKEYS) { if (flags & MUSIG_DERIVATION) {
BOOST_CHECK_EQUAL(num_xpubs, count_pks); BOOST_CHECK_EQUAL(count_pks, 1);
BOOST_CHECK_EQUAL(num_xpubs, pubkeys.size());
} else { } else {
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks); if (flags & MUSIG) count_pks++; // One extra key for the aggregate key that is not in the cache
if (flags & MIXED_PUBKEYS) {
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
} else {
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
}
} }
} else if (num_xpubs > 0) { } else if (num_xpubs > 0) {
// For ranged, hardened derivation, or not ranged, but has an xpub, all of the keys should appear in the cache // For ranged, hardened derivation, or not ranged, but has an xpub, all of the keys should appear in the cache
BOOST_CHECK(der_xpub_cache.size() + parent_xpub_cache.size() == num_xpubs); BOOST_CHECK_EQUAL(der_xpub_cache.size() + parent_xpub_cache.size(), num_xpubs);
if (!(flags & MIXED_PUBKEYS)) { if (!(flags & MIXED_PUBKEYS)) {
BOOST_CHECK(num_xpubs == script_provider_cached.origins.size()); BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), num_xpubs);
} }
// Get all of the derived pubkeys // Get all of the derived pubkeys
std::set<CPubKey> pubkeys; std::set<CPubKey> pubkeys;
@ -333,10 +354,17 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
const CPubKey& pk = origin_pair.second.first; const CPubKey& pk = origin_pair.second.first;
count_pks += pubkeys.count(pk); count_pks += pubkeys.count(pk);
} }
if (flags & MIXED_PUBKEYS) { if (flags & MUSIG_DERIVATION && !(flags & MIXED_PUBKEYS)) {
BOOST_CHECK_EQUAL(num_xpubs, count_pks); // pubkeys is one key per xpub + one derived key per xpub
BOOST_CHECK_EQUAL(2 * count_pks, pubkeys.size());
BOOST_CHECK_EQUAL(2 * num_xpubs, pubkeys.size());
} else { } else {
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks); if (flags & MUSIG) count_pks++; // One extra key for the aggregate key that is not in the cache
if (flags & MIXED_PUBKEYS) {
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
} else {
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
}
} }
} else if (!(flags & MIXED_PUBKEYS)) { } else if (!(flags & MIXED_PUBKEYS)) {
// Only const pubkeys, nothing should be cached // Only const pubkeys, nothing should be cached
@ -1071,6 +1099,96 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
CheckInferDescriptor("76a914a31725c74421fadc50d35520ab8751ed120af80588ac", "pkh(04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31)", {}, {{"04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31", ""}}); CheckInferDescriptor("76a914a31725c74421fadc50d35520ab8751ed120af80588ac", "pkh(04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31)", {}, {{"04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31", ""}});
// Infer pk() from p2pk with uncompressed key // Infer pk() from p2pk with uncompressed key
CheckInferDescriptor("4104032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220ac", "pk(04032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220)", {}, {{"04032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220", ""}}); CheckInferDescriptor("4104032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220ac", "pk(04032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220)", {}, {{"04032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220", ""}});
// MuSig2 parsing
Check("rawtr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "rawtr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "rawtr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", XONLY_KEYS | MUSIG, {{"5120789d937bade6673538f3e28d8368dda4d0512f94da44cf477a505716d26a1575"}}, OutputType::BECH32M);
Check("tr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "tr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "tr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", XONLY_KEYS | MUSIG, {{"512079e6c3e628c9bfbce91de6b7fb28e2aec7713d377cf260ab599dcbc40e542312"}}, OutputType::BECH32M);
Check("rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*))","rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*))","rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*))", XONLY_KEYS | RANGE | MUSIG, {{"5120754ccfd18ed4051de3b1144b6145cad4b2999387338dfb85ec392f2963ceaa3a"}, {"5120be80016576d2691ccc4077bc91d7ece4db34667d6e84829d5e08480cd4bc0b78"}, {"5120b7139e2f8b92570ad96c40c3b5e6557a5194e288a96df6f29980523365239d58"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, {{}, {0, 0}, {0, 1}, {0, 2}});
Check("rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)","rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)","rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)", XONLY_KEYS | RANGE | MUSIG | MUSIG_DERIVATION, {{"51209508c08832f3bb9d5e8baf8cb5cfa3669902e2f2da19acea63ff47b93faa9bfc"}, {"51205ca1102663025a83dd9b5dbc214762c5a6309af00d48167d2d6483808525a298"}, {"51207dbed1b89c338df6a1ae137f133a19cae6e03d481196ee6f1a5c7d1aeb56b166"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, {{}, {0, 0}, {0, 1}, {0, 2}});
Check("rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)","rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)","rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)", XONLY_KEYS | MUSIG | MUSIG_DERIVATION, {{"51200e355f2bc9e754268e12bbd337499c2f7ffafc3101c41792709007b25a862532"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, {{}, {0}, {1}});
Check("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*,pk(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S))","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*,pk(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*,pk(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))", XONLY_KEYS | RANGE | MUSIG | MUSIG_DERIVATION, {{"51201d377b637b5c73f670f5c8a96a2c0bb0d1a682a1fca6aba91fe673501a189782"}, {"51208950c83b117a6c208d5205ffefcf75b187b32512eb7f0d8577db8d9102833036"}, {"5120a49a477c61df73691b77fcd563a80a15ea67bb9c75470310ce5c0f25918db60d"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, {{}, {0, 0}, {0, 1}, {0, 2}});
Check("tr(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,pk(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*))","tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,pk(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*))","tr(f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,pk(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*))", XONLY_KEYS | RANGE | MUSIG | MUSIG_DERIVATION, {{"512068983d461174afc90c26f3b2821d8a9ced9534586a756763b68371a404635cc8"}, {"5120368e2d864115181bdc8bb5dc8684be8d0760d5c33315570d71a21afce4afd43e"}, {"512097a1e6270b33ad85744677418bae5f59ea9136027223bc6e282c47c167b471d5"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, {{}, {0, 0}, {0, 1}, {0, 2}});
CheckMultipath("rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/<1;2;3>/0/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/*,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4/0/0/<3;4;5>/*))",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/<1;2;3>/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/<3;4;5>/*))",
{
"rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/0/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/*,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4/0/0/3/*))",
"rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/2/0/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/*,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4/0/0/4/*))",
"rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3/0/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/*,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4/0/0/5/*))",
},
{
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/3/*))",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/2/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/4/*))",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/5/*))",
},
{
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/3/*))",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/2/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/4/*))",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3/0/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/*,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t/0/0/5/*))",
},
XONLY_KEYS | RANGE | MUSIG,
{
{{"51204ba445a411bd8500476ef916e6d4dd7c137a77e0637e5b0e98339210d78d595a"},{"5120800394c4f39743734c9a15eaa171476814bed0ea19ad771037c5f1ceb20244a9"},{"512011658c4e00fae6f22b9adc2b3823ff3ec6367599783788f4aa8fe1ab3dd0a7ea"}},
{{"5120b977ae89f221762a61ee986fed7a493426462483afef46f7225765e015934961"},{"5120b70bf732ed38fcc2052075f83901f8588f1016f6741aaacce6e439a02235e5ed"},{"5120d7fa329159ae543b41ca81c7b0e916824ce5d13f61de5b6246dc55a3367f8596"}},
{{"5120cae8685560b38da78300cc06a230a0f47179f20689d71655a665bdd8c5c875cf"},{"5120ad51a056d67374c56c7f6d9bb1a6d0d5a20449f5805628334dbac8d4ed8686b5"},{"5120e080130242eae1fc92d8c84d7390697e80b4d1e54184bdcbccfc7d6c4fe9bb0f"}},
},
OutputType::BECH32M,
{
{{}, {1, 0, 0}, {1, 0, 1}, {1, 0, 2}, {0, 0}, {0, 1}, {0, 2}, {0, 0, 3, 0}, {0, 0, 3, 1}, {0, 0, 3, 2}},
{{}, {2, 0, 0}, {2, 0, 1}, {2, 0, 2}, {0, 0}, {0, 1}, {0, 2}, {0, 0, 4, 0}, {0, 0, 4, 1}, {0, 0, 4, 2}},
{{}, {3, 0, 0}, {3, 0, 1}, {3, 0, 2}, {0, 0}, {0, 1}, {0, 2}, {0, 0, 5, 0}, {0, 0, 5, 1}, {0, 0, 5, 2}},
}
);
CheckMultipath("rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/<3;4;5>/*)",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/<3;4;5>/*)",
{
"rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/3/*)",
"rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/4/*)",
"rawtr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/5/*)",
},
{
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/3/*)",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/4/*)",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/5/*)",
},
{
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/3/*)",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/4/*)",
"rawtr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/5/*)",
},
XONLY_KEYS | RANGE | MUSIG | MUSIG_DERIVATION,
{
{{"51204a0fecdd99c67eb2afca0efa9a008c8bbc4dbb5ccb094b3eee273127b1ababee"},{"512006120155e6bfd6a3abf8a697caaf5669058395ae0052283a1c6e852d373ceccd"},{"5120d46831206710fca12ef7b562a0812250fdda110146dc1b9ac3a099c81ebcef82"}},
{{"5120f2b491de0be3b53482253865a5e0f2d2dbdc425d59db0c48f01c6bed9c6687c2"},{"5120601daf543e702b9c28a02f33961dfddfad666d9218b3b0b80177420b37619683"},{"512081dc64aac07811399defde8c959e3a66c56b621360e55ff01c2d43dfe7928b66"}},
{{"51201bde67648efbd371e63fc5d30325113d0ad5fb853afc53e9b78302708d5fd865"},{"51205bf89fde498522610b5db4eb306b3e1499057aac6d9a56dea832adca4722858b"},{"5120b4a81ca1cc45973422d26d687ab3b586d18508a6dbbbcd38e841400c214c4e83"}},
},
OutputType::BECH32M,
{
{{}, {3, 0}, {3, 1}, {3, 2}},
{{}, {4, 0}, {4, 1}, {4, 2}},
{{}, {5, 0}, {5, 1}, {5, 2}},
}
);
// MuSig2 Parsing Failures
CheckUnparsable("pk(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pk(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pk(): musig() is only allowed in tr()");
CheckUnparsable("pkh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pkh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pkh(): musig() is only allowed in tr()");
CheckUnparsable("wpkh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "wpkh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "wpkh(): musig() is only allowed in tr()");
CheckUnparsable("combo(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "combo(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "combo(): musig() is only allowed in tr()");
CheckUnparsable("sh(wpkh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)))", "sh(wpkh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)))", "wpkh(): musig() is only allowed in tr()");
CheckUnparsable("sh(wsh(pk(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)))", "sh(wsh(pk(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))))", "pk(): musig() is only allowed in tr()");
CheckUnparsable("wsh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "wsh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "A function is needed within P2WSH");
CheckUnparsable("sh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "sh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "A function is needed within P2SH");
CheckUnparsable("tr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)/0/0)", "tr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)/0/0)", "tr(): musig(): Ranged musig() requires all participants to be xpubs");
CheckUnparsable("tr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)/0/0)", "tr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)/0/0)", "tr(): musig(): Ranged musig() requires all participants to be xpubs");
CheckUnparsable("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)", "tr(): musig(): Cannot have ranged participant keys if musig() is also ranged");
CheckUnparsable("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0h/*)","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0h/*)", "tr(): musig(): cannot have hardened derivation steps");
CheckUnparsable("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*h)","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*h)", "tr(): musig(): Cannot have hardened child derivation");
CheckUnparsable("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/<0;1>,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/<2;3>)/<3;4>)","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/<0;1>,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/<2;3>)/<3;4>)", "tr(): musig(): Cannot have multipath participant keys if musig() is also multipath");
CheckUnparsable("tr(musig()/0)", "tr(musig()/0)", "tr(): musig(): Must contain key expressions");
// Fuzzer crash test cases
CheckUnparsable("pk(musig(dd}uue/00/)k(", "pk(musig(dd}uue/00/)k(", "Invalid musig() expression");
CheckUnparsable("tr(musig(tuus(oldepk(gg)ggggfgg)<,z(((((((((((((((((((((st)", "tr(musig(tuus(oldepk(gg)ggggfgg)<,z(((((((((((((((((((((st)","tr(): Too many ')' in musig() expression");
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View file

@ -279,7 +279,8 @@ FUZZ_TARGET(key, .init = initialize_key)
{ {
CPubKey child_pubkey; CPubKey child_pubkey;
ChainCode child_chaincode; ChainCode child_chaincode;
const bool ok = pubkey.Derive(child_pubkey, child_chaincode, 0, random_uint256); uint256 tweak;
const bool ok = pubkey.Derive(child_pubkey, child_chaincode, 0, random_uint256, tweak);
assert(ok); assert(ok);
assert(child_pubkey != pubkey); assert(child_pubkey != pubkey);
assert(child_pubkey.IsCompressed()); assert(child_pubkey.IsCompressed());

View file

@ -79,7 +79,7 @@ util::Result<void> ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestin
} }
// If sign is true, transaction must previously have been filled // If sign is true, transaction must previously have been filled
std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (!sign) { if (!sign) {
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize); return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize);

View file

@ -35,7 +35,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
*/ */
util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const; util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
}; };
} // namespace wallet } // namespace wallet
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H #endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H

View file

@ -343,8 +343,8 @@ bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) {
// First fill transaction with our data without signing, // First fill transaction with our data without signing,
// so external signers are not asked to sign more than once. // so external signers are not asked to sign more than once.
bool complete; bool complete;
wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); wallet.FillPSBT(psbtx, complete, std::nullopt, false /* sign */, true /* bip32derivs */);
auto err{wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */)}; auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, true /* sign */, false /* bip32derivs */)};
if (err) return false; if (err) return false;
complete = FinalizeAndExtractPSBT(psbtx, mtx); complete = FinalizeAndExtractPSBT(psbtx, mtx);
return complete; return complete;

View file

@ -391,7 +391,7 @@ public:
} }
return {}; return {};
} }
std::optional<PSBTError> fillPSBT(int sighash_type, std::optional<PSBTError> fillPSBT(std::optional<int> sighash_type,
bool sign, bool sign,
bool bip32derivs, bool bip32derivs,
size_t* n_signed, size_t* n_signed,

View file

@ -1091,6 +1091,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]); std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]);
} }
// Only single key descriptors are allowed to be imported to a legacy wallet's keypool
bool can_keypool = parsed_descs.at(0)->IsSingleKey();
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
for (size_t j = 0; j < parsed_descs.size(); ++j) { for (size_t j = 0; j < parsed_descs.size(); ++j) {
@ -1107,8 +1110,10 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
std::vector<CScript> scripts_temp; std::vector<CScript> scripts_temp;
parsed_desc->Expand(i, keys, scripts_temp, out_keys); parsed_desc->Expand(i, keys, scripts_temp, out_keys);
std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
for (const auto& key_pair : out_keys.pubkeys) { if (can_keypool) {
ordered_pubkeys.emplace_back(key_pair.first, desc_internal); for (const auto& key_pair : out_keys.pubkeys) {
ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
}
} }
for (const auto& x : out_keys.scripts) { for (const auto& x : out_keys.scripts) {

View file

@ -100,8 +100,8 @@ static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const
// First fill transaction with our data without signing, // First fill transaction with our data without signing,
// so external signers are not asked to sign more than once. // so external signers are not asked to sign more than once.
bool complete; bool complete;
pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true); pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/true);
const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/false)}; const auto err{pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/true, /*bip32derivs=*/false)};
if (err) { if (err) {
throw JSONRPCPSBTError(*err); throw JSONRPCPSBTError(*err);
} }
@ -951,12 +951,15 @@ RPCHelpMan signrawtransactionwithwallet()
// Parse the prevtxs array // Parse the prevtxs array
ParsePrevouts(request.params[1], nullptr, coins); ParsePrevouts(request.params[1], nullptr, coins);
int nHashType = ParseSighashString(request.params[2]); std::optional<int> nHashType = ParseSighashString(request.params[2]);
if (!nHashType) {
nHashType = SIGHASH_DEFAULT;
}
// Script verification errors // Script verification errors
std::map<int, bilingual_str> input_errors; std::map<int, bilingual_str> input_errors;
bool complete = pwallet->SignTransaction(mtx, coins, nHashType, input_errors); bool complete = pwallet->SignTransaction(mtx, coins, *nHashType, input_errors);
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result); SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
return result; return result;
@ -1169,7 +1172,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
} else { } else {
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true)}; const auto err{pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/true)};
CHECK_NONFATAL(!err); CHECK_NONFATAL(!err);
CHECK_NONFATAL(!complete); CHECK_NONFATAL(!complete);
DataStream ssTx{}; DataStream ssTx{};
@ -1623,7 +1626,7 @@ RPCHelpMan walletprocesspsbt()
} }
// Get the sighash type // Get the sighash type
int nHashType = ParseSighashString(request.params[2]); std::optional<int> nHashType = ParseSighashString(request.params[2]);
// Fill transaction with our data and also sign // Fill transaction with our data and also sign
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
@ -1769,7 +1772,7 @@ RPCHelpMan walletcreatefundedpsbt()
// Fill transaction with out data but don't sign // Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool(); bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
bool complete = true; bool complete = true;
const auto err{wallet.FillPSBT(psbtx, complete, 1, /*sign=*/false, /*bip32derivs=*/bip32derivs)}; const auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/bip32derivs)};
if (err) { if (err) {
throw JSONRPCPSBTError(*err); throw JSONRPCPSBTError(*err);
} }

View file

@ -638,7 +638,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
return SigningResult::SIGNING_FAILED; return SigningResult::SIGNING_FAILED;
} }
std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@ -651,11 +651,6 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
continue; continue;
} }
// Get the Sighash type
if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) {
return PSBTError::SIGHASH_MISMATCH;
}
// Check non_witness_utxo has specified prevout // Check non_witness_utxo has specified prevout
if (input.non_witness_utxo) { if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) { if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
@ -665,7 +660,10 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
// There's no UTXO so we can just skip this now // There's no UTXO so we can just skip this now
continue; continue;
} }
SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); PSBTError res = SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
if (res != PSBTError::OK && res != PSBTError::INCOMPLETE) {
return res;
}
bool signed_one = PSBTInputSigned(input); bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) { if (n_signed && (signed_one || !sign)) {
@ -2498,6 +2496,10 @@ std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvid
FlatSigningProvider master_provider; FlatSigningProvider master_provider;
master_provider.keys = GetKeys(); master_provider.keys = GetKeys();
m_wallet_descriptor.descriptor->ExpandPrivate(index, master_provider, *out_keys); 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; return out_keys;
@ -2545,7 +2547,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
return SigningResult::OK; return SigningResult::OK;
} }
std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@ -2558,11 +2560,6 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
continue; continue;
} }
// Get the Sighash type
if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) {
return PSBTError::SIGHASH_MISMATCH;
}
// Get the scriptPubKey to know which SigningProvider to use // Get the scriptPubKey to know which SigningProvider to use
CScript script; CScript script;
if (!input.witness_utxo.IsNull()) { if (!input.witness_utxo.IsNull()) {
@ -2620,7 +2617,10 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
} }
} }
SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!sign, /*hide_origin=*/!bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); PSBTError res = SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!sign, /*hide_origin=*/!bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
if (res != PSBTError::OK && res != PSBTError::INCOMPLETE) {
return res;
}
bool signed_one = PSBTInputSigned(input); bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) { if (n_signed && (signed_one || !sign)) {

View file

@ -10,6 +10,7 @@
#include <common/signmessage.h> #include <common/signmessage.h>
#include <common/types.h> #include <common/types.h>
#include <logging.h> #include <logging.h>
#include <musig.h>
#include <node/types.h> #include <node/types.h>
#include <psbt.h> #include <psbt.h>
#include <script/descriptor.h> #include <script/descriptor.h>
@ -246,7 +247,7 @@ public:
/** Sign a message with the given script */ /** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */ /** Adds script and derivation path information to a PSBT, and optionally signs it. */
virtual std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return common::PSBTError::UNSUPPORTED; } virtual std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = std::nullopt, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return common::PSBTError::UNSUPPORTED; }
virtual uint256 GetID() const { return uint256(); } virtual uint256 GetID() const { return uint256(); }
@ -492,7 +493,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override; bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = std::nullopt, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override; uint256 GetID() const override;
@ -605,6 +606,20 @@ private:
//! Number of pre-generated keys/scripts (part of the look-ahead process, used to detect payments) //! 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}; 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.
*/
// TODO: Check thread safety of this
mutable std::map<uint256, MuSig2SecNonce> m_musig2_secnonces;
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); 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); KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
@ -681,7 +696,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override; bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = std::nullopt, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override; uint256 GetID() const override;

View file

@ -188,7 +188,8 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
} }
auto psbt{*opt_psbt}; auto psbt{*opt_psbt};
const PrecomputedTransactionData txdata{PrecomputePSBTData(psbt)}; const PrecomputedTransactionData txdata{PrecomputePSBTData(psbt)};
const int sighash_type{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 150)}; std::optional<int> sighash_type{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 151)};
if (sighash_type == 151) sighash_type = std::nullopt;
auto sign = fuzzed_data_provider.ConsumeBool(); auto sign = fuzzed_data_provider.ConsumeBool();
auto bip32derivs = fuzzed_data_provider.ConsumeBool(); auto bip32derivs = fuzzed_data_provider.ConsumeBool();
auto finalize = fuzzed_data_provider.ConsumeBool(); auto finalize = fuzzed_data_provider.ConsumeBool();

View file

@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Fill transaction with our data // Fill transaction with our data
bool complete = true; bool complete = true;
BOOST_REQUIRE(!m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false, true)); BOOST_REQUIRE(!m_wallet.FillPSBT(psbtx, complete, std::nullopt, false, true));
// Get the final tx // Get the final tx
DataStream ssTx{}; DataStream ssTx{};
@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input // Try to sign the mutated input
SignatureData sigdata; SignatureData sigdata;
BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true)); BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, std::nullopt, true, true));
} }
BOOST_AUTO_TEST_CASE(parse_hd_keypath) BOOST_AUTO_TEST_CASE(parse_hd_keypath)

View file

@ -26,6 +26,7 @@ public:
bool IsRange() const override { return false; } bool IsRange() const override { return false; }
bool IsSolvable() const override { return false; } bool IsSolvable() const override { return false; }
bool IsSingleType() const override { return true; } bool IsSingleType() const override { return true; }
bool IsSingleKey() const override { return true; }
bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; } bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; }
bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; } bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; }
bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; }; bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };

View file

@ -2197,7 +2197,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
return false; return false;
} }
std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, std::optional<int> sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@ -2240,7 +2240,7 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bo
} }
} }
RemoveUnnecessaryTransactions(psbtx, sighash_type); RemoveUnnecessaryTransactions(psbtx);
// Complete if every input is now signed // Complete if every input is now signed
complete = true; complete = true;

View file

@ -663,7 +663,7 @@ public:
*/ */
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbtx, std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbtx,
bool& complete, bool& complete,
int sighash_type = SIGHASH_DEFAULT, std::optional<int> sighash_type = std::nullopt,
bool sign = true, bool sign = true,
bool bip32derivs = true, bool bip32derivs = true,
size_t* n_signed = nullptr, size_t* n_signed = nullptr,

View file

@ -27,13 +27,14 @@ from test_framework.psbt import (
PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_UNSIGNED_TX,
PSBT_IN_RIPEMD160, PSBT_IN_RIPEMD160,
PSBT_IN_SHA256, PSBT_IN_SHA256,
PSBT_IN_SIGHASH_TYPE,
PSBT_IN_HASH160, PSBT_IN_HASH160,
PSBT_IN_HASH256, PSBT_IN_HASH256,
PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_NON_WITNESS_UTXO,
PSBT_IN_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO,
PSBT_OUT_TAP_TREE, PSBT_OUT_TAP_TREE,
) )
from test_framework.script import CScript, OP_TRUE from test_framework.script import CScript, OP_TRUE, SIGHASH_ALL, SIGHASH_ANYONECANPAY
from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
@ -201,6 +202,100 @@ class PSBTTest(BitcoinTestFramework):
wallet.unloadwallet() wallet.unloadwallet()
def test_sighash_mismatch(self):
self.log.info("Test sighash type mismatches")
self.nodes[0].createwallet("sighash_mismatch")
wallet = self.nodes[0].get_wallet_rpc("sighash_mismatch")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
addr = wallet.getnewaddress(address_type="bech32")
def_wallet.sendtoaddress(addr, 5)
self.generate(self.nodes[0], 6)
# Retrieve the descriptors so we can do all of the tests with descriptorprocesspsbt as well
if self.options.descriptors:
descs = wallet.listdescriptors(True)["descriptors"]
else:
descs = [descsum_create(f"wpkh({wallet.dumpprivkey(addr)})")]
# Make a PSBT
psbt = wallet.walletcreatefundedpsbt([], [{def_wallet.getnewaddress(): 1}])["psbt"]
# Modify the PSBT and insert a sighash field for ALL|ANYONECANPAY on input 0
mod_psbt = PSBT.from_base64(psbt)
mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL | SIGHASH_ANYONECANPAY).to_bytes(4, byteorder="little")
psbt = mod_psbt.to_base64()
# Mismatching sighash type fails
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, "DEFAULT")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, "ALL")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, "NONE")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, "SINGLE")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, "NONE|ANYONECANPAY")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, "SINGLE|ANYONECANPAY")
# No sighash type specified fails
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True)
# Matching sighash type succeeds
proc = wallet.walletprocesspsbt(psbt, True, "ALL|ANYONECANPAY")
assert_equal(proc["complete"], True)
# Repeat with descriptorprocesspsbt
# Mismatching sighash type fails
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, "DEFAULT")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, "ALL")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, "NONE")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, "SINGLE")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, "NONE|ANYONECANPAY")
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, "SINGLE|ANYONECANPAY")
# No sighash type specified fails
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs)
# Matching sighash type succeeds
proc = self.nodes[0].descriptorprocesspsbt(psbt, descs, "ALL|ANYONECANPAY")
assert_equal(proc["complete"], True)
wallet.unloadwallet()
def test_sighash_adding(self):
self.log.info("Test adding of sighash type field")
self.nodes[0].createwallet("sighash_adding")
wallet = self.nodes[0].get_wallet_rpc("sighash_adding")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
addr = wallet.getnewaddress(address_type="bech32")
def_wallet.sendtoaddress(addr, 5)
self.generate(self.nodes[0], 6)
# Make a PSBT
psbt = wallet.walletcreatefundedpsbt([], [{def_wallet.getnewaddress(): 1}])["psbt"]
psbt = wallet.walletprocesspsbt(psbt=psbt, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]
# Check that the PSBT has a sighash field on all inputs
dec_psbt = self.nodes[0].decodepsbt(psbt)
for input in dec_psbt["inputs"]:
assert_equal(input["sighash"], "ALL|ANYONECANPAY")
# Make sure we can still finalize the transaction
fin_res = self.nodes[0].finalizepsbt(psbt)
assert_equal(fin_res["complete"], True)
fin_hex = fin_res["hex"]
# Change the sighash field to a different value and make sure we still finalize to the same thing
mod_psbt = PSBT.from_base64(psbt)
mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
psbt = mod_psbt.to_base64()
fin_res = self.nodes[0].finalizepsbt(psbt)
assert_equal(fin_res["complete"], True)
assert_equal(fin_res["hex"], fin_hex)
self.nodes[0].sendrawtransaction(fin_res["hex"])
self.generate(self.nodes[0], 1)
wallet.unloadwallet()
def assert_change_type(self, psbtx, expected_type): def assert_change_type(self, psbtx, expected_type):
"""Assert that the given PSBT has a change output with the given type.""" """Assert that the given PSBT has a change output with the given type."""
@ -1051,6 +1146,8 @@ class PSBTTest(BitcoinTestFramework):
self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed") self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed")
assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all") assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all")
self.test_sighash_mismatch()
self.test_sighash_adding()
if __name__ == '__main__': if __name__ == '__main__':
PSBTTest(__file__).main() PSBTTest(__file__).main()

View file

@ -392,6 +392,7 @@ BASE_SCRIPTS = [
'mempool_datacarrier.py', 'mempool_datacarrier.py',
'feature_coinstatsindex.py', 'feature_coinstatsindex.py',
'wallet_orphanedreward.py', 'wallet_orphanedreward.py',
'wallet_musig.py --descriptors',
'wallet_timelock.py', 'wallet_timelock.py',
'p2p_permissions.py', 'p2p_permissions.py',
'feature_blocksdir.py', 'feature_blocksdir.py',

194
test/functional/wallet_musig.py Executable file
View file

@ -0,0 +1,194 @@
#!/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
PRIVKEY_RE = re.compile(r"^tr\((.+?)/.+\)#.{8}$")
PUBKEY_RE = re.compile(r"^tr\((\[.+?\].+?)/.+\)#.{8}$")
ORIGIN_PATH_RE = re.compile(r"^\[\w{8}(/.*)\].*$")
MULTIPATH_RE = re.compile(r"(.*?)<(\d+);(\d+)>")
class WalletMuSigTest(BitcoinTestFramework):
WALLET_NUM = 0
def add_options(self, parser):
self.add_wallet_options(parser, legacy=False)
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):
self.log.info(f"Testing {comment}")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
has_int = "<" in pattern and ">" in pattern
wallets = []
keys = []
pat = pattern.replace("$H", H_POINT)
# Figure out how many wallets are needed and create them
exp_key_leaf = 0
for i in range(10):
if f"${i}" in pat:
exp_key_leaf += pat.count(f"${i}")
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))
# Construct and import each wallet's musig descriptor
for i, wallet in enumerate(wallets):
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 wallet in wallets:
if addr is None:
addr = wallet.getnewaddress(address_type="bech32m")
else:
assert_equal(addr, wallet.getnewaddress(address_type="bech32m"))
if has_int:
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
def_wallet.sendtoaddress(addr, 10)
self.generate(self.nodes[0], 1)
# Spend that UTXO
utxo = wallets[0].listunspent()[0]
psbt = wallets[0].send(outputs=[{def_wallet.getnewaddress(): 5}], inputs=[utxo], change_type="bech32m")["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("))
# Retrieve all participant pubkeys
part_pks = set()
for agg in dec_psbt["inputs"][0]["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 dec_psbt["inputs"][0]["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 wallet in wallets:
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"]), exp_key_leaf)
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, internal 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 wallet in wallets:
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"]), exp_key_leaf)
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, internal key, or in any scripts"
else:
assert False, "Aggregate pubkey for partial sig not seen as output key or internal key"
# Non-participant aggregates partial sigs and send
finalized = self.nodes[0].finalizepsbt(comb_psig_psbt)
assert_equal(finalized["complete"], True)
assert "hex" in finalized
self.nodes[0].sendrawtransaction(finalized["hex"])
def run_test(self):
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/*))", "tr(musig($0/<0;1>/*,$1/<1;2>/*,$2/<2;3>/*))")
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("tr(H, pk(musig(keys/*)))", "tr($H,pk(musig($0/<0;1>/*,$1/<1;2>/*,$2/<2;3>/*)))")
self.do_test("tr(H,pk(musig/*))", "tr($H,pk(musig($0,$1,$2)/<0;1>/*))")
self.do_test("tr(H,{pk(musig/*), pk(musig/*)})", "tr($H,{pk(musig($0,$1,$2)/<0;1>/*),pk(musig($3,$4,$5)/0/*)})")
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/*)})")
if __name__ == '__main__':
WalletMuSigTest(__file__).main()