This commit is contained in:
Oghenovo Usiwoma 2025-03-13 02:04:10 +01:00 committed by GitHub
commit f104ded83b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 238 additions and 21 deletions

View file

@ -91,6 +91,8 @@ Descriptors consist of several types of expressions. The top level expression is
- `addr(ADDR)` (top level only): the script which ADDR expands to.
- `raw(HEX)` (top level only): the script whose hex encoding is HEX.
- `rawtr(KEY)` (top level only): P2TR output with the specified key as output key. NOTE: while it's possible to use this to construct wallets, it has several downsides, like being unable to prove no hidden script path exists. Use at your own risk.
- `rawnode(HEX)` (inside `tr` only): specify tree or branch merkle root in HEX encoding. NOTE: this hides script paths which are only revealed after spending. Use at your own risk
- `rawleaf(HEX[,LEAF_VERSION_HEX])`(inside `tr` only): specify raw leaf script and leaf version in HEX encoding. `LEAF_VERSION_HEX` defaults to `c0`
`KEY` expressions:
- Optionally, key origin information, consisting of:

View file

@ -7,6 +7,7 @@
#include <hash.h>
#include <key_io.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <script/miniscript.h>
#include <script/parsing.h>
#include <script/script.h>
@ -15,6 +16,7 @@
#include <uint256.h>
#include <common/args.h>
#include <crypto/common.h>
#include <span.h>
#include <util/bip32.h>
#include <util/check.h>
@ -1156,17 +1158,41 @@ public:
}
};
/** Represents the type of a node in taproot descriptor script tree */
enum TaprootNodeType {
LEAF_SCRIPT,
NODE_HASH
};
/** A struct hold information on a node taproot descriptor script tree */
struct TaprootNode {
int m_depth;
uint8_t m_leaf_version;
TaprootNodeType m_type;
TaprootNode(
int depth,
uint8_t leaf_version,
TaprootNodeType type = TaprootNodeType::LEAF_SCRIPT) : m_depth(depth), m_leaf_version(leaf_version), m_type(type) {}
};
/** A parsed tr(...) descriptor. */
class TRDescriptor final : public DescriptorImpl
{
std::vector<int> m_depths;
std::vector<TaprootNode> m_nodes;
protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override
{
TaprootBuilder builder;
assert(m_depths.size() == scripts.size());
for (size_t pos = 0; pos < m_depths.size(); ++pos) {
builder.Add(m_depths[pos], scripts[pos], TAPROOT_LEAF_TAPSCRIPT);
assert(m_nodes.size() == scripts.size());
for (size_t pos = 0; pos < m_nodes.size(); ++pos) {
if (m_nodes[pos].m_type == TaprootNodeType::NODE_HASH) {
builder.AddOmitted(m_nodes[pos].m_depth, uint256(Span(scripts[pos])));
} else if (m_nodes[pos].m_type == TaprootNodeType::LEAF_SCRIPT) {
builder.Add(m_nodes[pos].m_depth, scripts[pos], m_nodes[pos].m_leaf_version);
} else {
assert(false);
}
}
if (!builder.IsComplete()) return {};
assert(keys.size() == 1);
@ -1180,11 +1206,11 @@ protected:
}
bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override
{
if (m_depths.empty()) return true;
if (m_nodes.empty()) return true;
std::vector<bool> path;
for (size_t pos = 0; pos < m_depths.size(); ++pos) {
for (size_t pos = 0; pos < m_nodes.size(); ++pos) {
if (pos) ret += ',';
while ((int)path.size() <= m_depths[pos]) {
while ((int)path.size() <= m_nodes[pos].m_depth) {
if (path.size()) ret += '{';
path.push_back(false);
}
@ -1200,10 +1226,9 @@ protected:
return true;
}
public:
TRDescriptor(std::unique_ptr<PubkeyProvider> internal_key, std::vector<std::unique_ptr<DescriptorImpl>> descs, std::vector<int> depths) :
DescriptorImpl(Vector(std::move(internal_key)), std::move(descs), "tr"), m_depths(std::move(depths))
TRDescriptor(std::unique_ptr<PubkeyProvider> internal_key, std::vector<std::unique_ptr<DescriptorImpl>> descs, std::vector<TaprootNode> nodes) : DescriptorImpl(Vector(std::move(internal_key)), std::move(descs), "tr"), m_nodes(std::move(nodes))
{
assert(m_subdescriptor_args.size() == m_depths.size());
assert(m_subdescriptor_args.size() == m_nodes.size());
}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }
@ -1225,7 +1250,7 @@ public:
std::vector<std::unique_ptr<DescriptorImpl>> subdescs;
subdescs.reserve(m_subdescriptor_args.size());
std::transform(m_subdescriptor_args.begin(), m_subdescriptor_args.end(), subdescs.begin(), [](const std::unique_ptr<DescriptorImpl>& d) { return d->Clone(); });
return std::make_unique<TRDescriptor>(m_pubkey_args.at(0)->Clone(), std::move(subdescs), m_depths);
return std::make_unique<TRDescriptor>(m_pubkey_args.at(0)->Clone(), std::move(subdescs), m_nodes);
}
};
@ -1392,6 +1417,64 @@ public:
}
};
/** A parsed rawnode(...) descriptor */
class RawNodeDescriptor final : public DescriptorImpl
{
std::vector<unsigned char> m_bytes;
protected:
std::string ToStringExtra() const override { return HexStr(m_bytes); }
std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, Span<const CScript>, FlatSigningProvider&) const override { return Vector(CScript(m_bytes.begin(), m_bytes.end())); }
public:
RawNodeDescriptor(std::vector<unsigned char> bytes) : DescriptorImpl({}, "rawnode"), m_bytes(std::move(bytes)) {}
bool IsSolvable() const final { return false; }
bool IsSingleType() const final { return true; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
std::optional<int64_t> ScriptSize() const override { return m_bytes.size(); }
std::unique_ptr<DescriptorImpl> Clone() const override
{
return std::make_unique<RawNodeDescriptor>(m_bytes);
}
};
/** A parsed rawleaf(...) descriptor */
class RawLeafDescriptor final : public DescriptorImpl
{
CScript m_leaf_script;
uint8_t m_leaf_version;
protected:
std::string ToStringExtra() const override
{
return strprintf("%s,%x", HexStr(m_leaf_script), m_leaf_version);
}
std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, Span<const CScript>, FlatSigningProvider&) const override { return Vector(m_leaf_script); }
public:
RawLeafDescriptor(CScript leaf_script, uint8_t leaf_version) : DescriptorImpl({}, "rawleaf"), m_leaf_script(leaf_script), m_leaf_version(leaf_version) {}
bool IsSolvable() const final { return false; }
bool IsSingleType() const final { return true; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
std::optional<int64_t> ScriptSize() const override { return m_leaf_script.size(); }
uint8_t GetLeafVersion() const { return m_leaf_version; }
void SetLeafVersion(uint8_t version) { m_leaf_version = version; }
std::unique_ptr<DescriptorImpl> Clone() const override
{
return std::make_unique<RawLeafDescriptor>(m_leaf_script, m_leaf_version);
}
};
////////////////////////////////////////////////////////////////////////////
// Parser //
////////////////////////////////////////////////////////////////////////////
@ -1977,6 +2060,7 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
if (subscripts.back().empty()) return {};
max_providers_len = std::max(max_providers_len, subscripts.back().size());
depths.push_back(branches.size());
// Process closing braces; one is expected for every right branch we were in.
while (branches.size() && branches.back()) {
if (!Const("}", expr)) {
@ -2028,11 +2112,22 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
for (size_t i = 0; i < max_providers_len; ++i) {
// Build final subscripts vectors by retrieving the i'th subscript for each vector in subscripts
std::vector<std::unique_ptr<DescriptorImpl>> this_subs;
std::vector<TaprootNode> this_nodes;
this_subs.reserve(subscripts.size());
for (auto& subs : subscripts) {
this_subs.emplace_back(std::move(subs.at(i)));
this_nodes.reserve(subscripts.size());
for (size_t pos = 0; pos < subscripts.size(); pos++) {
this_subs.emplace_back(std::move(subscripts[pos].at(i)));
auto leaf_version{TAPROOT_LEAF_TAPSCRIPT};
auto type{TaprootNodeType::LEAF_SCRIPT};
if (dynamic_cast<RawNodeDescriptor*>(this_subs.back().get())) {
type = TaprootNodeType::NODE_HASH;
}
if (auto rawleaf = dynamic_cast<RawLeafDescriptor*>(this_subs.back().get())) {
leaf_version = rawleaf->GetLeafVersion();
}
this_nodes.emplace_back(depths[pos], leaf_version, type);
}
ret.emplace_back(std::make_unique<TRDescriptor>(std::move(internal_keys.at(i)), std::move(this_subs), depths));
ret.emplace_back(std::make_unique<TRDescriptor>(std::move(internal_keys.at(i)), std::move(this_subs), std::move(this_nodes)));
}
return ret;
@ -2074,6 +2169,66 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
error = "Can only have raw() at top level";
return {};
}
if (ctx == ParseScriptContext::P2TR && Func("rawnode", expr)) {
std::string str(expr.begin(), expr.end());
auto bytes = TryParseHex<uint8_t>(str);
if (!bytes.has_value()) {
error = "Rawnode hash is not hex";
return {};
}
if (bytes->size() != 32) {
error = "256 bits digest expected";
return {};
}
ret.emplace_back(std::make_unique<RawNodeDescriptor>(bytes.value()));
return ret;
} else if (Func("rawnode", expr)) {
error = "Can only have rawnode() inside tr()";
return {};
}
if (ctx == ParseScriptContext::P2TR && Func("rawleaf", expr)) {
auto arg1 = Expr(expr);
std::string leaf_script_str(arg1.begin(), arg1.end());
if (!IsHex(leaf_script_str)) {
error = "Leaf Script is not hex";
return {};
}
auto leaf_script_bytes = ParseHex(leaf_script_str);
CScript leaf_script(leaf_script_bytes.begin(), leaf_script_bytes.end());
if (!Const(",", expr)) {
// Leaf version not specified, return early
ret.emplace_back(std::make_unique<RawLeafDescriptor>(leaf_script, TAPROOT_LEAF_TAPSCRIPT));
return ret;
}
// Read and process leaf version
auto arg2 = Expr(expr);
std::string leaf_version_str(arg2.begin(), arg2.end());
auto leaf_version_hex_vec = TryParseHex<uint8_t>(leaf_version_str);
if (!leaf_version_hex_vec.has_value()) {
error = "Leaf Version is not hex";
return {};
}
if (leaf_version_hex_vec->size() > 1) {
error = "Leaf Version is too large";
return {};
}
if (leaf_version_hex_vec->size() == 0) {
error = "Expected Leaf Version but not provided";
return {};
}
uint8_t leaf_version = (*leaf_version_hex_vec)[0];
if ((leaf_version & ~TAPROOT_LEAF_MASK) == 1) {
error = "Leaf Version is invalid";
return {};
}
ret.emplace_back(std::make_unique<RawLeafDescriptor>(leaf_script, leaf_version));
return ret;
} else if (Func("rawleaf", expr)) {
error = "Can only have rawleaf() inside tr()";
return {};
}
// Process miniscript expressions.
{
const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT};
@ -2260,25 +2415,38 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
// If that works, try to infer subdescriptors for all leaves.
bool ok = true;
std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions
std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts)
std::vector<TaprootNode> nodes;
for (const auto& [depth, script, leaf_ver] : *tree) {
std::unique_ptr<DescriptorImpl> subdesc;
if (leaf_ver == TAPROOT_LEAF_TAPSCRIPT) {
subdesc = InferScript(CScript(script.begin(), script.end()), ParseScriptContext::P2TR, provider);
subdesc = InferScript(CScript(script.begin(), script.end()), ParseScriptContext::P2TR, provider);
if (auto rawleaf = dynamic_cast<RawLeafDescriptor*>(subdesc.get())) {
rawleaf->SetLeafVersion(leaf_ver);
}
if (!subdesc) {
ok = false;
break;
} else {
subscripts.push_back(std::move(subdesc));
depths.push_back(depth);
nodes.emplace_back(depth, leaf_ver);
}
}
if (ok) {
auto key = InferXOnlyPubkey(tap.internal_key, ParseScriptContext::P2TR, provider);
return std::make_unique<TRDescriptor>(std::move(key), std::move(subscripts), std::move(depths));
return std::make_unique<TRDescriptor>(std::move(key), std::move(subscripts), std::move(nodes));
}
}
// If the tree is empty but it has a merkle root, infer the merkle root as a rawnode()
if (!tap.merkle_root.IsNull()) {
auto key = InferXOnlyPubkey(tap.internal_key, ParseScriptContext::P2TR, provider);
std::vector<unsigned char> merkle_root_bytes;
std::copy(tap.merkle_root.begin(), tap.merkle_root.end(), std::back_inserter(merkle_root_bytes));
std::vector<std::unique_ptr<DescriptorImpl>> descs;
descs.push_back(std::make_unique<RawNodeDescriptor>(merkle_root_bytes));
std::vector<TaprootNode> nodes;
nodes.emplace_back(0, 0, TaprootNodeType::NODE_HASH);
return std::make_unique<TRDescriptor>(std::move(key), std::move(descs), std::move(nodes));
}
}
// If the above doesn't work, construct a rawtr() descriptor with just the encoded x-only pubkey.
if (pubkey.IsFullyValid()) {
@ -2303,6 +2471,10 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
}
}
if (ctx == ParseScriptContext::P2TR) {
return std::make_unique<RawLeafDescriptor>(script, 0);
}
// The following descriptors are all top-level only descriptors.
// So if we are not at the top level, return early.
if (ctx != ParseScriptContext::TOP) return nullptr;

View file

@ -1059,6 +1059,31 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,KztMyyi1pXUtuZfJSB7JzVdmJMAz7wfGVFoSRUR5CVZxXxULXuGR)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE | SIGNABLE_FAILS, {{"51209a3d79db56fbe3ba4d905d827b62e1ed31cd6df1198b8c759d589c0f4efc27bd"}}, OutputType::BECH32M);
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,KztMyyi1pXUtuZfJSB7JzVdmJMAz7wfGVFoSRUR5CVZxXxULXuGR)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"51209a3d79db56fbe3ba4d905d827b62e1ed31cd6df1198b8c759d589c0f4efc27bd"}}, OutputType::BECH32M, /*op_desc_id=*/{}, {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/42, /*preimages=*/{{"ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588"_hex_v_u8, "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"_hex_v_u8}});
// Can have rawnode under tr()
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b))", XONLY_KEYS | UNSOLVABLE, {{"512085d8d2f800c2fdf8b671f16c3ec75aa6e134aa6ae98c08b5351408abef957452"}}, OutputType::BECH32M);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,{rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b),rawnode(fe59e853cc46a633e556950a65a47b61098b669775b876e517339076a4970c23)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b),rawnode(fe59e853cc46a633e556950a65a47b61098b669775b876e517339076a4970c23)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b),rawnode(fe59e853cc46a633e556950a65a47b61098b669775b876e517339076a4970c23)})", XONLY_KEYS | UNSOLVABLE, {{"5120117ee4eb20c920ef1f1c088536903624997626423c9ce6737c2d0476f8ca27e3"}}, OutputType::BECH32M);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,{rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b),pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b),pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b),pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)})", XONLY_KEYS | UNSOLVABLE, {{"512013ed7d909ab69e6581906f54cb9e6f564fbd102d3a2f6e2bbeb2824bb9a68344"}}, OutputType::BECH32M);
CheckUnparsable("rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b)", "rawnode(e960f9fcfb646b5a6eb3a091d9270497738f7bcd99c2dda549acc699f02b043b)", "Can only have rawnode() inside tr()");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawnode(e9))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawnode(e9))", "256 bits digest expected");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawnode(e9))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawnode(zzzz))", "Rawnode hash is not hex");
// Can have a rawleaf under tr()
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,c0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,c0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,c0))", XONLY_KEYS | UNSOLVABLE, {{"51209eff65f093c70a94a43bbf6fe508fa557dc1b81551759d3fb15e09b521a42a2f"}}, OutputType::BECH32M);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,cc))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,cc))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,cc))", XONLY_KEYS | UNSOLVABLE, {{"5120ed4f8863f232246c6bb9c6e8877a3f586ea9c52203b35658e5a15776ce60aaa6"}}, OutputType::BECH32M);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,66))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,66))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,66))", XONLY_KEYS | UNSOLVABLE, {{"5120ab7116360411e36c55ba4f6e7a47a26b4725f55b87c31dca5ecf2b7b5d9dc294"}}, OutputType::BECH32M);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,{rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,c0),pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,c0),pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac,c0),pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)})", XONLY_KEYS | UNSOLVABLE, {{"51203ae76d659be52428698fc98f897b9c492c413e8cadc8115038ba0684b2050dea"}}, OutputType::BECH32M);
CheckUnparsable("rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac)", "rawleaf(41069228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4ac)", "Can only have rawleaf() inside tr()");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(z,c0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(z,c0))", "Leaf Script is not hex");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(a0,ca00))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(a0,ca00))", "Leaf Version is too large");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(a0,z0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(a0,z0))", "Leaf Version is not hex");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(a0,))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(a0,))", "Expected Leaf Version but not provided");
// Check leaf version constraints
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(a0,z0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(a0,bf))", "Leaf Version is invalid");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(a0,z0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(a0,c1))", "Leaf Version is invalid");
CheckUnparsable("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,rawleaf(a0,z0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,rawleaf(a0,ff))", "Leaf Version is invalid");
// Basic sh(pkh()) with key origin
CheckInferDescriptor("a9141a31ad23bf49c247dd531a623c2ef57da3c400c587", "sh(pkh([deadbeef/0h/0h/0]03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", {"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}, {{"03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", "deadbeef/0h/0h/0"}});
// p2pk script with hybrid key must infer as raw()

View file

@ -55,6 +55,14 @@ DESCS = [
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}},{P2WSH_MINISCRIPTS[2].replace('multi', 'multi_a')}}})",
# A Taproot with all above scripts in its tree.
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}},{{{P2WSH_MINISCRIPTS[2].replace('multi', 'multi_a')},{P2WSH_MINISCRIPTS[3]}}}}})",
# A Taproot with rawnode as root in its tree
f"tr({TPUBS[0]}/*,rawnode(8a62dc0a100c4156cc2a4b7c2a97747ce0dfe90562673fd662678aaae93121fb))",
# A Taproot with rawnode and multipath descriptor
f"tr({TPUBS[0]}/*,{{rawnode(8a62dc0a100c4156cc2a4b7c2a97747ce0dfe90562673fd662678aaae93121fb),pk({TPUBS[0]}/<1;2>/*)}})",
# A Taproot with rawnode as branch root in its tree
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{P2WSH_MINISCRIPTS[0]},rawnode(8a62dc0a100c4156cc2a4b7c2a97747ce0dfe90562673fd662678aaae93121fb)}})",
# A Taproot with rawleaf in its tree
f"tr({TPUBS[0]}/*,rawleaf(aa2061e33e9dbfefc45f6a194187684d278f789fd4d5e207a357e79971b6519a8b128821{PUBKEYS[0]}ac))"
]
DESCS_PRIV = [
@ -199,6 +207,14 @@ DESCS_PRIV = [
"sigs_count": 2,
"stack_size": 8,
},
# Taproot with a rawnode in the tree
{
"desc": f"tr({TPUBS[0]}/*,{{pk({TPRVS[0]}/*),rawnode(8a62dc0a100c4156cc2a4b7c2a97747ce0dfe90562673fd662678aaae93121fb)}})",
"sequence": None,
"locktime": None,
"sigs_count": 1,
"stack_size": 3,
}
]
@ -231,13 +247,15 @@ class WalletMiniscriptTest(BitcoinTestFramework):
self.log.info("Testing we derive new addresses for it")
addr_type = "bech32m" if desc.startswith("tr(") else "bech32"
derived = [self.funder.deriveaddresses(desc, 0), self.funder.deriveaddresses(desc, 1)]
is_multipath = desc.count("<1;2>") > 0
assert_equal(
self.ms_wo_wallet.getnewaddress(address_type=addr_type),
self.funder.deriveaddresses(desc, 0)[0],
derived[0][0][0] if is_multipath else derived[0][0],
)
assert_equal(
self.ms_wo_wallet.getnewaddress(address_type=addr_type),
self.funder.deriveaddresses(desc, 1)[1],
derived[1][0][1] if is_multipath else derived[1][1],
)
self.log.info("Testing we detect funds sent to one of them")