mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-17 17:02:43 +00:00
Merge bitcoin/bitcoin#31244: descriptors: MuSig2
5fe7915c865a8e7c0a95ec376d0f1ff737f5d1c2 doc: Add musig() example (Ava Chow)
d576079ab470db4f500d0f2df5ddc77ab65e74cc tests: Test musig() parsing (Ava Chow)
a53924bee321f9d01d053cf562ee3d9493e00529 descriptor: Parse musig() key expressions (Ava Chow)
9473e9606ce7210a8912fb6e81e96ae35fdfb6ad descriptors: Move DeriveType parsing into its own function (Ava Chow)
4af0dca096ca497a6b4e5314c9edea683efe620e descriptor: Add MuSigPubkeyProvider (Ava Chow)
d00d95437dd113a23ccd556c25a77bb04bce23f7 Add MuSig2 Keyagg Cache helper functions (Ava Chow)
8ecea91bf296b8fae8b84c3dbf68d5703821cb79 sign: Add GetMuSig2ParticipantPubkeys to SigningProvider (Ava Chow)
fac0ee0bfc910a82678a3f8ec13c47967fd7def2 build: Enable secp256k1 musig module (Ava Chow)
1894f975032013ef855c438654fbb745512e7982 descriptors: Add PubkeyProvider::IsBIP32() (Ava Chow)
12bc1d0b1e9681c338c9d0df0bbac1d4a3162322 util/string: Allow Split to include the separator (Ava Chow)
88113125716c50ce4deb864041840d53a567554c script/parsing: Allow Const to not skip the found constant (Ava Chow)
5fe4c66462e6149c2ed3ce24224a7a7b328a2cfa XOnlyPubKey: Add GetCPubKeys (Ava Chow)
Pull request description:
Implements parsing of BIP 390 `musig()` descriptors.
Split from #29675
ACKs for top commit:
w0xlt:
reACK 5fe7915c86
rkrux:
ACK 5fe7915c865a8e7c0a95ec376d0f1ff737f5d1c2
theStack:
re-ACK 5fe7915c865a8e7c0a95ec376d0f1ff737f5d1c2 🎹
Sjors:
ACK 5fe7915c865a8e7c0a95ec376d0f1ff737f5d1c2
Tree-SHA512: a5be6288e277187fb9a1e2adf4e9822b46b1b8380d732b2fabd53f317c839aecb1971b04410486cbd65047fbc20956675d4d676f56caa37a44ff0e4d12b9b081
This commit is contained in:
commit
5ee4e79669
@ -69,6 +69,7 @@ Output descriptors currently support:
|
||||
- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,sortedmulti_a(2,2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc))` describes a P2TR output with the `c6...` x-only pubkey as internal key, and a single `multi_a` script that needs 2 signatures with 2 specified x-only keys, which will be sorted lexicographically.
|
||||
- `wsh(sortedmulti(2,[6f53d49c/44h/1h/0h]tpubDDjsCRDQ9YzyaAq9rspCfq8RZFrWoBpYnLxK6sS2hS2yukqSczgcYiur8Scx4Hd5AZatxTuzMtJQJhchufv1FRFanLqUP7JHwusSSpfcEp2/0/*,[e6807791/44h/1h/0h]tpubDDAfvogaaAxaFJ6c15ht7Tq6ZmiqFYfrSmZsHu7tHXBgnjMZSHAeHSwhvjARNA6Qybon4ksPksjRbPDVp7yXA1KjTjSd5x18KHqbppnXP1s/0/*,[367c9cfa/44h/1h/0h]tpubDDtPnSgWYk8dDnaDwnof4ehcnjuL5VoUt1eW2MoAed1grPHuXPDnkX1fWMvXfcz3NqFxPbhqNZ3QBdYjLz2hABeM9Z2oqMR1Gt2HHYDoCgh/0/*))#av0kxgw0` describes a *2-of-3* multisig. For brevity, the internal "change" descriptor accompanying the above external "receiving" descriptor is not included here, but it typically differs only in the xpub derivation steps, ending in `/1/*` for change addresses.
|
||||
- `wsh(thresh(4,pk([7258e4f9/44h/1h/0h]tpubDCZrkQoEU3845aFKUu9VQBYWZtrTwxMzcxnBwKFCYXHD6gEXvtFcxddCCLFsEwmxQaG15izcHxj48SXg1QS5FQGMBx5Ak6deXKPAL7wauBU/0/*),s:pk([c80b1469/44h/1h/0h]tpubDD3UwwHoNUF4F3Vi5PiUVTc3ji1uThuRfFyBexTSHoAcHuWW2z8qEE2YujegcLtgthr3wMp3ZauvNG9eT9xfJyxXCfNty8h6rDBYU8UU1qq/0/*),s:pk([4e5024fe/44h/1h/0h]tpubDDLrpPymPLSCJyCMLQdmcWxrAWwsqqssm5NdxT2WSdEBPSXNXxwbeKtsHAyXPpLkhUyKovtZgCi47QxVpw9iVkg95UUgeevyAqtJ9dqBqa1/0/*),s:pk([3b1d1ee9/44h/1h/0h]tpubDCmDTANBWPzf6d8Ap1J5Ku7J1Ay92MpHMrEV7M5muWxCrTBN1g5f1NPcjMEL6dJHxbvEKNZtYCdowaSTN81DAyLsmv6w6xjJHCQNkxrsrfu/0/*),sln:after(840000),sln:after(1050000),sln:after(1260000)))#k28080kv` describes a Miniscript multisig with spending policy: `thresh(4,pk(key_1),pk(key_2),pk(key_3),pk(key_4),after(t1),after(t2),after(t3))` that starts as 4-of-4 and "decays" to 3-of-4, 2-of-4, and finally 1-of-4 at each future halvening block height. For brevity, the internal "change" descriptor accompanying the above external "receiving" descriptor is not included here, but it typically differs only in the xpub derivation steps, ending in `/1/*` for change addresses.
|
||||
- `tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)` describes a MuSig2 multisig with key derivation. The internal keys are derived at `m/0/*` from the aggregate key computed from the 2 participants.
|
||||
|
||||
## Reference
|
||||
|
||||
|
||||
@ -31,7 +31,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)
|
||||
@ -161,6 +161,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);
|
||||
}
|
||||
26
src/musig.h
Normal file
26
src/musig.h
Normal file
@ -0,0 +1,26 @@
|
||||
// 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 <optional>
|
||||
#include <vector>
|
||||
|
||||
struct secp256k1_musig_keyagg_cache;
|
||||
|
||||
//! MuSig2 chaincode as defined by BIP 328
|
||||
using namespace util::hex_literals;
|
||||
constexpr uint256 MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8};
|
||||
|
||||
//! Create a secp256k1_musig_keyagg_cache from the pubkeys in their current order. This is necessary for most MuSig2 operations
|
||||
bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache);
|
||||
//! Retrieve the full aggregate pubkey from the secp256k1_musig_keyagg_cache
|
||||
std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& cache);
|
||||
//! Compute the full aggregate pubkey from the given participant pubkeys in their current order
|
||||
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys);
|
||||
|
||||
#endif // BITCOIN_MUSIG_H
|
||||
@ -197,20 +197,26 @@ constexpr XOnlyPubKey XOnlyPubKey::NUMS_H{
|
||||
[]() consteval { return XOnlyPubKey{"50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"_hex_u8}; }(),
|
||||
};
|
||||
|
||||
std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const
|
||||
std::vector<CPubKey> XOnlyPubKey::GetCPubKeys() 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.
|
||||
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.GetID());
|
||||
out.push_back(fullpubkey);
|
||||
b[0] = 0x03;
|
||||
fullpubkey.Set(b, b + 33);
|
||||
out.push_back(fullpubkey.GetID());
|
||||
out.push_back(fullpubkey);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const
|
||||
{
|
||||
std::vector<CKeyID> out;
|
||||
for (const CPubKey& pk : GetCPubKeys()) {
|
||||
out.push_back(pk.GetID());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@ -283,9 +283,13 @@ public:
|
||||
std::optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;
|
||||
|
||||
/** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey.
|
||||
* As the CKeyID is the Hash160(full pubkey), the produced CKeyIDs are for the versions of this
|
||||
* XOnlyPubKey with 0x02 and 0x03 prefixes.
|
||||
* This is needed for key lookups since keys are indexed by CKeyID.
|
||||
*/
|
||||
std::vector<CKeyID> GetKeyIDs() const;
|
||||
/** Returns this XOnlyPubKey with 0x02 and 0x03 prefixes */
|
||||
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>
|
||||
@ -221,6 +222,9 @@ public:
|
||||
|
||||
/** Make a deep copy of this PubkeyProvider */
|
||||
virtual std::unique_ptr<PubkeyProvider> Clone() const = 0;
|
||||
|
||||
/** Whether this PubkeyProvider is a BIP 32 extended key that can be derived from */
|
||||
virtual bool IsBIP32() const = 0;
|
||||
};
|
||||
|
||||
class OriginPubkeyProvider final : public PubkeyProvider
|
||||
@ -251,6 +255,7 @@ public:
|
||||
}
|
||||
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
|
||||
{
|
||||
@ -319,6 +324,7 @@ public:
|
||||
}
|
||||
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
|
||||
{
|
||||
@ -406,6 +412,7 @@ 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 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
|
||||
{
|
||||
KeyOriginInfo info;
|
||||
@ -574,6 +581,214 @@ 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
|
||||
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;
|
||||
const bool m_ranged_participants;
|
||||
|
||||
bool IsRangedDerivation() const { return m_derive != DeriveType::NO; }
|
||||
|
||||
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),
|
||||
m_ranged_participants(std::any_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsRange(); }))
|
||||
{
|
||||
if (!Assume(!(m_ranged_participants && IsRangedDerivation()))) {
|
||||
throw std::runtime_error("musig(): Cannot have both ranged participants and ranged derivation");
|
||||
}
|
||||
if (!Assume(m_derive != DeriveType::HARDENED)) {
|
||||
throw std::runtime_error("musig(): Cannot have hardened derivation");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
|
||||
{
|
||||
FlatSigningProvider dummy;
|
||||
// If the participants are not ranged, we can compute and cache the aggregate pubkey by creating a PubkeyProvider for it
|
||||
if (!m_aggregate_provider && !m_ranged_participants) {
|
||||
// Retrieve the pubkeys from the providers
|
||||
std::vector<CPubKey> pubkeys;
|
||||
for (const auto& prov : m_participants) {
|
||||
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);
|
||||
if (!Assume(m_aggregate_pubkey.has_value())) return std::nullopt;
|
||||
|
||||
// Make our pubkey provider
|
||||
if (IsRangedDerivation() || !m_path.empty()) {
|
||||
// Make the synthetic xpub and construct the BIP32PubkeyProvider
|
||||
CExtPubKey extpub;
|
||||
extpub.nDepth = 0;
|
||||
std::memset(extpub.vchFingerprint, 0, 4);
|
||||
extpub.nChild = 0;
|
||||
extpub.chaincode = MUSIG_CHAINCODE;
|
||||
extpub.pubkey = m_aggregate_pubkey.value();
|
||||
|
||||
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 its GetPubKey
|
||||
// Use a dummy signing provider as private keys do not exist for the aggregate pubkey
|
||||
std::optional<CPubKey> pub = m_aggregate_provider->GetPubKey(pos, dummy, out, read_cache, write_cache);
|
||||
if (!pub) return std::nullopt;
|
||||
pubout = *pub;
|
||||
out.aggregate_pubkeys.emplace(m_aggregate_pubkey.value(), pubkeys);
|
||||
} else {
|
||||
if (!Assume(m_ranged_participants) || !Assume(m_path.empty())) return std::nullopt;
|
||||
// Compute aggregate key from derived participants
|
||||
std::optional<CPubKey> aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
|
||||
if (!aggregate_pubkey) return std::nullopt;
|
||||
pubout = *aggregate_pubkey;
|
||||
|
||||
std::unique_ptr<ConstPubkeyProvider> this_agg_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, aggregate_pubkey.value(), /*xonly=*/false);
|
||||
this_agg_provider->GetPubKey(0, dummy, out, read_cache, write_cache);
|
||||
out.aggregate_pubkeys.emplace(pubout, pubkeys);
|
||||
}
|
||||
|
||||
if (!Assume(pubout.IsValid())) return std::nullopt;
|
||||
return pubout;
|
||||
}
|
||||
bool IsRange() const override { return IsRangedDerivation() || m_ranged_participants; }
|
||||
// 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 += ",";
|
||||
out += pubkey->ToString(type);
|
||||
}
|
||||
out += ")";
|
||||
out += FormatHDKeypath(m_path);
|
||||
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);
|
||||
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, cache)) {
|
||||
return false;
|
||||
}
|
||||
out += tmp;
|
||||
}
|
||||
out += ")";
|
||||
out += FormatHDKeypath(m_path);
|
||||
if (IsRangedDerivation()) {
|
||||
out += "/*";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
|
||||
{
|
||||
// Get the private keys for any participants that we have
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Get RootPubKey and GetRootExtPubKey are used to return the single pubkey underlying the pubkey provider
|
||||
// to be presented to the user in gethdkeys. As this is a multisig construction, there is no single underlying
|
||||
// pubkey hence nothing should be returned.
|
||||
// While the aggregate pubkey could be returned as the root (ext)pubkey, it is not a pubkey that anyone should
|
||||
// be using by itself in a descriptor as it is unspendable without knowing its participants.
|
||||
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
|
||||
return std::all_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsBIP32(); });
|
||||
}
|
||||
};
|
||||
|
||||
/** Base class for all Descriptor implementations. */
|
||||
class DescriptorImpl : public Descriptor
|
||||
{
|
||||
@ -1397,9 +1612,10 @@ 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(std::span<const char> elem, bool& apostrophe, std::string& error)
|
||||
std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostrophe, std::string& error, bool& has_hardened)
|
||||
{
|
||||
bool hardened = false;
|
||||
if (elem.size() > 0) {
|
||||
@ -1418,6 +1634,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
error = strprintf("Key path value %u is out of range", *p);
|
||||
return std::nullopt;
|
||||
}
|
||||
has_hardened = has_hardened || hardened;
|
||||
|
||||
return std::make_optional<uint32_t>(*p | (((uint32_t)hardened) << 31));
|
||||
}
|
||||
@ -1432,7 +1649,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
* @param[in] allow_multipath Allows the parsed path to use the multipath specifier
|
||||
* @returns false if parsing failed
|
||||
**/
|
||||
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath)
|
||||
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath, bool& has_hardened)
|
||||
{
|
||||
KeyPath path;
|
||||
struct MultipathSubstitutes {
|
||||
@ -1440,6 +1657,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
std::vector<uint32_t> values;
|
||||
};
|
||||
std::optional<MultipathSubstitutes> substitutes;
|
||||
has_hardened = false;
|
||||
|
||||
for (size_t i = 1; i < split.size(); ++i) {
|
||||
const std::span<const char>& elem = split[i];
|
||||
@ -1465,7 +1683,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
substitutes.emplace();
|
||||
std::unordered_set<uint32_t> seen_substitutes;
|
||||
for (const auto& num : nums) {
|
||||
const auto& op_num = ParseKeyPathNum(num, apostrophe, error);
|
||||
const auto& op_num = ParseKeyPathNum(num, apostrophe, error, has_hardened);
|
||||
if (!op_num) return false;
|
||||
auto [_, inserted] = seen_substitutes.insert(*op_num);
|
||||
if (!inserted) {
|
||||
@ -1478,7 +1696,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
path.emplace_back(); // Placeholder for multipath segment
|
||||
substitutes->placeholder_index = path.size() - 1;
|
||||
} else {
|
||||
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error);
|
||||
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error, has_hardened);
|
||||
if (!op_num) return false;
|
||||
path.emplace_back(*op_num);
|
||||
}
|
||||
@ -1497,6 +1715,26 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath)
|
||||
{
|
||||
bool dummy;
|
||||
return ParseKeyPath(split, out, apostrophe, error, allow_multipath, /*has_hardened=*/dummy);
|
||||
}
|
||||
|
||||
static DeriveType ParseDeriveType(std::vector<std::span<const char>>& split, bool& apostrophe)
|
||||
{
|
||||
DeriveType type = DeriveType::NO;
|
||||
if (std::ranges::equal(split.back(), std::span{"*"}.first(1))) {
|
||||
split.pop_back();
|
||||
type = DeriveType::UNHARDENED;
|
||||
} else if (std::ranges::equal(split.back(), std::span{"*'"}.first(2)) || std::ranges::equal(split.back(), std::span{"*h"}.first(2))) {
|
||||
apostrophe = std::ranges::equal(split.back(), std::span{"*'"}.first(2));
|
||||
split.pop_back();
|
||||
type = DeriveType::HARDENED;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Parse a public key that excludes origin information. */
|
||||
std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_index, const std::span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool& apostrophe, std::string& error)
|
||||
{
|
||||
@ -1560,15 +1798,7 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
|
||||
return {};
|
||||
}
|
||||
std::vector<KeyPath> paths;
|
||||
DeriveType type = DeriveType::NO;
|
||||
if (std::ranges::equal(split.back(), std::span{"*"}.first(1))) {
|
||||
split.pop_back();
|
||||
type = DeriveType::UNHARDENED;
|
||||
} else if (std::ranges::equal(split.back(), std::span{"*'"}.first(2)) || std::ranges::equal(split.back(), std::span{"*h"}.first(2))) {
|
||||
apostrophe = std::ranges::equal(split.back(), std::span{"*'"}.first(2));
|
||||
split.pop_back();
|
||||
type = DeriveType::HARDENED;
|
||||
}
|
||||
DeriveType type = ParseDeriveType(split, apostrophe);
|
||||
if (!ParseKeyPath(split, paths, apostrophe, error, /*allow_multipath=*/true)) return {};
|
||||
if (extkey.key.IsValid()) {
|
||||
extpubkey = extkey.Neuter();
|
||||
@ -1581,9 +1811,154 @@ 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 std::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 std::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
|
||||
std::span<const char> span = sp;
|
||||
if (Const("musig(", span, /*skip=*/false)) {
|
||||
if (ctx != ParseScriptContext::P2TR) {
|
||||
error = "musig() is only allowed in tr() and rawtr()";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Split the span on the end parentheses. The end parentheses must
|
||||
// be included in the resulting span so that Expr is happy.
|
||||
auto split = Split(sp, ')', /*include_sep=*/true);
|
||||
if (split.size() > 2) {
|
||||
error = "Too many ')' in musig() expression";
|
||||
return {};
|
||||
}
|
||||
std::span<const char> expr(split.at(0).begin(), split.at(0).end());
|
||||
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 any_key_parsed = true;
|
||||
size_t max_multipath_len = 0;
|
||||
while (expr.size()) {
|
||||
if (!any_key_parsed && !Const(",", expr)) {
|
||||
error = strprintf("musig(): expected ',', got '%c'", expr[0]);
|
||||
return {};
|
||||
}
|
||||
any_key_parsed = false;
|
||||
auto arg = Expr(expr);
|
||||
auto pk = ParsePubkey(key_exp_index, arg, ParseScriptContext::MUSIG, out, error);
|
||||
if (pk.empty()) {
|
||||
error = strprintf("musig(): %s", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
any_ranged = any_ranged || pk.at(0)->IsRange();
|
||||
all_bip32 = all_bip32 && pk.at(0)->IsBIP32();
|
||||
|
||||
max_multipath_len = std::max(max_multipath_len, pk.size());
|
||||
|
||||
providers.emplace_back(std::move(pk));
|
||||
key_exp_index++;
|
||||
}
|
||||
if (any_key_parsed) {
|
||||
error = "musig(): Must contain key expressions";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Parse any derivation
|
||||
DeriveType deriv_type = DeriveType::NO;
|
||||
std::vector<KeyPath> derivation_multipaths;
|
||||
if (split.size() == 2 && Const("/", split.at(1), /*skip=*/false)) {
|
||||
if (!all_bip32) {
|
||||
error = "musig(): derivation requires all participants to be xpubs or xprvs";
|
||||
return {};
|
||||
}
|
||||
if (any_ranged) {
|
||||
error = "musig(): Cannot have ranged participant keys if musig() also has derivation";
|
||||
return {};
|
||||
}
|
||||
bool dummy = false;
|
||||
auto deriv_split = Split(split.at(1), '/');
|
||||
deriv_type = ParseDeriveType(deriv_split, dummy);
|
||||
if (deriv_type == DeriveType::HARDENED) {
|
||||
error = "musig(): Cannot have hardened child derivation";
|
||||
return {};
|
||||
}
|
||||
bool has_hardened = false;
|
||||
if (!ParseKeyPath(deriv_split, derivation_multipaths, dummy, error, /*allow_multipath=*/true, has_hardened)) {
|
||||
error = "musig(): " + error;
|
||||
return {};
|
||||
}
|
||||
if (has_hardened) {
|
||||
error = "musig(): cannot have hardened derivation steps";
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
derivation_multipaths.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& multipath_providers : providers) {
|
||||
if (multipath_providers.size() == 1) {
|
||||
for (size_t i = 1; i < length; ++i) {
|
||||
multipath_providers.emplace_back(multipath_providers.at(0)->Clone());
|
||||
}
|
||||
} else if (multipath_providers.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, &derivation_multipaths, &providers](size_t vec_idx, size_t path_idx) -> void {
|
||||
KeyPath& path = derivation_multipaths.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_multipath_len > 1 && derivation_multipaths.size() > 1) {
|
||||
error = "musig(): Cannot have multipath participant keys if musig() is also multipath";
|
||||
return {};
|
||||
} else if (max_multipath_len > 1) {
|
||||
if (!clone_providers(max_multipath_len)) {
|
||||
error = strprintf("musig(): Multipath derivation paths have mismatched lengths");
|
||||
return {};
|
||||
}
|
||||
for (size_t i = 0; i < max_multipath_len; ++i) {
|
||||
// Final MuSigPubkeyProvider uses participant pubkey providers at each multipath position, and the first (and only) path
|
||||
emplace_final_provider(i, 0);
|
||||
}
|
||||
} else if (derivation_multipaths.size() > 1) {
|
||||
// All key provider vectors should be length 1. Clone them until they have the same length as paths
|
||||
if (!Assume(clone_providers(derivation_multipaths.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 < derivation_multipaths.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";
|
||||
@ -1694,7 +2069,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;
|
||||
|
||||
@ -12,10 +12,10 @@
|
||||
|
||||
namespace script {
|
||||
|
||||
bool Const(const std::string& str, std::span<const char>& sp)
|
||||
bool Const(const std::string& str, std::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, std::span<const char>& sp);
|
||||
bool Const(const std::string& str, std::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::GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const
|
||||
{
|
||||
if (m_hide_origin) return {};
|
||||
return m_provider->GetMuSig2ParticipantPubkeys(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::GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const
|
||||
{
|
||||
std::vector<CPubKey> participant_pubkeys;
|
||||
LookupHelper(aggregate_pubkeys, pubkey, participant_pubkeys);
|
||||
return participant_pubkeys;
|
||||
}
|
||||
|
||||
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> GetMuSig2ParticipantPubkeys(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> GetMuSig2ParticipantPubkeys(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> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
|
||||
|
||||
FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -41,15 +42,19 @@ 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
|
||||
constexpr int MIXED_MUSIG = 1 << 11; // Both MuSig and normal key expressions are present
|
||||
constexpr int UNIQUE_XPUBS = 1 << 12; // Whether the xpub count should be of unique xpubs
|
||||
|
||||
/** Compare two descriptors. If only one of them has a checksum, the checksum is ignored. */
|
||||
bool EqualDescriptor(std::string a, std::string b)
|
||||
@ -101,6 +106,19 @@ static size_t CountXpubs(const std::string& desc)
|
||||
}
|
||||
return count;
|
||||
}
|
||||
//
|
||||
// Count the number of unique xpubs in a descriptor string
|
||||
static size_t CountUniqueXpubs(const std::string& desc)
|
||||
{
|
||||
std::regex xpub_regex("xpub\\w+?(?=/)");
|
||||
auto search_begin = std::sregex_iterator(desc.begin(), desc.end(), xpub_regex);
|
||||
auto search_end = std::sregex_iterator();
|
||||
std::set<std::string> xpubs;
|
||||
for (std::regex_iterator i = search_begin; i != search_end; ++i) {
|
||||
xpubs.emplace(i->str());
|
||||
}
|
||||
return xpubs.size();
|
||||
}
|
||||
|
||||
const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}};
|
||||
|
||||
@ -122,8 +140,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);
|
||||
@ -284,7 +312,12 @@ 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);
|
||||
size_t num_unique_xpubs = CountUniqueXpubs(pub1);
|
||||
if (flags & MUSIG_DERIVATION) {
|
||||
num_xpubs++;
|
||||
num_unique_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
|
||||
@ -302,16 +335,28 @@ 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) {
|
||||
if (!(flags & MIXED_MUSIG)) {
|
||||
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());
|
||||
if (flags & UNIQUE_XPUBS) {
|
||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), num_unique_xpubs);
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(script_provider_cached.origins.size(), num_xpubs);
|
||||
}
|
||||
}
|
||||
// Get all of the derived pubkeys
|
||||
std::set<CPubKey> pubkeys;
|
||||
@ -334,10 +379,21 @@ 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());
|
||||
if (flags & UNIQUE_XPUBS) {
|
||||
BOOST_CHECK_EQUAL(2 * num_unique_xpubs, pubkeys.size());
|
||||
} else {
|
||||
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
|
||||
@ -1078,6 +1134,128 @@ 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}});
|
||||
Check("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1)/2)", "tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1)/2)", "tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1)/2)", XONLY_KEYS | MUSIG | MUSIG_DERIVATION | UNIQUE_XPUBS, {{"5120a17ceacd6422bd5ffd9f165807b254b7d68ad39f179cc4f11545a6835227e97c"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, {{1}, {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}},
|
||||
}
|
||||
);
|
||||
CheckMultipath("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/6/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/<7;8;9>/*))",
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/<7;8;9>/*))",
|
||||
{
|
||||
"tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/6/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/7/*))",
|
||||
"tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/6/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/8/*))",
|
||||
"tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L,xprv9s21ZrQH143K3jUwNHoqQNrtzJnJmx4Yup8NkNLdVQCymYbPbJXnPhwkfTfxZfptcs3rLAPUXS39oDLgrNKQGwbGsEmJJ8BU3RzQuvShEG4)/6/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/9/*))",
|
||||
},
|
||||
{
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/7/*))",
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/8/*))",
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/9/*))",
|
||||
},
|
||||
{
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/7/*))",
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/8/*))",
|
||||
"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y,xpub661MyMwAqRbcGDZQUKLqmWodYLcoBQnQH33yYkkF3jjxeLvY8qr2wWGEWkiKFaaQfJCoi3HeEq3Dc5DptfbCyjD38fNhSqtKc1UHaP4ba3t)/6/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/9/*))",
|
||||
},
|
||||
XONLY_KEYS | RANGE | MUSIG | MUSIG_DERIVATION | MIXED_MUSIG,
|
||||
{
|
||||
{{"5120682fd07aa0f18643fd2c8a2411a0525b5391d7ad54e1904c9c0d7f524e49b39c"},{"51200410a642ea6b9ab884ba892205f484ad716cf0d8426dd739bd67d1636cdce870"},{"5120971dc3136dd90be8aa879d9b0f5c449b62e738cbf7129623098bb3b3aa57eb7d"}},
|
||||
{{"51208e4158b3e54e32b4ad22f6dffb9f7968c92dfe96fd0b8fd3d30d3e2558a2a694"},{"512094273ac6b6f8ac060ba5681ba0906e54a51ab67fd08092e9b2af568b82aa1c7c"},{"5120ef0c7915708eb5baa95125e75417306391f339cfc533a19000ab8f2f53da78c1"}},
|
||||
{{"5120096d8a1f2091b8e8afa24eafd03b714acaea6a14df5cb673b02c7215b7764aeb"},{"512011833d6e7a6531b40c3a8beb5f59793f6dd10f216384630d78cb3f6319c964f1"},{"5120853417543de5ae914e5cc9de9e52342f253a11faada92148f8b0832650f4eb2f"}},
|
||||
},
|
||||
OutputType::BECH32M,
|
||||
{
|
||||
{{}, {6, 0}, {6, 1}, {6, 2}, {7, 0}, {7, 1}, {7, 2}},
|
||||
{{}, {6, 0}, {6, 1}, {6, 2}, {8, 0}, {8, 1}, {8, 2}},
|
||||
{{}, {6, 0}, {6, 1}, {6, 2}, {9, 0}, {9, 1}, {9, 2}},
|
||||
}
|
||||
);
|
||||
|
||||
// MuSig2 Parsing Failures
|
||||
CheckUnparsable("pk(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pk(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pk(): musig() is only allowed in tr() and rawtr()");
|
||||
CheckUnparsable("pkh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pkh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "pkh(): musig() is only allowed in tr() and rawtr()");
|
||||
CheckUnparsable("wpkh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "wpkh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "wpkh(): musig() is only allowed in tr() and rawtr()");
|
||||
CheckUnparsable("combo(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "combo(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))", "combo(): musig() is only allowed in tr() and rawtr()");
|
||||
CheckUnparsable("sh(wpkh(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)))", "sh(wpkh(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)))", "wpkh(): musig() is only allowed in tr() and rawtr()");
|
||||
CheckUnparsable("sh(wsh(pk(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)))", "sh(wsh(pk(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66))))", "pk(): musig() is only allowed in tr() and rawtr()");
|
||||
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(): derivation requires all participants to be xpubs or xprvs");
|
||||
CheckUnparsable("tr(musig(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)/0/0)", "tr(musig(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9,03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)/0/0)", "tr(): musig(): derivation requires all participants to be xpubs or xprvs");
|
||||
CheckUnparsable("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)", "tr(): musig(): Cannot have ranged participant keys if musig() also has derivation");
|
||||
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");
|
||||
CheckUnparsable("tr(musig(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/*)/0)","tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/*)/0)", "tr(): musig(): Cannot have ranged participant keys if musig() also has derivation");
|
||||
|
||||
// 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()
|
||||
|
||||
@ -1137,13 +1137,24 @@ BOOST_AUTO_TEST_CASE(test_script_parsing)
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "MilkToastHoney");
|
||||
|
||||
success = Const("Milk", sp, /*skip=*/false);
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "MilkToastHoney");
|
||||
|
||||
success = Const("Milk", sp);
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "ToastHoney");
|
||||
|
||||
success = Const("Bread", sp, /*skip=*/false);
|
||||
BOOST_CHECK(!success);
|
||||
|
||||
success = Const("Bread", sp);
|
||||
BOOST_CHECK(!success);
|
||||
|
||||
success = Const("Toast", sp, /*skip=*/false);
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "ToastHoney");
|
||||
|
||||
success = Const("Toast", sp);
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "Honey");
|
||||
@ -1151,10 +1162,13 @@ BOOST_AUTO_TEST_CASE(test_script_parsing)
|
||||
success = Const("Honeybadger", sp);
|
||||
BOOST_CHECK(!success);
|
||||
|
||||
success = Const("Honey", sp, /*skip=*/false);
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "Honey");
|
||||
|
||||
success = Const("Honey", sp);
|
||||
BOOST_CHECK(success);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(sp), "");
|
||||
|
||||
// Func(...): parse a function call, update span to argument if successful
|
||||
input = "Foo(Bar(xy,z()))";
|
||||
sp = input;
|
||||
@ -1232,6 +1246,12 @@ BOOST_AUTO_TEST_CASE(test_script_parsing)
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "two");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "three");
|
||||
|
||||
results = Split(input, '#', /*include_sep=*/true);
|
||||
BOOST_CHECK_EQUAL(results.size(), 3U);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one#");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "two#");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "three");
|
||||
|
||||
input = "*foo*bar*";
|
||||
results = Split(input, '*');
|
||||
BOOST_CHECK_EQUAL(results.size(), 4U);
|
||||
@ -1239,6 +1259,13 @@ BOOST_AUTO_TEST_CASE(test_script_parsing)
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "foo");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "bar");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
|
||||
|
||||
results = Split(input, '*', /*include_sep=*/true);
|
||||
BOOST_CHECK_EQUAL(results.size(), 4U);
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[0]), "*");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "foo*");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "bar*");
|
||||
BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_SplitString)
|
||||
|
||||
@ -100,18 +100,30 @@ void ReplaceAll(std::string& in_out, const std::string& search, const std::strin
|
||||
*
|
||||
* If sep does not occur in sp, a singleton with the entirety of sp is returned.
|
||||
*
|
||||
* @param[in] include_sep Whether to include the separator at the end of the left side of the splits.
|
||||
*
|
||||
* Note that this function does not care about braces, so splitting
|
||||
* "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
|
||||
*
|
||||
* If include_sep == true, splitting "foo(bar(1),2),3) on ','
|
||||
* will return:
|
||||
* - foo(bar(1),
|
||||
* - 2),
|
||||
* - 3)
|
||||
*/
|
||||
template <typename T = std::span<const char>>
|
||||
std::vector<T> Split(const std::span<const char>& sp, std::string_view separators)
|
||||
std::vector<T> Split(const std::span<const char>& sp, std::string_view separators, bool include_sep = false)
|
||||
{
|
||||
std::vector<T> ret;
|
||||
auto it = sp.begin();
|
||||
auto start = it;
|
||||
while (it != sp.end()) {
|
||||
if (separators.find(*it) != std::string::npos) {
|
||||
ret.emplace_back(start, it);
|
||||
if (include_sep) {
|
||||
ret.emplace_back(start, it + 1);
|
||||
} else {
|
||||
ret.emplace_back(start, it);
|
||||
}
|
||||
start = it + 1;
|
||||
}
|
||||
++it;
|
||||
@ -128,9 +140,9 @@ std::vector<T> Split(const std::span<const char>& sp, std::string_view separator
|
||||
* "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
|
||||
*/
|
||||
template <typename T = std::span<const char>>
|
||||
std::vector<T> Split(const std::span<const char>& sp, char sep)
|
||||
std::vector<T> Split(const std::span<const char>& sp, char sep, bool include_sep = false)
|
||||
{
|
||||
return Split<T>(sp, std::string_view{&sep, 1});
|
||||
return Split<T>(sp, std::string_view{&sep, 1}, include_sep);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user