mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-01 09:35:52 -05:00
Merge bd595c0429
into 85f96b01b7
This commit is contained in:
commit
cf09e81867
48 changed files with 2173 additions and 221 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
122
src/key.cpp
122
src/key.cpp
|
@ -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;
|
||||||
|
|
|
@ -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
183
src/musig.cpp
Normal 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
54
src/musig.h
Normal 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
|
|
@ -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 {
|
||||||
|
|
74
src/psbt.cpp
74
src/psbt.cpp
|
@ -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,13 +484,11 @@ 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
|
|
||||||
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
|
|
||||||
// Figure out if any non_witness_utxos should be dropped
|
// Figure out if any non_witness_utxos should be dropped
|
||||||
std::vector<unsigned int> to_drop;
|
std::vector<unsigned int> to_drop;
|
||||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||||
|
@ -465,6 +505,14 @@ void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int&
|
||||||
to_drop.clear();
|
to_drop.clear();
|
||||||
break;
|
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
|
||||||
|
// need to look at that field. If it is not present, then we can assume SIGHASH_DEFAULT or SIGHASH_ALL.
|
||||||
|
if (input.sighash_type != std::nullopt && (*input.sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
|
||||||
|
to_drop.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (input.non_witness_utxo) {
|
if (input.non_witness_utxo) {
|
||||||
to_drop.push_back(i);
|
to_drop.push_back(i);
|
||||||
}
|
}
|
||||||
|
@ -475,7 +523,6 @@ void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int&
|
||||||
psbtx.inputs.at(i).non_witness_utxo = nullptr;
|
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;
|
||||||
|
|
169
src/psbt.h
169
src/psbt.h
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,12 +283,94 @@ 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;
|
||||||
|
} 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;
|
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;
|
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 sigdata.taproot_script_sigs.contains(lookup_key);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename M, typename K, typename V>
|
template<typename M, typename K, typename V>
|
||||||
miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value)
|
miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value)
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyOriginInfo output_key_info;
|
||||||
|
if (provider.GetKeyOriginByXOnly(output, output_key_info)) {
|
||||||
|
auto it = sigdata.taproot_misc_pubkeys.find(output);
|
||||||
|
if (it == sigdata.taproot_misc_pubkeys.end()) {
|
||||||
|
sigdata.taproot_misc_pubkeys.emplace(output, std::make_pair(std::set<uint256>(), output_key_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto make_keypath_sig = [&](const XOnlyPubKey& pk, const uint256* merkle_root) {
|
||||||
std::vector<unsigned char> sig;
|
std::vector<unsigned char> sig;
|
||||||
if (sigdata.taproot_key_path_sig.size() == 0) {
|
if (creator.CreateSchnorrSig(provider, sig, pk, nullptr, merkle_root, SigVersion::TAPROOT)) {
|
||||||
if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sigdata.taproot_key_path_sig.size() == 0) {
|
}
|
||||||
if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) {
|
|
||||||
|
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;
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 & MUSIG_DERIVATION) {
|
||||||
|
BOOST_CHECK_EQUAL(count_pks, 1);
|
||||||
|
BOOST_CHECK_EQUAL(num_xpubs, pubkeys.size());
|
||||||
|
} else {
|
||||||
|
if (flags & MUSIG) count_pks++; // One extra key for the aggregate key that is not in the cache
|
||||||
if (flags & MIXED_PUBKEYS) {
|
if (flags & MIXED_PUBKEYS) {
|
||||||
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
||||||
} else {
|
} else {
|
||||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
|
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,11 +354,18 @@ 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 & MUSIG_DERIVATION && !(flags & MIXED_PUBKEYS)) {
|
||||||
|
// 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 {
|
||||||
|
if (flags & MUSIG) count_pks++; // One extra key for the aggregate key that is not in the cache
|
||||||
if (flags & MIXED_PUBKEYS) {
|
if (flags & MIXED_PUBKEYS) {
|
||||||
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
||||||
} else {
|
} else {
|
||||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
|
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
|
||||||
BOOST_CHECK(der_xpub_cache.empty());
|
BOOST_CHECK(der_xpub_cache.empty());
|
||||||
|
@ -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()
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,9 +1110,11 @@ 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()));
|
||||||
|
if (can_keypool) {
|
||||||
for (const auto& key_pair : out_keys.pubkeys) {
|
for (const auto& key_pair : out_keys.pubkeys) {
|
||||||
ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
|
ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& x : out_keys.scripts) {
|
for (const auto& x : out_keys.scripts) {
|
||||||
import_data.import_scripts.emplace(x.second);
|
import_data.import_scripts.emplace(x.second);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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; };
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
194
test/functional/wallet_musig.py
Executable 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()
|
Loading…
Add table
Reference in a new issue