mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-25 10:43:19 -03:00
Merge bitcoin/bitcoin#24148: Miniscript support in Output Descriptors
ffc79b8e49
qa: functional test Miniscript watchonly support (Antoine Poinsot)bfb036756a
Miniscript support in output descriptors (Antoine Poinsot)4a082887be
qa: better error reporting on descriptor parsing error (Antoine Poinsot)d25d58bf5f
miniscript: add a helper to find the first insane sub with no child (Antoine Poinsot)c38c7c5817
miniscript: don't check for top level validity at parsing time (Antoine Poinsot) Pull request description: This adds Miniscript support for Output Descriptors without any signing logic (yet). See the OP of #24147 for a description of Miniscript and a rationale of having it in Bitcoin Core. On its own, this PR adds "watchonly" support for Miniscript descriptors in the descriptor wallet. A follow-up adds signing support. A minified corpus of Miniscript Descriptors for the `descriptor_parse` fuzz target is available at https://github.com/bitcoin-core/qa-assets/pull/92. The Miniscript descriptors used in the unit tests here and in #24149 were cross-tested against the Rust implementation at https://github.com/rust-bitcoin/rust-miniscript. This PR contains code and insights from Pieter Wuille. ACKs for top commit: Sjors: re-utACKffc79b8e49
achow101: ACKffc79b8e49
w0xlt: reACKffc79b8e49
Tree-SHA512: 02d919d38bb626d3c557eca3680ce71117739fa161b7a92cfdb6c9c432ed88870b1ed127ba24248574c40c7428217d7e9bdd986fd8cd7c51fae8c776e1271fb9
This commit is contained in:
commit
85b601e043
6 changed files with 401 additions and 36 deletions
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <key_io.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
#include <script/standard.h>
|
||||
|
||||
|
@ -161,6 +162,20 @@ public:
|
|||
|
||||
virtual ~PubkeyProvider() = default;
|
||||
|
||||
/** Compare two public keys represented by this provider.
|
||||
* 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;
|
||||
|
||||
GetPubKey(0, dummy, a, dummy_info);
|
||||
other.GetPubKey(0, dummy, b, dummy_info);
|
||||
|
||||
return a < b;
|
||||
}
|
||||
|
||||
/** Derive a public key.
|
||||
* read_cache is the cache to read keys from (if not nullptr)
|
||||
* write_cache is the cache to write keys to (if not nullptr)
|
||||
|
@ -493,12 +508,12 @@ public:
|
|||
/** Base class for all Descriptor implementations. */
|
||||
class DescriptorImpl : public Descriptor
|
||||
{
|
||||
//! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig).
|
||||
protected:
|
||||
//! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for WSH and Multisig).
|
||||
const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args;
|
||||
//! The string name of the descriptor function.
|
||||
const std::string m_name;
|
||||
|
||||
protected:
|
||||
//! The sub-descriptor arguments (empty for everything but SH and WSH).
|
||||
//! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT)
|
||||
//! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions.
|
||||
|
@ -563,7 +578,7 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const
|
||||
virtual bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const
|
||||
{
|
||||
std::string extra = ToStringExtra();
|
||||
size_t pos = extra.size() > 0 ? 1 : 0;
|
||||
|
@ -917,6 +932,89 @@ public:
|
|||
bool IsSingleType() const final { return true; }
|
||||
};
|
||||
|
||||
/* We instantiate Miniscript here with a simple integer as key type.
|
||||
* The value of these key integers are an index in the
|
||||
* DescriptorImpl::m_pubkey_args vector.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The context for converting a Miniscript descriptor into a Script.
|
||||
*/
|
||||
class ScriptMaker {
|
||||
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
|
||||
const std::vector<CPubKey>& m_keys;
|
||||
|
||||
public:
|
||||
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {}
|
||||
|
||||
std::vector<unsigned char> ToPKBytes(uint32_t key) const {
|
||||
return {m_keys[key].begin(), m_keys[key].end()};
|
||||
}
|
||||
|
||||
std::vector<unsigned char> ToPKHBytes(uint32_t key) const {
|
||||
auto id = m_keys[key].GetID();
|
||||
return {id.begin(), id.end()};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The context for converting a Miniscript descriptor to its textual form.
|
||||
*/
|
||||
class StringMaker {
|
||||
//! To convert private keys for private descriptors.
|
||||
const SigningProvider* m_arg;
|
||||
//! Keys contained in the Miniscript (a reference to DescriptorImpl::m_pubkey_args).
|
||||
const std::vector<std::unique_ptr<PubkeyProvider>>& m_pubkeys;
|
||||
//! Whether to serialize keys as private or public.
|
||||
bool m_private;
|
||||
|
||||
public:
|
||||
StringMaker(const SigningProvider* arg LIFETIMEBOUND, const std::vector<std::unique_ptr<PubkeyProvider>>& pubkeys LIFETIMEBOUND, bool priv)
|
||||
: m_arg(arg), m_pubkeys(pubkeys), m_private(priv) {}
|
||||
|
||||
std::optional<std::string> ToString(uint32_t key) const
|
||||
{
|
||||
std::string ret;
|
||||
if (m_private) {
|
||||
if (!m_pubkeys[key]->ToPrivateString(*m_arg, ret)) return {};
|
||||
} else {
|
||||
ret = m_pubkeys[key]->ToString();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class MiniscriptDescriptor final : public DescriptorImpl
|
||||
{
|
||||
private:
|
||||
miniscript::NodeRef<uint32_t> m_node;
|
||||
|
||||
protected:
|
||||
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
|
||||
FlatSigningProvider& provider) const override
|
||||
{
|
||||
for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key);
|
||||
return Vector(m_node->ToScript(ScriptMaker(keys)));
|
||||
}
|
||||
|
||||
public:
|
||||
MiniscriptDescriptor(std::vector<std::unique_ptr<PubkeyProvider>> providers, miniscript::NodeRef<uint32_t> node)
|
||||
: DescriptorImpl(std::move(providers), "?"), m_node(std::move(node)) {}
|
||||
|
||||
bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type,
|
||||
const DescriptorCache* cache = nullptr) const override
|
||||
{
|
||||
if (const auto res = m_node->ToString(StringMaker(arg, m_pubkey_args, type == StringType::PRIVATE))) {
|
||||
out = *res;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsSolvable() const override { return false; } // For now, mark these descriptors as non-solvable (as we don't have signing logic for them).
|
||||
bool IsSingleType() const final { return true; }
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Parser //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1058,6 +1156,94 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
|
|||
return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider));
|
||||
}
|
||||
|
||||
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
|
||||
{
|
||||
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false);
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
|
||||
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
|
||||
}
|
||||
return key_provider;
|
||||
}
|
||||
|
||||
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
|
||||
{
|
||||
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
|
||||
std::copy(xkey.begin(), xkey.end(), full_key + 1);
|
||||
CPubKey pubkey(full_key);
|
||||
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOriginByXOnly(xkey, info)) {
|
||||
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
|
||||
}
|
||||
return key_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* The context for parsing a Miniscript descriptor (either from Script or from its textual representation).
|
||||
*/
|
||||
struct KeyParser {
|
||||
//! The Key type is an index in DescriptorImpl::m_pubkey_args
|
||||
using Key = uint32_t;
|
||||
//! Must not be nullptr if parsing from string.
|
||||
FlatSigningProvider* m_out;
|
||||
//! Must not be nullptr if parsing from Script.
|
||||
const SigningProvider* m_in;
|
||||
//! List of keys contained in the Miniscript.
|
||||
mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys;
|
||||
//! Used to detect key parsing errors within a Miniscript.
|
||||
mutable std::string m_key_parsing_error;
|
||||
|
||||
KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {}
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return *m_keys.at(a) < *m_keys.at(b);
|
||||
}
|
||||
|
||||
template<typename I> std::optional<Key> FromString(I begin, I end) const
|
||||
{
|
||||
assert(m_out);
|
||||
Key key = m_keys.size();
|
||||
auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error);
|
||||
if (!pk) return {};
|
||||
m_keys.push_back(std::move(pk));
|
||||
return key;
|
||||
}
|
||||
|
||||
std::optional<std::string> ToString(const Key& key) const
|
||||
{
|
||||
return m_keys.at(key)->ToString();
|
||||
}
|
||||
|
||||
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
|
||||
{
|
||||
assert(m_in);
|
||||
CPubKey pubkey(begin, end);
|
||||
if (pubkey.IsValid()) {
|
||||
Key key = m_keys.size();
|
||||
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
|
||||
return key;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename I> std::optional<Key> FromPKHBytes(I begin, I end) const
|
||||
{
|
||||
assert(end - begin == 20);
|
||||
assert(m_in);
|
||||
uint160 hash;
|
||||
std::copy(begin, end, hash.begin());
|
||||
CKeyID keyid(hash);
|
||||
CPubKey pubkey;
|
||||
if (m_in->GetPubKey(keyid, pubkey)) {
|
||||
Key key = m_keys.size();
|
||||
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
|
||||
return key;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/** Parse a script in a particular context. */
|
||||
std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
|
||||
{
|
||||
|
@ -1279,6 +1465,45 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
|
|||
error = "Can only have raw() at top level";
|
||||
return nullptr;
|
||||
}
|
||||
// Process miniscript expressions.
|
||||
{
|
||||
KeyParser parser(&out, nullptr);
|
||||
auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser);
|
||||
if (node) {
|
||||
if (ctx != ParseScriptContext::P2WSH) {
|
||||
error = "Miniscript expressions can only be used in wsh";
|
||||
return nullptr;
|
||||
}
|
||||
if (parser.m_key_parsing_error != "") {
|
||||
error = std::move(parser.m_key_parsing_error);
|
||||
return nullptr;
|
||||
}
|
||||
if (!node->IsSane()) {
|
||||
// Try to find the first insane sub for better error reporting.
|
||||
auto insane_node = node.get();
|
||||
if (const auto sub = node->FindInsaneSub()) insane_node = sub;
|
||||
if (const auto str = insane_node->ToString(parser)) error = *str;
|
||||
if (!insane_node->IsValid()) {
|
||||
error += " is invalid";
|
||||
} else {
|
||||
error += " is not sane";
|
||||
if (!insane_node->IsNonMalleable()) {
|
||||
error += ": malleable witnesses exist";
|
||||
} else if (insane_node == node.get() && !insane_node->NeedsSignature()) {
|
||||
error += ": witnesses without signature exist";
|
||||
} else if (!insane_node->CheckTimeLocksMix()) {
|
||||
error += ": contains mixes of timelocks expressed in blocks and seconds";
|
||||
} else if (!insane_node->CheckDuplicateKey()) {
|
||||
error += ": contains duplicate public keys";
|
||||
} else if (!insane_node->ValidSatisfactions()) {
|
||||
error += ": needs witnesses that may exceed resource limits";
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
|
||||
}
|
||||
}
|
||||
if (ctx == ParseScriptContext::P2SH) {
|
||||
error = "A function is needed within P2SH";
|
||||
return nullptr;
|
||||
|
@ -1290,29 +1515,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
|
||||
{
|
||||
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false);
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
|
||||
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
|
||||
}
|
||||
return key_provider;
|
||||
}
|
||||
|
||||
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
|
||||
{
|
||||
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
|
||||
std::copy(xkey.begin(), xkey.end(), full_key + 1);
|
||||
CPubKey pubkey(full_key);
|
||||
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOriginByXOnly(xkey, info)) {
|
||||
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
|
||||
}
|
||||
return key_provider;
|
||||
}
|
||||
|
||||
std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
|
||||
{
|
||||
auto match = MatchMultiA(script);
|
||||
|
@ -1426,6 +1628,14 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
|
|||
}
|
||||
}
|
||||
|
||||
if (ctx == ParseScriptContext::P2WSH) {
|
||||
KeyParser parser(nullptr, &provider);
|
||||
auto node = miniscript::FromScript(script, parser);
|
||||
if (node && node->IsSane()) {
|
||||
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
|
||||
}
|
||||
}
|
||||
|
||||
CTxDestination dest;
|
||||
if (ExtractDestination(script, dest)) {
|
||||
if (GetScriptForDestination(dest) == script) {
|
||||
|
|
|
@ -429,6 +429,21 @@ private:
|
|||
));
|
||||
}
|
||||
|
||||
/** Like TreeEval, but without downfn or State type.
|
||||
* upfn takes (const Node&, Span<Result>) and returns Result. */
|
||||
template<typename Result, typename UpFn>
|
||||
Result TreeEval(UpFn upfn) const
|
||||
{
|
||||
struct DummyState {};
|
||||
return std::move(*TreeEvalMaybe<Result>(DummyState{},
|
||||
[](DummyState, const Node&, size_t) { return DummyState{}; },
|
||||
[&upfn](DummyState, const Node& node, Span<Result> subs) {
|
||||
Result res{upfn(node, subs)};
|
||||
return std::optional<Result>(std::move(res));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/** Compare two miniscript subtrees, using a non-recursive algorithm. */
|
||||
friend int Compare(const Node<Key>& node1, const Node<Key>& node2)
|
||||
{
|
||||
|
@ -818,6 +833,15 @@ public:
|
|||
//! Return the expression type.
|
||||
Type GetType() const { return typ; }
|
||||
|
||||
//! Find an insane subnode which has no insane children. Nullptr if there is none.
|
||||
const Node* FindInsaneSub() const {
|
||||
return TreeEval<const Node*>([](const Node& node, Span<const Node*> subs) -> const Node* {
|
||||
for (auto& sub: subs) if (sub) return sub;
|
||||
if (!node.IsSaneSubexpression()) return &node;
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
//! Check whether this node is valid at all.
|
||||
bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; }
|
||||
|
||||
|
@ -953,7 +977,11 @@ void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& construct
|
|||
}
|
||||
}
|
||||
|
||||
//! Parse a miniscript from its textual descriptor form.
|
||||
/**
|
||||
* Parse a miniscript from its textual descriptor form.
|
||||
* This does not check whether the script is valid, let alone sane. The caller is expected to use
|
||||
* the `IsValidTopLevel()` and `IsSaneTopLevel()` to check for these properties on the node.
|
||||
*/
|
||||
template<typename Key, typename Ctx>
|
||||
inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
||||
{
|
||||
|
@ -1255,9 +1283,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
// Sanity checks on the produced miniscript
|
||||
assert(constructed.size() == 1);
|
||||
if (in.size() > 0) return {};
|
||||
const NodeRef<Key> tl_node = std::move(constructed.front());
|
||||
if (!tl_node->IsValidTopLevel()) return {};
|
||||
return tl_node;
|
||||
return std::move(constructed.front());
|
||||
}
|
||||
|
||||
/** Decode a script into opcode/push pairs.
|
||||
|
|
|
@ -141,14 +141,13 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
|
|||
} else {
|
||||
parse_priv = Parse(prv, keys_priv, error);
|
||||
}
|
||||
BOOST_CHECK_MESSAGE(parse_priv, error);
|
||||
if (replace_apostrophe_with_h_in_pub) {
|
||||
parse_pub = Parse(UseHInsteadOfApostrophe(pub), keys_pub, error);
|
||||
} else {
|
||||
parse_pub = Parse(pub, keys_pub, error);
|
||||
}
|
||||
|
||||
BOOST_CHECK(parse_priv);
|
||||
BOOST_CHECK(parse_pub);
|
||||
BOOST_CHECK_MESSAGE(parse_pub, error);
|
||||
|
||||
// Check that the correct OutputType is inferred
|
||||
BOOST_CHECK(parse_priv->GetOutputType() == type);
|
||||
|
@ -161,8 +160,8 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
|
|||
// Check that both versions serialize back to the public version.
|
||||
std::string pub1 = parse_priv->ToString();
|
||||
std::string pub2 = parse_pub->ToString();
|
||||
BOOST_CHECK(EqualDescriptor(pub, pub1));
|
||||
BOOST_CHECK(EqualDescriptor(pub, pub2));
|
||||
BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub1), "Private ser: " + pub1 + " Public desc: " + pub);
|
||||
BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub2), "Public ser: " + pub2 + " Public desc: " + pub);
|
||||
|
||||
// Check that both can be serialized with private key back to the private version, but not without private key.
|
||||
if (!(flags & MISSING_PRIVKEYS)) {
|
||||
|
@ -486,6 +485,29 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN
|
|||
}
|
||||
nonminimalmultisig << std::vector<unsigned char>{4} << OP_CHECKMULTISIG;
|
||||
CheckInferRaw(nonminimalmultisig);
|
||||
|
||||
// Miniscript tests
|
||||
|
||||
// Invalid checksum
|
||||
CheckUnparsable("wsh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))#abcdef12", "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))#abcdef12", "Provided checksum 'abcdef12' does not match computed checksum 'tyzp6a7p'");
|
||||
// Only p2wsh context is valid
|
||||
CheckUnparsable("sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh");
|
||||
CheckUnparsable("tr(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "tr(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "tr(): key 'and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10))' is not valid");
|
||||
CheckUnparsable("raw(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh");
|
||||
CheckUnparsable("", "tr(034D2224bbbbbbbbbbcbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb40,{{{{{{{{{{{{{{{{{{{{{{multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008')", "'multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008'' is not a valid descriptor function");
|
||||
// Insane at top level
|
||||
CheckUnparsable("wsh(and_b(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "wsh(and_b(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "and_b(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)) is invalid");
|
||||
// Invalid sub
|
||||
CheckUnparsable("wsh(and_v(vc:andor(v:pk_k(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "wsh(and_v(vc:andor(v:pk_k(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "v:pk_k(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204) is invalid");
|
||||
// Insane subs
|
||||
CheckUnparsable("wsh(or_i(older(1),pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(or_i(older(1),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "or_i(older(1),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: witnesses without signature exist");
|
||||
CheckUnparsable("wsh(or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "wsh(or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: malleable witnesses exist");
|
||||
CheckUnparsable("wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "and_b(older(1),a:older(100000000)) is not sane: contains mixes of timelocks expressed in blocks and seconds");
|
||||
CheckUnparsable("wsh(and_b(or_b(pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),s:pk(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: contains duplicate public keys");
|
||||
// Valid with extended keys.
|
||||
Check("wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", UNSOLVABLE, {{"0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73"}}, OutputType::BECH32, {{},{0}});
|
||||
// Valid under sh(wsh()) and with a mix of xpubs and raw keys
|
||||
Check("sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", UNSOLVABLE | MIXED_PUBKEYS, {{"a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487"}}, OutputType::P2SH_SEGWIT, {{},{0}});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -111,6 +111,10 @@ struct KeyConverter {
|
|||
assert(it != g_testdata->pkmap.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::optional<std::string> ToString(const Key& key) const {
|
||||
return HexStr(ToPKBytes(key));
|
||||
}
|
||||
};
|
||||
|
||||
//! Singleton instance of KeyConverter.
|
||||
|
@ -276,7 +280,7 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
|||
// (for now) have 'd:' be 'u'. This tests we can't use a 'd:' wrapper for a thresh, which requires
|
||||
// its subs to all be 'u' (taken from https://github.com/rust-bitcoin/rust-miniscript/discussions/341).
|
||||
const auto ms_minimalif = miniscript::FromString("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),sc:pk_k(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798),sdv:older(32))", CONVERTER);
|
||||
BOOST_CHECK(!ms_minimalif);
|
||||
BOOST_CHECK(ms_minimalif && !ms_minimalif->IsValid());
|
||||
// A Miniscript with duplicate keys is not sane
|
||||
const auto ms_dup1 = miniscript::FromString("and_v(v:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER);
|
||||
BOOST_CHECK(ms_dup1);
|
||||
|
@ -290,6 +294,15 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
|||
// Same when the duplicates are on different levels in the tree
|
||||
const auto ms_dup4 = miniscript::FromString("thresh(2,pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),a:and_b(dv:older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER);
|
||||
BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey());
|
||||
// Test we find the first insane sub closer to be a leaf node. This fragment is insane for two reasons:
|
||||
// 1. It can be spent without a signature
|
||||
// 2. It contains timelock mixes
|
||||
// We'll report the timelock mix error, as it's "deeper" (closer to be a leaf node) than the "no 's' property"
|
||||
// error is.
|
||||
const auto ms_ins = miniscript::FromString("or_i(and_b(after(1),a:after(1000000000)),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204))", CONVERTER);
|
||||
BOOST_CHECK(ms_ins && ms_ins->IsValid() && !ms_ins->IsSane());
|
||||
const auto insane_sub = ms_ins->FindInsaneSub();
|
||||
BOOST_CHECK(insane_sub && *insane_sub->ToString(CONVERTER) == "and_b(after(1),a:after(1000000000))");
|
||||
|
||||
// Timelock tests
|
||||
Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
|
||||
|
|
|
@ -205,6 +205,7 @@ BASE_SCRIPTS = [
|
|||
'wallet_keypool.py --legacy-wallet',
|
||||
'wallet_keypool.py --descriptors',
|
||||
'wallet_descriptor.py --descriptors',
|
||||
'wallet_miniscript.py',
|
||||
'feature_maxtipage.py',
|
||||
'p2p_nobloomfilter_messages.py',
|
||||
'p2p_filter.py',
|
||||
|
|
93
test/functional/wallet_miniscript.py
Executable file
93
test/functional/wallet_miniscript.py
Executable file
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2022 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test Miniscript descriptors integration in the wallet."""
|
||||
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
|
||||
MINISCRIPTS = [
|
||||
# One of two keys
|
||||
"or_b(pk(tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a/*),s:pk(tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy/*))",
|
||||
# A script similar (same spending policy) to BOLT3's offered HTLC (with anchor outputs)
|
||||
"or_d(pk(tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a/*),and_v(and_v(v:pk(tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy/*),or_c(pk(tpubD6NzVbkrYhZ4YNwtTWrKRJQzQX3PjPKeUQg1gYh1hiLMkk1cw8SRLgB1yb7JzE8bHKNt6EcZXkJ6AqpCZL1aaRSjnG36mLgbQvJZBNsjWnG/*),v:hash160(7f999c905d5e35cefd0a37673f746eb13fba3640))),older(1)))",
|
||||
# A Revault Unvault policy with the older() replaced by an after()
|
||||
"andor(multi(2,tpubD6NzVbkrYhZ4YMQC15JS7QcrsAyfGrGiykweqMmPxTkEVScu7vCZLNpPXW1XphHwzsgmqdHWDQAfucbM72EEB1ZEyfgZxYvkZjYVXx1xS9p/*,tpubD6NzVbkrYhZ4WkCyc7E3z6g6NkypHMiecnwc4DpWHTPqFdteRGkEKukdrSSyJGNnGrHNMfy4BCw2UXo5soYRCtCDDfy4q8pc8oyB7RgTFv8/*),and_v(v:multi(4,030f64b922aee2fd597f104bc6cb3b670f1ca2c6c49b1071a1a6c010575d94fe5a,02abe475b199ec3d62fa576faee16a334fdb86ffb26dce75becebaaedf328ac3fe,0314f3dc33595b0d016bb522f6fe3a67680723d842c1b9b8ae6b59fdd8ab5cccb4,025eba3305bd3c829e4e1551aac7358e4178832c739e4fc4729effe428de0398ab),after(424242)),thresh(4,pkh(tpubD6NzVbkrYhZ4YVrNggiT2ptVHwnFbLBqDkCtV5HkxR4WtcRLAQReKTkqZGNcV6GE7cQsmpBzzSzhk16DUwB1gn1L7ZPnJF2dnNePP1uMBCY/*),a:pkh(tpubD6NzVbkrYhZ4YU9vM1s53UhD75UyJatx8EMzMZ3VUjR2FciNfLLkAw6a4pWACChzobTseNqdWk4G7ZdBqRDLtLSACKykTScmqibb1ZrCvJu/*),a:pkh(tpubD6NzVbkrYhZ4YUHcFfuH9iEBLiH8CBRJTpS7X3qjHmh82m1KCNbzs6w9gyK8oWHSZmKHWcakAXCGfbKg6xoCvKzQCWAHyxaC7QcWfmzyBf4/*),a:pkh(tpubD6NzVbkrYhZ4XXEmQtS3sgxpJbMyMg4McqRR1Af6ULzyrTRnhwjyr1etPD7svap9oFtJf4MM72brUb5o7uvF2Jyszc5c1t836fJW7SX2e8D/*)))",
|
||||
# Liquid-like federated pegin with emergency recovery keys
|
||||
"or_i(and_b(pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),a:and_b(pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),a:and_b(pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),a:and_b(pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),s:pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2))))),and_v(v:thresh(2,pkh(tpubD6NzVbkrYhZ4YK67cd5fDe4fBVmGB2waTDrAt1q4ey9HPq9veHjWkw3VpbaCHCcWozjkhgAkWpFrxuPMUrmXVrLHMfEJ9auoZA6AS1g3grC/*),a:pkh(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),a:pkh(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)),older(4209713)))",
|
||||
]
|
||||
|
||||
|
||||
class WalletMiniscriptTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
self.skip_if_no_sqlite()
|
||||
|
||||
def watchonly_test(self, ms):
|
||||
self.log.info(f"Importing Miniscript '{ms}'")
|
||||
desc = descsum_create(f"wsh({ms})")
|
||||
assert self.ms_wo_wallet.importdescriptors(
|
||||
[
|
||||
{
|
||||
"desc": desc,
|
||||
"active": True,
|
||||
"range": 2,
|
||||
"next_index": 0,
|
||||
"timestamp": "now",
|
||||
}
|
||||
]
|
||||
)[0]["success"]
|
||||
|
||||
self.log.info("Testing we derive new addresses for it")
|
||||
assert_equal(
|
||||
self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 0)[0]
|
||||
)
|
||||
assert_equal(
|
||||
self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 1)[1]
|
||||
)
|
||||
|
||||
self.log.info("Testing we detect funds sent to one of them")
|
||||
addr = self.ms_wo_wallet.getnewaddress()
|
||||
txid = self.funder.sendtoaddress(addr, 0.01)
|
||||
self.wait_until(
|
||||
lambda: len(self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])) == 1
|
||||
)
|
||||
utxo = self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])[0]
|
||||
assert utxo["txid"] == txid and not utxo["solvable"] # No satisfaction logic (yet)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("Making a descriptor wallet")
|
||||
self.funder = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
||||
self.nodes[0].createwallet(
|
||||
wallet_name="ms_wo", descriptors=True, disable_private_keys=True
|
||||
)
|
||||
self.ms_wo_wallet = self.nodes[0].get_wallet_rpc("ms_wo")
|
||||
|
||||
# Sanity check we wouldn't let an insane Miniscript descriptor in
|
||||
res = self.ms_wo_wallet.importdescriptors(
|
||||
[
|
||||
{
|
||||
"desc": descsum_create(
|
||||
"wsh(and_b(ripemd160(1fd9b55a054a2b3f658d97e6b84cf3ee00be429a),a:1))"
|
||||
),
|
||||
"active": False,
|
||||
"timestamp": "now",
|
||||
}
|
||||
]
|
||||
)[0]
|
||||
assert not res["success"]
|
||||
assert "is not sane: witnesses without signature exist" in res["error"]["message"]
|
||||
|
||||
# Test we can track any type of Miniscript
|
||||
for ms in MINISCRIPTS:
|
||||
self.watchonly_test(ms)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
WalletMiniscriptTest().main()
|
Loading…
Add table
Reference in a new issue