mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-17 21:32:00 +01:00
Merge efe85f891e625634e489e47ae2f84b851caa1785 into db2c57ae9eebdb75c58cd165ac929919969c19a9
This commit is contained in:
commit
fca5e0e3e3
@ -27,7 +27,7 @@ message("Configuring secp256k1 subtree...")
|
||||
set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_ENABLE_MODULE_ECDH OFF CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG ON CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE)
|
||||
set(SECP256K1_BUILD_EXHAUSTIVE_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE)
|
||||
@ -157,6 +157,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
|
||||
key.cpp
|
||||
key_io.cpp
|
||||
merkleblock.cpp
|
||||
musig.cpp
|
||||
net_permissions.cpp
|
||||
net_types.cpp
|
||||
netaddress.cpp
|
||||
|
53
src/musig.cpp
Normal file
53
src/musig.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
// 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 <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);
|
||||
}
|
18
src/musig.h
Normal file
18
src/musig.h
Normal file
@ -0,0 +1,18 @@
|
||||
// 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;
|
||||
|
||||
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);
|
||||
|
||||
#endif // BITCOIN_MUSIG_H
|
@ -197,20 +197,29 @@ constexpr XOnlyPubKey XOnlyPubKey::NUMS_H{
|
||||
[]() 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> out;
|
||||
// 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
|
||||
// with 0x03.
|
||||
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.GetID());
|
||||
b[0] = 0x03;
|
||||
fullpubkey.Set(b, b + 33);
|
||||
out.push_back(fullpubkey.GetID());
|
||||
for (const CPubKey& pk : GetCPubKeys()) {
|
||||
out.push_back(pk.GetID());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,7 @@ public:
|
||||
* This is needed for key lookups since keys are indexed by CKeyID.
|
||||
*/
|
||||
std::vector<CKeyID> GetKeyIDs() const;
|
||||
std::vector<CPubKey> GetCPubKeys() const;
|
||||
|
||||
CPubKey GetEvenCorrespondingCPubKey() const;
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <hash.h>
|
||||
#include <key_io.h>
|
||||
#include <pubkey.h>
|
||||
#include <musig.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/parsing.h>
|
||||
#include <script/script.h>
|
||||
@ -174,12 +175,10 @@ public:
|
||||
* Used by the Miniscript descriptors to check for duplicate keys in the script.
|
||||
*/
|
||||
bool operator<(PubkeyProvider& other) const {
|
||||
CPubKey a, b;
|
||||
SigningProvider dummy;
|
||||
KeyOriginInfo dummy_info;
|
||||
FlatSigningProvider dummy;
|
||||
|
||||
GetPubKey(0, dummy, a, dummy_info);
|
||||
other.GetPubKey(0, dummy, b, dummy_info);
|
||||
std::optional<CPubKey> a = GetPubKey(0, dummy, dummy);
|
||||
std::optional<CPubKey> b = other.GetPubKey(0, dummy, dummy);
|
||||
|
||||
return a < b;
|
||||
}
|
||||
@ -189,7 +188,7 @@ public:
|
||||
* 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
|
||||
*/
|
||||
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. */
|
||||
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;
|
||||
|
||||
/** Derive a private key, if private data is available in arg. */
|
||||
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
|
||||
/** Derive a private key, if private data is available in arg and put it into out. */
|
||||
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. */
|
||||
virtual std::optional<CPubKey> GetRootPubKey() const = 0;
|
||||
@ -223,6 +222,9 @@ public:
|
||||
|
||||
/** Make a deep copy of this PubkeyProvider */
|
||||
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
|
||||
@ -240,15 +242,20 @@ class OriginPubkeyProvider final : public PubkeyProvider
|
||||
|
||||
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) {}
|
||||
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::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint);
|
||||
info.path.insert(info.path.begin(), m_origin.path.begin(), m_origin.path.end());
|
||||
return true;
|
||||
std::optional<CPubKey> pub = m_provider->GetPubKey(pos, arg, out, read_cache, write_cache);
|
||||
if (!pub) return std::nullopt;
|
||||
Assert(out.pubkeys.contains(pub->GetID()));
|
||||
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(); }
|
||||
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); }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
|
||||
{
|
||||
@ -272,9 +279,9 @@ public:
|
||||
}
|
||||
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
|
||||
{
|
||||
@ -296,24 +303,34 @@ class ConstPubkeyProvider final : public PubkeyProvider
|
||||
CPubKey m_pubkey;
|
||||
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:
|
||||
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;
|
||||
info.path.clear();
|
||||
KeyOriginInfo info;
|
||||
CKeyID keyid = m_pubkey.GetID();
|
||||
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; }
|
||||
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); }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
|
||||
{
|
||||
CKey key;
|
||||
if (!GetPrivKey(/*pos=*/0, arg, key)) return false;
|
||||
ret = EncodeSecret(key);
|
||||
std::optional<CKey> key = GetPrivKey(arg);
|
||||
if (!key) return false;
|
||||
ret = EncodeSecret(*key);
|
||||
return true;
|
||||
}
|
||||
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
|
||||
@ -321,10 +338,11 @@ public:
|
||||
ret = ToString(StringType::PUBLIC);
|
||||
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) :
|
||||
arg.GetKey(m_pubkey.GetID(), key);
|
||||
std::optional<CKey> key = GetPrivKey(arg);
|
||||
if (!key) return;
|
||||
out.keys.emplace(key->GetPubKey().GetID(), *key);
|
||||
}
|
||||
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) {}
|
||||
bool IsRange() const override { return m_derive != DeriveType::NO; }
|
||||
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
|
||||
KeyOriginInfo parent_info;
|
||||
KeyOriginInfo info;
|
||||
CKeyID keyid = m_root_extkey.pubkey.GetID();
|
||||
std::copy(keyid.begin(), keyid.begin() + sizeof(parent_info.fingerprint), parent_info.fingerprint);
|
||||
parent_info.path = m_path;
|
||||
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
|
||||
info.path = m_path;
|
||||
|
||||
// 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) final_info_out_tmp.path.push_back((uint32_t)pos);
|
||||
if (m_derive == DeriveType::HARDENED) final_info_out_tmp.path.push_back(((uint32_t)pos) | 0x80000000L);
|
||||
if (m_derive == DeriveType::UNHARDENED) info.path.push_back((uint32_t)pos);
|
||||
if (m_derive == DeriveType::HARDENED) info.path.push_back(((uint32_t)pos) | 0x80000000L);
|
||||
|
||||
// Derive keys or fetch them from cache
|
||||
CExtPubKey final_extkey = m_root_extkey;
|
||||
@ -414,16 +432,16 @@ public:
|
||||
bool der = true;
|
||||
if (read_cache) {
|
||||
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
|
||||
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;
|
||||
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
|
||||
}
|
||||
} else if (IsHardened()) {
|
||||
CExtKey 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();
|
||||
if (m_derive == DeriveType::UNHARDENED) der = xprv.Derive(xprv, pos);
|
||||
if (m_derive == DeriveType::HARDENED) der = xprv.Derive(xprv, pos | 0x80000000UL);
|
||||
@ -433,16 +451,16 @@ public:
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
|
||||
assert(m_derive != DeriveType::HARDENED);
|
||||
}
|
||||
if (!der) return false;
|
||||
if (!der) return std::nullopt;
|
||||
|
||||
final_info_out = final_info_out_tmp;
|
||||
key_out = final_extkey.pubkey;
|
||||
out.origins.emplace(final_extkey.pubkey.GetID(), std::make_pair(final_extkey.pubkey, info));
|
||||
out.pubkeys.emplace(final_extkey.pubkey.GetID(), final_extkey.pubkey);
|
||||
|
||||
if (write_cache) {
|
||||
// Only cache parent if there is any unhardened derivation
|
||||
@ -452,12 +470,12 @@ public:
|
||||
if (last_hardened_extkey.pubkey.IsValid()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return final_extkey.pubkey;
|
||||
}
|
||||
std::string ToString(StringType type, bool normalized) const
|
||||
{
|
||||
@ -543,15 +561,14 @@ public:
|
||||
}
|
||||
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 dummy;
|
||||
if (!GetDerivedExtKey(arg, extkey, dummy)) return false;
|
||||
if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return false;
|
||||
if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return false;
|
||||
key = extkey.key;
|
||||
return true;
|
||||
if (!GetDerivedExtKey(arg, extkey, dummy)) return;
|
||||
if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return;
|
||||
if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return;
|
||||
out.keys.emplace(extkey.key.GetPubKey().GetID(), extkey.key);
|
||||
}
|
||||
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. */
|
||||
class DescriptorImpl : public Descriptor
|
||||
{
|
||||
@ -700,16 +930,17 @@ public:
|
||||
// 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
|
||||
{
|
||||
std::vector<std::pair<CPubKey, KeyOriginInfo>> entries;
|
||||
entries.reserve(m_pubkey_args.size());
|
||||
FlatSigningProvider subprovider;
|
||||
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) {
|
||||
entries.emplace_back();
|
||||
if (!p->GetPubKey(pos, arg, entries.back().first, entries.back().second, read_cache, write_cache)) return false;
|
||||
std::optional<CPubKey> pubkey = p->GetPubKey(pos, arg, subprovider, read_cache, write_cache);
|
||||
if (!pubkey) return false;
|
||||
pubkeys.push_back(pubkey.value());
|
||||
}
|
||||
std::vector<CScript> subscripts;
|
||||
FlatSigningProvider subprovider;
|
||||
for (const auto& subarg : m_subdescriptor_args) {
|
||||
std::vector<CScript> outscripts;
|
||||
if (!subarg->ExpandHelper(pos, arg, read_cache, outscripts, subprovider, write_cache)) return false;
|
||||
@ -718,13 +949,6 @@ public:
|
||||
}
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
@ -744,8 +968,7 @@ public:
|
||||
{
|
||||
for (const auto& p : m_pubkey_args) {
|
||||
CKey key;
|
||||
if (!p->GetPrivKey(pos, provider, key)) continue;
|
||||
out.keys.emplace(key.GetPubKey().GetID(), key);
|
||||
p->GetPrivKey(pos, provider, out);
|
||||
}
|
||||
for (const auto& arg : m_subdescriptor_args) {
|
||||
arg->ExpandPrivate(pos, provider, out);
|
||||
@ -800,6 +1023,7 @@ public:
|
||||
return OutputTypeFromDestination(m_destination);
|
||||
}
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() 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(); }
|
||||
@ -827,6 +1051,7 @@ public:
|
||||
return OutputTypeFromDestination(dest);
|
||||
}
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() 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(); }
|
||||
@ -855,6 +1080,7 @@ protected:
|
||||
public:
|
||||
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 IsSingleKey() const final { return true; }
|
||||
|
||||
std::optional<int64_t> ScriptSize() const override {
|
||||
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
|
||||
{
|
||||
CKeyID id = keys[0].GetID();
|
||||
out.pubkeys.emplace(id, keys[0]);
|
||||
return Vector(GetScriptForDestination(PKHash(id)));
|
||||
}
|
||||
public:
|
||||
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
|
||||
std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
|
||||
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; }
|
||||
|
||||
@ -918,13 +1144,13 @@ protected:
|
||||
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
|
||||
{
|
||||
CKeyID id = keys[0].GetID();
|
||||
out.pubkeys.emplace(id, keys[0]);
|
||||
return Vector(GetScriptForDestination(WitnessV0KeyHash(id)));
|
||||
}
|
||||
public:
|
||||
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
|
||||
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() const final { return true; }
|
||||
|
||||
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; }
|
||||
|
||||
@ -953,7 +1179,6 @@ protected:
|
||||
{
|
||||
std::vector<CScript> ret;
|
||||
CKeyID id = keys[0].GetID();
|
||||
out.pubkeys.emplace(id, keys[0]);
|
||||
ret.emplace_back(GetScriptForRawPubKey(keys[0])); // P2PK
|
||||
ret.emplace_back(GetScriptForDestination(PKHash(id))); // P2PKH
|
||||
if (keys[0].IsCompressed()) {
|
||||
@ -971,6 +1196,7 @@ public:
|
||||
{
|
||||
return std::make_unique<ComboDescriptor>(m_pubkey_args.at(0)->Clone());
|
||||
}
|
||||
bool IsSingleKey() const final { return true; }
|
||||
};
|
||||
|
||||
/** A parsed multi(...) or sortedmulti(...) descriptor */
|
||||
@ -991,6 +1217,7 @@ protected:
|
||||
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) {}
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() const final { return false; }
|
||||
|
||||
std::optional<int64_t> ScriptSize() const override {
|
||||
const auto n_keys = m_pubkey_args.size();
|
||||
@ -1042,6 +1269,7 @@ protected:
|
||||
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) {}
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() const final { return false; }
|
||||
|
||||
std::optional<int64_t> ScriptSize() const override {
|
||||
const auto n_keys = m_pubkey_args.size();
|
||||
@ -1088,6 +1316,7 @@ public:
|
||||
return OutputType::LEGACY;
|
||||
}
|
||||
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; }
|
||||
|
||||
@ -1129,6 +1358,7 @@ public:
|
||||
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
|
||||
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
|
||||
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; }
|
||||
|
||||
@ -1175,7 +1405,6 @@ protected:
|
||||
builder.Finalize(xpk);
|
||||
WitnessV1Taproot output = builder.GetOutput();
|
||||
out.tr_trees[output] = builder;
|
||||
out.pubkeys.emplace(keys[0].GetID(), keys[0]);
|
||||
return Vector(GetScriptForDestination(output));
|
||||
}
|
||||
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; }
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() const final { return false; }
|
||||
|
||||
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
|
||||
|
||||
@ -1334,6 +1564,7 @@ public:
|
||||
|
||||
bool IsSolvable() const override { 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(); }
|
||||
|
||||
@ -1373,6 +1604,7 @@ public:
|
||||
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
|
||||
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool IsSingleKey() const final { return false; }
|
||||
|
||||
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)
|
||||
P2WSH, //!< Inside wsh() (script becomes v0 witness script)
|
||||
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;
|
||||
if (elem.size() > 0) {
|
||||
const char last = elem[elem.size() - 1];
|
||||
if (last == '\'' || last == 'h') {
|
||||
if (!allow_hardened) {
|
||||
error = "cannot have hardened derivation steps";
|
||||
return std::nullopt;
|
||||
}
|
||||
elem = elem.first(elem.size() - 1);
|
||||
hardened = true;
|
||||
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
|
||||
* @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;
|
||||
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) {
|
||||
const auto& op_num = ParseKeyPathNum(num, apostrophe, error);
|
||||
const auto& op_num = ParseKeyPathNum(num, apostrophe, error, allow_hardened);
|
||||
if (!op_num) return false;
|
||||
auto [_, inserted] = seen_multipath.insert(*op_num);
|
||||
if (!inserted) {
|
||||
@ -1479,7 +1716,7 @@ std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe,
|
||||
path.emplace_back(); // Placeholder for multipath segment
|
||||
multipath_segment_index = path.size()-1;
|
||||
} else {
|
||||
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error);
|
||||
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error, allow_hardened);
|
||||
if (!op_num) return false;
|
||||
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). */
|
||||
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;
|
||||
|
||||
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, ']');
|
||||
if (origin_split.size() > 2) {
|
||||
error = "Multiple ']' characters found for a single pubkey";
|
||||
@ -1691,7 +2071,8 @@ struct KeyParser {
|
||||
{
|
||||
assert(m_out);
|
||||
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 {};
|
||||
m_keys.emplace_back(std::move(pk));
|
||||
return key;
|
||||
|
@ -111,6 +111,11 @@ struct Descriptor {
|
||||
/** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
|
||||
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. */
|
||||
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
|
||||
|
||||
|
@ -12,10 +12,10 @@
|
||||
|
||||
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())) {
|
||||
sp = sp.subspan(str.size());
|
||||
if (skip) sp = sp.subspan(str.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -13,10 +13,10 @@ namespace script {
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -52,6 +52,11 @@ bool HidingSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, Tap
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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); }
|
||||
@ -82,6 +87,13 @@ bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, Tapro
|
||||
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;
|
||||
}
|
||||
|
||||
FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
|
||||
{
|
||||
scripts.merge(b.scripts);
|
||||
@ -89,6 +101,7 @@ FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
|
||||
keys.merge(b.keys);
|
||||
origins.merge(b.origins);
|
||||
tr_trees.merge(b.tr_trees);
|
||||
aggregate_pubkeys.merge(b.aggregate_pubkeys);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -161,6 +161,7 @@ public:
|
||||
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 GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; }
|
||||
virtual std::vector<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const { return {}; }
|
||||
|
||||
bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const
|
||||
{
|
||||
@ -204,6 +205,7 @@ public:
|
||||
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
|
||||
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
|
||||
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
|
||||
std::vector<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override;
|
||||
};
|
||||
|
||||
struct FlatSigningProvider final : public SigningProvider
|
||||
@ -213,6 +215,7 @@ struct FlatSigningProvider final : public SigningProvider
|
||||
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins;
|
||||
std::map<CKeyID, CKey> keys;
|
||||
std::map<XOnlyPubKey, TaprootBuilder> tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */
|
||||
std::map<CPubKey, std::vector<CPubKey>> aggregate_pubkeys; /** MuSig2 aggregate pubkeys */
|
||||
|
||||
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
|
||||
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
|
||||
@ -221,6 +224,7 @@ struct FlatSigningProvider final : public SigningProvider
|
||||
bool GetKey(const CKeyID& keyid, CKey& key) const override;
|
||||
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
|
||||
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
|
||||
std::vector<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override;
|
||||
|
||||
FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
|
||||
};
|
||||
|
@ -40,15 +40,17 @@ void CheckInferRaw(const CScript& script)
|
||||
}
|
||||
|
||||
constexpr int DEFAULT = 0;
|
||||
constexpr int RANGE = 1; // Expected to be ranged descriptor
|
||||
constexpr int HARDENED = 2; // Derivation needs access to private keys
|
||||
constexpr int UNSOLVABLE = 4; // 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 DERIVE_HARDENED = 16; // The final derivation is hardened, i.e. ends with *' or *h
|
||||
constexpr int MIXED_PUBKEYS = 32;
|
||||
constexpr int XONLY_KEYS = 64; // 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 SIGNABLE_FAILS = 256; // We can sign with this descriptor, but actually trying to sign will fail
|
||||
constexpr int RANGE = 1 << 0; // Expected to be ranged descriptor
|
||||
constexpr int HARDENED = 1 << 1; // Derivation needs access to private keys
|
||||
constexpr int UNSOLVABLE = 1 << 2; // This descriptor is not expected to be solvable
|
||||
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 = 1 << 4; // The final derivation is hardened, i.e. ends with *' or *h
|
||||
constexpr int MIXED_PUBKEYS = 1 << 5;
|
||||
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 = 1 << 7; // Not all private keys are available, so ToPrivateString will fail.
|
||||
constexpr int SIGNABLE_FAILS = 1 << 8; // We can sign with this descriptor, but actually trying to sign will fail
|
||||
constexpr int MUSIG = 1 << 9; // This is a MuSig so key counts will have an extra key
|
||||
constexpr int MUSIG_DERIVATION = 1 << 10; // MuSig with derivation from the aggregate key
|
||||
|
||||
/** Compare two descriptors. If only one of them has a checksum, the checksum is ignored. */
|
||||
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<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;
|
||||
for (const auto& [_, data] : provider.origins) {
|
||||
for (const auto& [keyid, data] : provider.origins) {
|
||||
if (ignored.contains(keyid)) continue;
|
||||
if (flags & XONLY_KEYS) {
|
||||
unsigned char bytes[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
|
||||
const auto& der_xpub_cache = desc_cache.GetCachedDerivedExtPubKeys();
|
||||
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))) {
|
||||
// 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
|
||||
@ -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;
|
||||
count_pks += pubkeys.count(pk);
|
||||
}
|
||||
if (flags & MIXED_PUBKEYS) {
|
||||
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
||||
if (flags & MUSIG_DERIVATION) {
|
||||
BOOST_CHECK_EQUAL(count_pks, 1);
|
||||
BOOST_CHECK_EQUAL(num_xpubs, pubkeys.size());
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
|
||||
if (flags & MUSIG) count_pks++; // One extra key for the aggregate key that is not in the cache
|
||||
if (flags & MIXED_PUBKEYS) {
|
||||
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
|
||||
}
|
||||
}
|
||||
} else if (num_xpubs > 0) {
|
||||
// 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)) {
|
||||
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
|
||||
std::set<CPubKey> pubkeys;
|
||||
@ -333,10 +354,17 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
|
||||
const CPubKey& pk = origin_pair.second.first;
|
||||
count_pks += pubkeys.count(pk);
|
||||
}
|
||||
if (flags & MIXED_PUBKEYS) {
|
||||
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
||||
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 {
|
||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
|
||||
if (flags & MUSIG) count_pks++; // One extra key for the aggregate key that is not in the cache
|
||||
if (flags & MIXED_PUBKEYS) {
|
||||
BOOST_CHECK_EQUAL(num_xpubs, count_pks);
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), count_pks);
|
||||
}
|
||||
}
|
||||
} else if (!(flags & MIXED_PUBKEYS)) {
|
||||
// Only const pubkeys, nothing should be cached
|
||||
@ -1071,6 +1099,96 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
|
||||
CheckInferDescriptor("76a914a31725c74421fadc50d35520ab8751ed120af80588ac", "pkh(04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31)", {}, {{"04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31", ""}});
|
||||
// Infer pk() from p2pk with uncompressed key
|
||||
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()
|
||||
|
@ -1091,6 +1091,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
|
||||
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();
|
||||
|
||||
for (size_t j = 0; j < parsed_descs.size(); ++j) {
|
||||
@ -1107,8 +1110,10 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
|
||||
std::vector<CScript> scripts_temp;
|
||||
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()));
|
||||
for (const auto& key_pair : out_keys.pubkeys) {
|
||||
ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
|
||||
if (can_keypool) {
|
||||
for (const auto& key_pair : out_keys.pubkeys) {
|
||||
ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& x : out_keys.scripts) {
|
||||
|
@ -26,6 +26,7 @@ public:
|
||||
bool IsRange() const override { return false; }
|
||||
bool IsSolvable() const override { return false; }
|
||||
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 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; };
|
||||
|
Loading…
x
Reference in New Issue
Block a user