From 13bab10d50144aad56aef76b11c07936995abece Mon Sep 17 00:00:00 2001 From: jbride Date: Fri, 8 Aug 2025 21:51:15 -0600 Subject: [PATCH] BIP360: Includes the following: 1. validateaddress RPC now support p2qrh address 2. ability to spend from a p2qrh utxo 3. p2qrh related integration tests 4. deriveaddresses and getdescriptorinfo RPC functions now support qrh() descriptor --- src/addresstype.cpp | 12 + src/addresstype.h | 9 +- src/key_io.cpp | 18 ++ src/leveldb/db/db_impl.cc | 3 - src/leveldb/db/db_impl.cc.rej | 12 + src/policy/policy.cpp | 34 +++ src/policy/policy.h | 2 + src/pubkey.cpp | 1 + src/rpc/rawtransaction.cpp | 2 + src/rpc/util.cpp | 10 + src/script/descriptor.cpp | 191 ++++++++++++++- src/script/interpreter.cpp | 104 +++++++- src/script/interpreter.h | 7 + src/script/miniscript.h | 2 + src/script/script_error.cpp | 2 + src/script/script_error.h | 3 + src/script/sign.cpp | 1 + src/script/solver.cpp | 27 +++ src/script/solver.h | 1 + src/test/descriptor_tests.cpp | 4 +- .../fuzz/script_assets_test_minimizer.cpp | 4 + src/test/fuzz/util.cpp | 3 + src/test/key_io_tests.cpp | 30 ++- src/test/script_assets_tests.cpp | 26 ++ src/wallet/rpc/addresses.cpp | 1 + src/wallet/scriptpubkeyman.cpp | 1 + test/functional/rpc_p2qrh.py | 226 ++++++++++++++++++ test/functional/rpc_validateaddress.py | 5 + test/functional/test_runner.py | 1 + 29 files changed, 716 insertions(+), 26 deletions(-) create mode 100644 src/leveldb/db/db_impl.cc.rej create mode 100755 test/functional/rpc_p2qrh.py diff --git a/src/addresstype.cpp b/src/addresstype.cpp index 67e643943d4d..f4054d1cbd98 100644 --- a/src/addresstype.cpp +++ b/src/addresstype.cpp @@ -87,6 +87,12 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) addressRet = tap; return true; } + case TxoutType::WITNESS_V3_P2QRH: { + WitnessV3P2QRH p2qrh; + std::copy(vSolutions[0].begin(), vSolutions[0].end(), p2qrh.begin()); + addressRet = p2qrh; + return true; + } case TxoutType::ANCHOR: { addressRet = PayToAnchor(); return true; @@ -147,6 +153,11 @@ class CScriptVisitor { return CScript() << CScript::EncodeOP_N(id.GetWitnessVersion()) << id.GetWitnessProgram(); } + + CScript operator()(const WitnessV3P2QRH& id) const + { + return CScript() << CScript::EncodeOP_N(3) << ToByteVector(id); + } }; class ValidDestinationVisitor @@ -160,6 +171,7 @@ class ValidDestinationVisitor bool operator()(const WitnessV0ScriptHash& dest) const { return true; } bool operator()(const WitnessV1Taproot& dest) const { return true; } bool operator()(const WitnessUnknown& dest) const { return true; } + bool operator()(const WitnessV3P2QRH& dest) const { return true; } }; } // namespace diff --git a/src/addresstype.h b/src/addresstype.h index 78d3126d853b..5ddf4b97db7f 100644 --- a/src/addresstype.h +++ b/src/addresstype.h @@ -91,6 +91,13 @@ struct WitnessV1Taproot : public XOnlyPubKey explicit WitnessV1Taproot(const XOnlyPubKey& xpk) : XOnlyPubKey(xpk) {} }; +struct WitnessV3P2QRH : public BaseHash +{ + WitnessV3P2QRH() : BaseHash() {} + explicit WitnessV3P2QRH(const uint256& hash) : BaseHash(hash) {} + explicit WitnessV3P2QRH(const CScript& script); +}; + //! CTxDestination subtype to encode any future Witness version struct WitnessUnknown { @@ -140,7 +147,7 @@ struct PayToAnchor : public WitnessUnknown * * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address) * A CTxDestination is the internal data type encoded in a bitcoin address */ -using CTxDestination = std::variant; +using CTxDestination = std::variant; /** Check whether a CTxDestination corresponds to one with an address. */ bool IsValidDestination(const CTxDestination& dest); diff --git a/src/key_io.cpp b/src/key_io.cpp index 3726d22233f6..6aa553dbc1b2 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -77,6 +77,14 @@ class DestinationEncoder return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); } + std::string operator()(const WitnessV3P2QRH& id) const + { + std::vector data = {3}; // Version 3 + data.reserve(53); // Reserve space for the hash + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end()); + return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); + } + std::string operator()(const CNoDestination& no) const { return {}; } std::string operator()(const PubKeyDestination& pk) const { return {}; } }; @@ -181,6 +189,16 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return tap; } + if (version == 3) { + WitnessV3P2QRH qrh; + if (data.size() == qrh.size()) { + std::copy(data.begin(), data.end(), qrh.begin()); + return qrh; + } + error_str = strprintf("Invalid P2QRH address program size (%d %s)", data.size(), byte_str); + return CNoDestination(); + } + if (CScript::IsPayToAnchor(version, data)) { return PayToAnchor(); } diff --git a/src/leveldb/db/db_impl.cc b/src/leveldb/db/db_impl.cc index 65e31724bcec..f61b4719536a 100644 --- a/src/leveldb/db/db_impl.cc +++ b/src/leveldb/db/db_impl.cc @@ -1028,9 +1028,6 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) { stats.bytes_read += compact->compaction->input(which, i)->file_size; } } - for (size_t i = 0; i < compact->outputs.size(); i++) { - stats.bytes_written += compact->outputs[i].file_size; - } mutex_.Lock(); stats_[compact->compaction->level() + 1].Add(stats); diff --git a/src/leveldb/db/db_impl.cc.rej b/src/leveldb/db/db_impl.cc.rej new file mode 100644 index 000000000000..db5dc0a680e5 --- /dev/null +++ b/src/leveldb/db/db_impl.cc.rej @@ -0,0 +1,12 @@ +--- src/leveldb/db/db_impl.cc ++++ src/leveldb/db/db_impl.cc +@@ -1028,9 +1028,6 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) { + stats.bytes_read += compact->compaction->input(which, i)->file_size; + } + } +- for (size_t i = 0; i < compact->outputs.size(); i++) { +- stats.bytes_written += compact->outputs[i].file_size; +- } + + mutex_.Lock(); + stats_[compact->compaction->level() + 1].Add(stats); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 48f2a6a74464..a1d2bb396907 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -91,6 +91,9 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType) return false; if (m < 1 || m > n) return false; + } else if (whichType == TxoutType::WITNESS_V3_P2QRH) { + // Accept as standard + return true; } return true; @@ -242,6 +245,9 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) { return false; } + } else if (whichType == TxoutType::WITNESS_V3_P2QRH) { + // Accept as standard + continue; } } @@ -333,6 +339,34 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) return false; } } + + // Check policy limits for P2QRH spends: + // - MAX_STANDARD_P2QRH_STACK_ITEM_SIZE limit for stack item size + // - Script path only (no key path spending) + // - No annexes + if (witnessversion == 3 && witnessprogram.size() == WITNESS_V3_P2QRH_SIZE) { + // P2QRH spend (non-P2SH-wrapped, version 3, witness program size 32) + std::span stack{tx.vin[i].scriptWitness.stack}; + if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { + // Annexes are nonstandard as long as no semantics are defined for them. + return false; + } + if (stack.size() >= 2) { + // Script path spend (2 or more stack elements after removing optional annex) + const auto& control_block = SpanPopBack(stack); + SpanPopBack(stack); // Ignore script + if (control_block.empty()) return false; // Empty control block is invalid + if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) { + // Leaf version 0xc0 (aka Tapscript, see BIP 342) + for (const auto& item : stack) { + if (item.size() > MAX_STANDARD_P2QRH_STACK_ITEM_SIZE) return false; + } + } + } else { + // P2QRH only supports script path spending, no key path spending allowed + return false; + } + } } return true; } diff --git a/src/policy/policy.h b/src/policy/policy.h index f9a18561bcea..20f7fc97fd4b 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -52,6 +52,8 @@ static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS{100}; static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE{80}; /** The maximum size in bytes of each witness stack item in a standard BIP 342 script (Taproot, leaf version 0xc0) */ static constexpr unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE{80}; +/** The maximum size in bytes of each witness stack item in a standard P2QRH script (Quantum-Resistant-Hash) */ +static constexpr unsigned int MAX_STANDARD_P2QRH_STACK_ITEM_SIZE{8000}; /** The maximum size in bytes of a standard witnessScript */ static constexpr unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE{3600}; /** The maximum size of a standard ScriptSig */ diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 6041c89e7f10..016faa0dfb15 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -236,6 +236,7 @@ bool XOnlyPubKey::IsFullyValid() const bool XOnlyPubKey::VerifySchnorr(const uint256& msg, std::span sigbytes) const { assert(sigbytes.size() == 64); + secp256k1_xonly_pubkey pubkey; if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false; return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 9c26e5c733c1..7fa711d64dec 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -547,6 +547,7 @@ static RPCHelpMan decodescript() case TxoutType::SCRIPTHASH: case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_V1_TAPROOT: + case TxoutType::WITNESS_V3_P2QRH: case TxoutType::ANCHOR: // Should not be wrapped return false; @@ -590,6 +591,7 @@ static RPCHelpMan decodescript() case TxoutType::WITNESS_V0_KEYHASH: case TxoutType::WITNESS_V0_SCRIPTHASH: case TxoutType::WITNESS_V1_TAPROOT: + case TxoutType::WITNESS_V3_P2QRH: case TxoutType::ANCHOR: // Should not be wrapped return false; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 5da02b4df4e4..48ab83b02aa4 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -345,6 +345,16 @@ class DescribeAddressVisitor obj.pushKV("witness_program", HexStr(id.GetWitnessProgram())); return obj; } + + UniValue operator()(const WitnessV3P2QRH& id) const + { + UniValue obj(UniValue::VOBJ); + obj.pushKV("isscript", true); + obj.pushKV("iswitness", true); + obj.pushKV("witness_version", 3); + obj.pushKV("witness_program", HexStr(id)); + return obj; + } }; UniValue DescribeAddress(const CTxDestination& dest) diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index bd819d365ae6..2db51b6cf1c8 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1403,7 +1403,7 @@ class TRDescriptor final : public DescriptorImpl ret += tmp; while (!path.empty() && path.back()) { if (path.size() > 1) ret += '}'; - path.pop_back(); + path.pop_back(); // move up one level after encountering '}' } if (!path.empty()) path.back() = true; } @@ -1602,6 +1602,102 @@ class RawTRDescriptor final : public DescriptorImpl } }; +class QRHDescriptor final : public DescriptorImpl +{ + std::vector m_depths; +protected: + std::vector MakeScripts(const std::vector& keys, std::span scripts, FlatSigningProvider& out) const override + { + // P2QRH only supports script path, no keypath + // For now, implement a basic version that creates a witness v3 output + // This would need to be enhanced with actual quantum-resistant hashing + + assert(m_depths.size() == scripts.size()); + + if (scripts.empty()) { + // No scripts provided, return empty + return {}; + } + + // For P2QRH, we create a witness v3 output with the script tree + // The output script is: OP_3 <32-byte-hash> + // For now, use a placeholder hash - in a real implementation this would be + // computed using quantum-resistant hashing of the script tree + + // Compute the quantum-resistant merkle root from the script tree + // Use the same merkle root computation as Taproot + TaprootBuilder builder; + for (size_t pos = 0; pos < m_depths.size(); ++pos) { + builder.Add(m_depths[pos], scripts[pos], TAPROOT_LEAF_TAPSCRIPT); + } + if (!builder.IsComplete()) return {}; + + // Create a dummy internal key for finalization (we only need the merkle root) + // Use NUMS_H as placeholder since P2QRH doesn't use keypath + XOnlyPubKey dummy_key = XOnlyPubKey::NUMS_H; + builder.Finalize(dummy_key); + + // Get the merkle root from the builder + uint256 merkle_root = builder.GetSpendData().merkle_root; + + CScript output_script; + output_script << OP_3 << ToByteVector(merkle_root); + + return {output_script}; + } + + bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override + { + if (m_depths.empty()) return true; + std::vector path; + for (size_t pos = 0; pos < m_depths.size(); ++pos) { + if (pos) ret += ','; + while ((int)path.size() <= m_depths[pos]) { + if (path.size()) ret += '{'; + path.push_back(false); + } + std::string tmp; + if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, type, cache)) return false; + ret += tmp; + while (!path.empty() && path.back()) { + if (path.size() > 1) ret += '}'; + path.pop_back(); + } + if (!path.empty()) path.back() = true; + } + return true; + } +public: + QRHDescriptor(std::vector> descs, std::vector depths) : + DescriptorImpl({}, std::move(descs), "qrh"), m_depths(std::move(depths)) + { + assert(m_subdescriptor_args.size() == m_depths.size()); + } + + std::optional GetOutputType() const override { return OutputType::BECH32M; } + bool IsSingleType() const final { return true; } + + std::optional ScriptSize() const override { return 1 + 1 + 32; } + + std::optional MaxSatisfactionWeight(bool) const override { + // P2QRH only supports script path, no keypath + return 1 + 65; // Script path satisfaction + } + + std::optional MaxSatisfactionElems() const override { + // Script path satisfaction elements + return 1; + } + + std::unique_ptr Clone() const override + { + std::vector> subdescs; + subdescs.reserve(m_subdescriptor_args.size()); + std::transform(m_subdescriptor_args.begin(), m_subdescriptor_args.end(), subdescs.begin(), [](const std::unique_ptr& d) { return d->Clone(); }); + return std::make_unique(std::move(subdescs), m_depths); + } +}; + //////////////////////////////////////////////////////////////////////////// // Parser // //////////////////////////////////////////////////////////////////////////// @@ -1613,6 +1709,7 @@ enum class ParseScriptContext { 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()) + P2QRH, //!< Inside qrh() (Bip360 script leaf only) }; std::optional ParseKeyPathNum(std::span elem, bool& apostrophe, std::string& error, bool& has_hardened) @@ -1766,7 +1863,7 @@ std::vector> ParsePubkeyInner(uint32_t key_exp_i error = "Uncompressed keys are not allowed"; return {}; } - } else if (data.size() == 32 && ctx == ParseScriptContext::P2TR) { + } else if (data.size() == 32 && (ctx == ParseScriptContext::P2TR || ctx == ParseScriptContext::P2QRH)) { unsigned char fullkey[33] = {0x02}; std::copy(data.begin(), data.end(), fullkey + 1); pubkey.Set(std::begin(fullkey), std::end(fullkey)); @@ -1783,7 +1880,7 @@ std::vector> ParsePubkeyInner(uint32_t key_exp_i if (permit_uncompressed || key.IsCompressed()) { CPubKey pubkey = key.GetPubKey(); out.keys.emplace(pubkey.GetID(), key); - ret.emplace_back(std::make_unique(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR)); + ret.emplace_back(std::make_unique(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR || ctx == ParseScriptContext::P2QRH)); return ret; } else { error = "Uncompressed keys are not allowed"; @@ -2061,6 +2158,7 @@ struct KeyParser { switch (m_script_ctx) { case miniscript::MiniscriptContext::P2WSH: return ParseScriptContext::P2WSH; case miniscript::MiniscriptContext::TAPSCRIPT: return ParseScriptContext::P2TR; + case miniscript::MiniscriptContext::P2QRH: return ParseScriptContext::P2QRH; } assert(false); } @@ -2133,7 +2231,7 @@ struct KeyParser { std::vector> ParseScript(uint32_t& key_exp_index, std::span& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { using namespace script; - Assume(ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR); + Assume(ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR || ctx == ParseScriptContext::P2QRH); std::vector> ret; auto expr = Expr(sp); if (Func("pk", expr)) { @@ -2180,7 +2278,7 @@ std::vector> ParseScript(uint32_t& key_exp_index const bool multi_a = !(multi || sortedmulti) && Func("multi_a", expr); const bool sortedmulti_a = !(multi || sortedmulti || multi_a) && Func("sortedmulti_a", expr); if (((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && (multi || sortedmulti)) || - (ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) { + ((ctx == ParseScriptContext::P2TR || ctx == ParseScriptContext::P2QRH) && (multi_a || sortedmulti_a))) { auto threshold = Expr(expr); uint32_t thres; std::vector>> providers; // List of multipath expanded pubkeys @@ -2422,6 +2520,71 @@ std::vector> ParseScript(uint32_t& key_exp_index error = "Can only have tr at top level"; return {}; } + if (ctx == ParseScriptContext::TOP && Func("qrh", expr)) { + // P2QRH only supports script path, no internal key + std::vector>> subscripts; + std::vector depths; + + if (expr.size()) { + /** The path from the top of the tree to what we're currently processing. + * branches[i] == false: left branch in the i'th step from the top; true: right branch. + */ + std::vector branches; + // Loop over all provided scripts. In every iteration exactly one script will be processed. + do { + // First process all open braces. + while (Const("{", expr)) { + branches.push_back(false); // new left branch + if (branches.size() > TAPROOT_CONTROL_MAX_NODE_COUNT) { + error = strprintf("qrh() supports at most %i nesting levels", TAPROOT_CONTROL_MAX_NODE_COUNT); + return {}; + } + } + // Process the actual script expression. + auto sarg = Expr(expr); + subscripts.emplace_back(ParseScript(key_exp_index, sarg, ParseScriptContext::P2QRH, out, error)); + if (subscripts.back().empty()) return {}; + 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)) { + error = strprintf("qrh(): expected '}' after script expression"); + return {}; + } + branches.pop_back(); + } + // If after that, we're at the end of a left branch, expect a comma. + if (branches.size() && !branches.back()) { + if (!Const(",", expr)) { + error = strprintf("qrh(): expected ',' after script expression"); + return {}; + } + branches.back() = true; + } + } while (branches.size()); + // After we've explored a whole tree, we must be at the end of the expression. + if (expr.size()) { + error = strprintf("qrh(): expected ')' after script expression"); + return {}; + } + } + + assert(TaprootBuilder::ValidDepths(depths)); + + // Build the final descriptors vector + // For qrh(), we create a single descriptor with all subdescriptors + std::vector> all_descs; + for (auto& subscripts_vec : subscripts) { + for (auto& desc : subscripts_vec) { + all_descs.push_back(std::move(desc)); + } + } + ret.emplace_back(std::make_unique(std::move(all_descs), depths)); + return ret; + } else if (Func("qrh", expr)) { + error = "Can only have qrh at top level"; + return {}; + } if (ctx == ParseScriptContext::TOP && Func("rawtr", expr)) { auto arg = Expr(expr); if (expr.size()) { @@ -2457,7 +2620,9 @@ std::vector> ParseScript(uint32_t& key_exp_index } // 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 : + ctx == ParseScriptContext::P2QRH ? miniscript::MiniscriptContext::P2QRH : + miniscript::MiniscriptContext::TAPSCRIPT}; KeyParser parser(/*out = */&out, /* in = */nullptr, /* ctx = */script_ctx, key_exp_index); auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser); if (parser.m_key_parsing_error != "") { @@ -2465,8 +2630,8 @@ std::vector> ParseScript(uint32_t& key_exp_index return {}; } if (node) { - if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR) { - error = "Miniscript expressions can only be used in wsh or tr."; + if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR && ctx != ParseScriptContext::P2QRH) { + error = "Miniscript expressions can only be used in wsh, tr, or qrh."; return {}; } if (!node->IsSane() || node->IsNotSatisfiable()) { @@ -2563,7 +2728,7 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo return std::make_unique(InferXOnlyPubkey(key, ctx, provider), true); } - if (ctx == ParseScriptContext::P2TR) { + if (ctx == ParseScriptContext::P2TR || ctx == ParseScriptContext::P2QRH) { auto ret = InferMultiA(script, ctx, provider); if (ret) return ret; } @@ -2670,8 +2835,10 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo } } - if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR) { - const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT}; + if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR || ctx == ParseScriptContext::P2QRH) { + const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : + ctx == ParseScriptContext::P2QRH ? miniscript::MiniscriptContext::P2QRH : + miniscript::MiniscriptContext::TAPSCRIPT}; KeyParser parser(/* out = */nullptr, /* in = */&provider, /* ctx = */script_ctx); auto node = miniscript::FromScript(script, parser); if (node && node->IsSane()) { @@ -2697,8 +2864,6 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo return std::make_unique(script); } - - } // namespace /** Check a descriptor checksum, and update desc to be the checksum-less part. */ diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 61ea7f4503c2..7ffce9fa8b97 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -11,6 +11,7 @@ #include #include