descriptor: Implement rawnode() partial descriptor

This commit is contained in:
Novo 2024-06-07 10:28:47 +01:00 committed by Eunovo
parent 8072e87480
commit 8bdbd9bab4
2 changed files with 173 additions and 3 deletions

View file

@ -7,6 +7,7 @@
#include <hash.h> #include <hash.h>
#include <key_io.h> #include <key_io.h>
#include <pubkey.h> #include <pubkey.h>
#include <script/interpreter.h>
#include <script/miniscript.h> #include <script/miniscript.h>
#include <script/parsing.h> #include <script/parsing.h>
#include <script/script.h> #include <script/script.h>
@ -15,6 +16,7 @@
#include <uint256.h> #include <uint256.h>
#include <common/args.h> #include <common/args.h>
#include <crypto/common.h>
#include <span.h> #include <span.h>
#include <util/bip32.h> #include <util/bip32.h>
#include <util/check.h> #include <util/check.h>
@ -1415,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 // // Parser //
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -2057,7 +2117,15 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
this_nodes.reserve(subscripts.size()); this_nodes.reserve(subscripts.size());
for (size_t pos = 0; pos < subscripts.size(); pos++) { for (size_t pos = 0; pos < subscripts.size(); pos++) {
this_subs.emplace_back(std::move(subscripts[pos].at(i))); this_subs.emplace_back(std::move(subscripts[pos].at(i)));
this_nodes.emplace_back(depths[pos], TAPROOT_LEAF_TAPSCRIPT); 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), std::move(this_nodes))); ret.emplace_back(std::make_unique<TRDescriptor>(std::move(internal_keys.at(i)), std::move(this_subs), std::move(this_nodes)));
} }
@ -2101,6 +2169,66 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
error = "Can only have raw() at top level"; error = "Can only have raw() at top level";
return {}; 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. // Process miniscript expressions.
{ {
const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT}; const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT};
@ -2290,8 +2418,10 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
std::vector<TaprootNode> nodes; std::vector<TaprootNode> nodes;
for (const auto& [depth, script, leaf_ver] : *tree) { for (const auto& [depth, script, leaf_ver] : *tree) {
std::unique_ptr<DescriptorImpl> subdesc; 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) { if (!subdesc) {
ok = false; ok = false;
@ -2306,6 +2436,17 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
return std::make_unique<TRDescriptor>(std::move(key), std::move(subscripts), std::move(nodes)); 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 the above doesn't work, construct a rawtr() descriptor with just the encoded x-only pubkey.
if (pubkey.IsFullyValid()) { if (pubkey.IsFullyValid()) {
@ -2330,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. // The following descriptors are all top-level only descriptors.
// So if we are not at the top level, return early. // So if we are not at the top level, return early.
if (ctx != ParseScriptContext::TOP) return nullptr; 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 | 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}}); 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 // Basic sh(pkh()) with key origin
CheckInferDescriptor("a9141a31ad23bf49c247dd531a623c2ef57da3c400c587", "sh(pkh([deadbeef/0h/0h/0]03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", {"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}, {{"03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", "deadbeef/0h/0h/0"}}); CheckInferDescriptor("a9141a31ad23bf49c247dd531a623c2ef57da3c400c587", "sh(pkh([deadbeef/0h/0h/0]03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", {"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}, {{"03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", "deadbeef/0h/0h/0"}});
// p2pk script with hybrid key must infer as raw() // p2pk script with hybrid key must infer as raw()