Merge 58e9c7ca89654abdae2e37fe848ed8afc1e250e1 into 5f4422d68dc3530c353af1f87499de1c864b60ad

This commit is contained in:
hodlinator 2025-03-17 03:54:44 +01:00 committed by GitHub
commit 6dcb8951a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 276 additions and 278 deletions

View File

@ -1301,13 +1301,13 @@ public:
class MiniscriptDescriptor final : public DescriptorImpl class MiniscriptDescriptor final : public DescriptorImpl
{ {
private: private:
miniscript::NodeRef<uint32_t> m_node; const miniscript::Node<uint32_t> m_node;
protected: protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
FlatSigningProvider& provider) const override FlatSigningProvider& provider) const override
{ {
const auto script_ctx{m_node->GetMsCtx()}; const auto script_ctx{m_node.GetMsCtx()};
for (const auto& key : keys) { for (const auto& key : keys) {
if (miniscript::IsTapscript(script_ctx)) { if (miniscript::IsTapscript(script_ctx)) {
provider.pubkeys.emplace(Hash160(XOnlyPubKey{key}), key); provider.pubkeys.emplace(Hash160(XOnlyPubKey{key}), key);
@ -1315,17 +1315,17 @@ protected:
provider.pubkeys.emplace(key.GetID(), key); provider.pubkeys.emplace(key.GetID(), key);
} }
} }
return Vector(m_node->ToScript(ScriptMaker(keys, script_ctx))); return Vector(m_node.ToScript(ScriptMaker(keys, script_ctx)));
} }
public: public:
MiniscriptDescriptor(std::vector<std::unique_ptr<PubkeyProvider>> providers, miniscript::NodeRef<uint32_t> node) MiniscriptDescriptor(std::vector<std::unique_ptr<PubkeyProvider>> providers, miniscript::Node<uint32_t>&& node)
: DescriptorImpl(std::move(providers), "?"), m_node(std::move(node)) {} : DescriptorImpl(std::move(providers), "?"), m_node(std::move(node)) {}
bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type,
const DescriptorCache* cache = nullptr) const override const DescriptorCache* cache = nullptr) const override
{ {
if (const auto res = m_node->ToString(StringMaker(arg, m_pubkey_args, type == StringType::PRIVATE))) { if (const auto res = m_node.ToString(StringMaker(arg, m_pubkey_args, type == StringType::PRIVATE))) {
out = *res; out = *res;
return true; return true;
} }
@ -1335,15 +1335,15 @@ public:
bool IsSolvable() const override { return true; } bool IsSolvable() const override { return true; }
bool IsSingleType() const final { return true; } bool IsSingleType() const final { return true; }
std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); } std::optional<int64_t> ScriptSize() const override { return m_node.ScriptSize(); }
std::optional<int64_t> MaxSatSize(bool) const override { std::optional<int64_t> MaxSatSize(bool) const override {
// For Miniscript we always assume high-R ECDSA signatures. // For Miniscript we always assume high-R ECDSA signatures.
return m_node->GetWitnessSize(); return m_node.GetWitnessSize();
} }
std::optional<int64_t> MaxSatisfactionElems() const override { std::optional<int64_t> MaxSatisfactionElems() const override {
return m_node->GetStackSize(); return m_node.GetStackSize();
} }
std::unique_ptr<DescriptorImpl> Clone() const override std::unique_ptr<DescriptorImpl> Clone() const override
@ -1353,7 +1353,7 @@ public:
for (const auto& arg : m_pubkey_args) { for (const auto& arg : m_pubkey_args) {
providers.push_back(arg->Clone()); providers.push_back(arg->Clone());
} }
return std::make_unique<MiniscriptDescriptor>(std::move(providers), m_node->Clone()); return std::make_unique<MiniscriptDescriptor>(std::move(providers), m_node.Clone());
} }
}; };
@ -2090,7 +2090,7 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
} }
if (!node->IsSane() || node->IsNotSatisfiable()) { if (!node->IsSane() || node->IsNotSatisfiable()) {
// Try to find the first insane sub for better error reporting. // Try to find the first insane sub for better error reporting.
auto insane_node = node.get(); const auto* insane_node = &*node;
if (const auto sub = node->FindInsaneSub()) insane_node = sub; if (const auto sub = node->FindInsaneSub()) insane_node = sub;
if (const auto str = insane_node->ToString(parser)) error = *str; if (const auto str = insane_node->ToString(parser)) error = *str;
if (!insane_node->IsValid()) { if (!insane_node->IsValid()) {
@ -2099,7 +2099,7 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
error += " is not sane"; error += " is not sane";
if (!insane_node->IsNonMalleable()) { if (!insane_node->IsNonMalleable()) {
error += ": malleable witnesses exist"; error += ": malleable witnesses exist";
} else if (insane_node == node.get() && !insane_node->NeedsSignature()) { } else if (insane_node == &*node && !insane_node->NeedsSignature()) {
error += ": witnesses without signature exist"; error += ": witnesses without signature exist";
} else if (!insane_node->CheckTimeLocksMix()) { } else if (!insane_node->CheckTimeLocksMix()) {
error += ": contains mixes of timelocks expressed in blocks and seconds"; error += ": contains mixes of timelocks expressed in blocks and seconds";
@ -2299,7 +2299,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
for (auto& key : parser.m_keys) { for (auto& key : parser.m_keys) {
keys.emplace_back(std::move(key.at(0))); keys.emplace_back(std::move(key.at(0)));
} }
return std::make_unique<MiniscriptDescriptor>(std::move(keys), std::move(node)); return std::make_unique<MiniscriptDescriptor>(std::move(keys), std::move(*node));
} }
} }

View File

@ -188,13 +188,6 @@ inline consteval Type operator""_mst(const char* c, size_t l)
using Opcode = std::pair<opcodetype, std::vector<unsigned char>>; using Opcode = std::pair<opcodetype, std::vector<unsigned char>>;
template<typename Key> struct Node;
template<typename Key> using NodeRef = std::unique_ptr<const Node<Key>>;
//! Construct a miniscript node as a unique_ptr.
template<typename Key, typename... Args>
NodeRef<Key> MakeNodeRef(Args&&... args) { return std::make_unique<const Node<Key>>(std::forward<Args>(args)...); }
//! The different node types in miniscript. //! The different node types in miniscript.
enum class Fragment { enum class Fragment {
JUST_0, //!< OP_0 JUST_0, //!< OP_0
@ -349,8 +342,8 @@ struct InputResult {
//! Class whose objects represent the maximum of a list of integers. //! Class whose objects represent the maximum of a list of integers.
template<typename I> template<typename I>
struct MaxInt { struct MaxInt {
const bool valid; bool valid;
const I value; I value;
MaxInt() : valid(false), value(0) {} MaxInt() : valid(false), value(0) {}
MaxInt(I val) : valid(true), value(val) {} MaxInt(I val) : valid(true), value(val) {}
@ -421,11 +414,11 @@ struct Ops {
*/ */
struct SatInfo { struct SatInfo {
//! Whether a canonical satisfaction/dissatisfaction is possible at all. //! Whether a canonical satisfaction/dissatisfaction is possible at all.
const bool valid; bool valid;
//! How much higher the stack size at start of execution can be compared to at the end. //! How much higher the stack size at start of execution can be compared to at the end.
const int32_t netdiff; int32_t netdiff;
//! Mow much higher the stack size can be during execution compared to at the end. //! Mow much higher the stack size can be during execution compared to at the end.
const int32_t exec; int32_t exec;
/** Empty script set. */ /** Empty script set. */
constexpr SatInfo() noexcept : valid(false), netdiff(0), exec(0) {} constexpr SatInfo() noexcept : valid(false), netdiff(0), exec(0) {}
@ -480,7 +473,7 @@ struct SatInfo {
}; };
struct StackSize { struct StackSize {
const SatInfo sat, dsat; SatInfo sat, dsat;
constexpr StackSize(SatInfo in_sat, SatInfo in_dsat) noexcept : sat(in_sat), dsat(in_dsat) {}; constexpr StackSize(SatInfo in_sat, SatInfo in_dsat) noexcept : sat(in_sat), dsat(in_dsat) {};
constexpr StackSize(SatInfo in_both) noexcept : sat(in_both), dsat(in_both) {}; constexpr StackSize(SatInfo in_both) noexcept : sat(in_both), dsat(in_both) {};
@ -503,56 +496,59 @@ struct NoDupCheck {};
template<typename Key> template<typename Key>
struct Node { struct Node {
//! What node type this node is. //! What node type this node is.
const Fragment fragment; Fragment fragment;
//! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M)) //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M))
const uint32_t k = 0; uint32_t k = 0;
//! The keys used by this expression (only for PK_K/PK_H/MULTI) //! The keys used by this expression (only for PK_K/PK_H/MULTI)
const std::vector<Key> keys; std::vector<Key> keys;
//! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD10). //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD10).
const std::vector<unsigned char> data; std::vector<unsigned char> data;
//! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH) //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH)
mutable std::vector<NodeRef<Key>> subs; std::vector<Node> subs;
//! The Script context for this node. Either P2WSH or Tapscript. //! The Script context for this node. Either P2WSH or Tapscript.
const MiniscriptContext m_script_ctx; MiniscriptContext m_script_ctx;
/* Destroy the shared pointers iteratively to avoid a stack-overflow due to recursive calls ~Node()
* to the subs' destructors. */ {
~Node() { // Destroy the subexpressions iteratively to avoid a stack-overflow due
while (!subs.empty()) { // to recursive calls to the subs' destructors.
auto node = std::move(subs.back()); std::vector<std::vector<Node<Key>>> to_be_stripped;
subs.pop_back(); to_be_stripped.emplace_back(std::move(subs));
while (!node->subs.empty()) { while (!to_be_stripped.empty()) {
subs.push_back(std::move(node->subs.back())); auto stripping{std::move(to_be_stripped.back())};
node->subs.pop_back(); to_be_stripped.pop_back();
for (auto& i : stripping) {
to_be_stripped.emplace_back(std::move(i.subs));
} }
} }
} }
NodeRef<Key> Clone() const Node<Key> Clone() const
{ {
// Use TreeEval() to avoid a stack-overflow due to recursion // Use TreeEval() to avoid a stack-overflow due to recursion
auto upfn = [](const Node& node, Span<NodeRef<Key>> children) { auto upfn = [](const Node& node, Span<Node> children) {
std::vector<NodeRef<Key>> new_subs; std::vector<Node> new_subs;
for (auto child = children.begin(); child != children.end(); ++child) { for (auto& child : children) {
new_subs.emplace_back(std::move(*child)); // It's fine to move from children as they are new nodes having
// been produced by calling this function one level down.
new_subs.emplace_back(std::move(child));
} }
// std::make_unique (and therefore MakeNodeRef) doesn't work on private constructors return Node{internal::NoDupCheck{}, node.m_script_ctx, node.fragment, std::move(new_subs), node.keys, node.data, node.k};
return std::unique_ptr<Node>{new Node{internal::NoDupCheck{}, node.m_script_ctx, node.fragment, std::move(new_subs), node.keys, node.data, node.k}};
}; };
return TreeEval<NodeRef<Key>>(upfn); return TreeEval<Node>(upfn);
} }
private: private:
//! Cached ops counts. //! Cached ops counts.
const internal::Ops ops; internal::Ops ops;
//! Cached stack size bounds. //! Cached stack size bounds.
const internal::StackSize ss; internal::StackSize ss;
//! Cached witness size bounds. //! Cached witness size bounds.
const internal::WitnessSize ws; internal::WitnessSize ws;
//! Cached expression type (computed by CalcType and fed through SanitizeType). //! Cached expression type (computed by CalcType and fed through SanitizeType).
const Type typ; Type typ;
//! Cached script length (computed by CalcScriptLen). //! Cached script length (computed by CalcScriptLen).
const size_t scriptlen; size_t scriptlen;
//! Whether a public key appears more than once in this node. This value is initialized //! Whether a public key appears more than once in this node. This value is initialized
//! by all constructors except the NoDupCheck ones. The NoDupCheck ones skip the //! by all constructors except the NoDupCheck ones. The NoDupCheck ones skip the
//! computation, requiring it to be done manually by invoking DuplicateKeyCheck(). //! computation, requiring it to be done manually by invoking DuplicateKeyCheck().
@ -563,17 +559,17 @@ private:
// Constructor which takes all of the data that a Node could possibly contain. // Constructor which takes all of the data that a Node could possibly contain.
// This is kept private as no valid fragment has all of these arguments. // This is kept private as no valid fragment has all of these arguments.
// Only used by Clone() // Only used by Clone()
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, std::vector<unsigned char> arg, uint32_t val) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Node<Key>> sub, std::vector<Key> key, std::vector<unsigned char> arg, uint32_t val)
: fragment(nt), k(val), keys(key), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), keys(key), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
//! Compute the length of the script for this miniscript (including children). //! Compute the length of the script for this miniscript (including children).
size_t CalcScriptLen() const { size_t CalcScriptLen() const {
size_t subsize = 0; size_t subsize = 0;
for (const auto& sub : subs) { for (const auto& sub : subs) {
subsize += sub->ScriptSize(); subsize += sub.ScriptSize();
} }
static constexpr auto NONE_MST{""_mst}; static constexpr auto NONE_MST{""_mst};
Type sub0type = subs.size() > 0 ? subs[0]->GetType() : NONE_MST; Type sub0type = subs.size() > 0 ? subs[0].GetType() : NONE_MST;
return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size(), m_script_ctx); return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size(), m_script_ctx);
} }
@ -644,7 +640,7 @@ private:
* that child (and all earlier children) will be at the end of `results`. */ * that child (and all earlier children) will be at the end of `results`. */
size_t child_index = stack.back().expanded++; size_t child_index = stack.back().expanded++;
State child_state = downfn(stack.back().state, node, child_index); State child_state = downfn(stack.back().state, node, child_index);
stack.emplace_back(*node.subs[child_index], 0, std::move(child_state)); stack.emplace_back(node.subs[child_index], 0, std::move(child_state));
continue; continue;
} }
// Invoke upfn with the last node.subs.size() elements of results as input. // Invoke upfn with the last node.subs.size() elements of results as input.
@ -721,7 +717,7 @@ private:
if (b.subs.size() < a.subs.size()) return 1; if (b.subs.size() < a.subs.size()) return 1;
size_t n = a.subs.size(); size_t n = a.subs.size();
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
queue.emplace_back(*a.subs[n - 1 - i], *b.subs[n - 1 - i]); queue.emplace_back(a.subs[n - 1 - i], b.subs[n - 1 - i]);
} }
} }
return 0; return 0;
@ -734,13 +730,13 @@ private:
// THRESH has a variable number of subexpressions // THRESH has a variable number of subexpressions
std::vector<Type> sub_types; std::vector<Type> sub_types;
if (fragment == Fragment::THRESH) { if (fragment == Fragment::THRESH) {
for (const auto& sub : subs) sub_types.push_back(sub->GetType()); for (const auto& sub : subs) sub_types.push_back(sub.GetType());
} }
// All other nodes than THRESH can be computed just from the types of the 0-3 subexpressions. // All other nodes than THRESH can be computed just from the types of the 0-3 subexpressions.
static constexpr auto NONE_MST{""_mst}; static constexpr auto NONE_MST{""_mst};
Type x = subs.size() > 0 ? subs[0]->GetType() : NONE_MST; Type x = subs.size() > 0 ? subs[0].GetType() : NONE_MST;
Type y = subs.size() > 1 ? subs[1]->GetType() : NONE_MST; Type y = subs.size() > 1 ? subs[1].GetType() : NONE_MST;
Type z = subs.size() > 2 ? subs[2]->GetType() : NONE_MST; Type z = subs.size() > 2 ? subs[2].GetType() : NONE_MST;
return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size(), m_script_ctx)); return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size(), m_script_ctx));
} }
@ -779,7 +775,7 @@ public:
case Fragment::WRAP_C: return BuildScript(std::move(subs[0]), verify ? OP_CHECKSIGVERIFY : OP_CHECKSIG); case Fragment::WRAP_C: return BuildScript(std::move(subs[0]), verify ? OP_CHECKSIGVERIFY : OP_CHECKSIG);
case Fragment::WRAP_D: return BuildScript(OP_DUP, OP_IF, subs[0], OP_ENDIF); case Fragment::WRAP_D: return BuildScript(OP_DUP, OP_IF, subs[0], OP_ENDIF);
case Fragment::WRAP_V: { case Fragment::WRAP_V: {
if (node.subs[0]->GetType() << "x"_mst) { if (node.subs[0].GetType() << "x"_mst) {
return BuildScript(std::move(subs[0]), OP_VERIFY); return BuildScript(std::move(subs[0]), OP_VERIFY);
} else { } else {
return std::move(subs[0]); return std::move(subs[0]);
@ -835,9 +831,9 @@ public:
node.fragment == Fragment::WRAP_D || node.fragment == Fragment::WRAP_V || node.fragment == Fragment::WRAP_D || node.fragment == Fragment::WRAP_V ||
node.fragment == Fragment::WRAP_J || node.fragment == Fragment::WRAP_N || node.fragment == Fragment::WRAP_J || node.fragment == Fragment::WRAP_N ||
node.fragment == Fragment::WRAP_C || node.fragment == Fragment::WRAP_C ||
(node.fragment == Fragment::AND_V && node.subs[1]->fragment == Fragment::JUST_1) || (node.fragment == Fragment::AND_V && node.subs[1].fragment == Fragment::JUST_1) ||
(node.fragment == Fragment::OR_I && node.subs[0]->fragment == Fragment::JUST_0) || (node.fragment == Fragment::OR_I && node.subs[0].fragment == Fragment::JUST_0) ||
(node.fragment == Fragment::OR_I && node.subs[1]->fragment == Fragment::JUST_0)); (node.fragment == Fragment::OR_I && node.subs[1].fragment == Fragment::JUST_0));
}; };
// The upward function computes for a node, given whether its parent is a wrapper, // The upward function computes for a node, given whether its parent is a wrapper,
// and the string representations of its child nodes, the string representation of the node. // and the string representations of its child nodes, the string representation of the node.
@ -849,15 +845,15 @@ public:
case Fragment::WRAP_A: return "a" + std::move(subs[0]); case Fragment::WRAP_A: return "a" + std::move(subs[0]);
case Fragment::WRAP_S: return "s" + std::move(subs[0]); case Fragment::WRAP_S: return "s" + std::move(subs[0]);
case Fragment::WRAP_C: case Fragment::WRAP_C:
if (node.subs[0]->fragment == Fragment::PK_K) { if (node.subs[0].fragment == Fragment::PK_K) {
// pk(K) is syntactic sugar for c:pk_k(K) // pk(K) is syntactic sugar for c:pk_k(K)
auto key_str = ctx.ToString(node.subs[0]->keys[0]); auto key_str = ctx.ToString(node.subs[0].keys[0]);
if (!key_str) return {}; if (!key_str) return {};
return std::move(ret) + "pk(" + std::move(*key_str) + ")"; return std::move(ret) + "pk(" + std::move(*key_str) + ")";
} }
if (node.subs[0]->fragment == Fragment::PK_H) { if (node.subs[0].fragment == Fragment::PK_H) {
// pkh(K) is syntactic sugar for c:pk_h(K) // pkh(K) is syntactic sugar for c:pk_h(K)
auto key_str = ctx.ToString(node.subs[0]->keys[0]); auto key_str = ctx.ToString(node.subs[0].keys[0]);
if (!key_str) return {}; if (!key_str) return {};
return std::move(ret) + "pkh(" + std::move(*key_str) + ")"; return std::move(ret) + "pkh(" + std::move(*key_str) + ")";
} }
@ -868,11 +864,11 @@ public:
case Fragment::WRAP_N: return "n" + std::move(subs[0]); case Fragment::WRAP_N: return "n" + std::move(subs[0]);
case Fragment::AND_V: case Fragment::AND_V:
// t:X is syntactic sugar for and_v(X,1). // t:X is syntactic sugar for and_v(X,1).
if (node.subs[1]->fragment == Fragment::JUST_1) return "t" + std::move(subs[0]); if (node.subs[1].fragment == Fragment::JUST_1) return "t" + std::move(subs[0]);
break; break;
case Fragment::OR_I: case Fragment::OR_I:
if (node.subs[0]->fragment == Fragment::JUST_0) return "l" + std::move(subs[1]); if (node.subs[0].fragment == Fragment::JUST_0) return "l" + std::move(subs[1]);
if (node.subs[1]->fragment == Fragment::JUST_0) return "u" + std::move(subs[0]); if (node.subs[1].fragment == Fragment::JUST_0) return "u" + std::move(subs[0]);
break; break;
default: break; default: break;
} }
@ -903,7 +899,7 @@ public:
case Fragment::OR_I: return std::move(ret) + "or_i(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; case Fragment::OR_I: return std::move(ret) + "or_i(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
case Fragment::ANDOR: case Fragment::ANDOR:
// and_n(X,Y) is syntactic sugar for andor(X,Y,0). // and_n(X,Y) is syntactic sugar for andor(X,Y,0).
if (node.subs[2]->fragment == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; if (node.subs[2].fragment == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
return std::move(ret) + "andor(" + std::move(subs[0]) + "," + std::move(subs[1]) + "," + std::move(subs[2]) + ")"; return std::move(ret) + "andor(" + std::move(subs[0]) + "," + std::move(subs[1]) + "," + std::move(subs[2]) + ")";
case Fragment::MULTI: { case Fragment::MULTI: {
CHECK_NONFATAL(!is_tapscript); CHECK_NONFATAL(!is_tapscript);
@ -953,59 +949,59 @@ private:
case Fragment::RIPEMD160: case Fragment::RIPEMD160:
case Fragment::HASH256: case Fragment::HASH256:
case Fragment::HASH160: return {4, 0, {}}; case Fragment::HASH160: return {4, 0, {}};
case Fragment::AND_V: return {subs[0]->ops.count + subs[1]->ops.count, subs[0]->ops.sat + subs[1]->ops.sat, {}}; case Fragment::AND_V: return {subs[0].ops.count + subs[1].ops.count, subs[0].ops.sat + subs[1].ops.sat, {}};
case Fragment::AND_B: { case Fragment::AND_B: {
const auto count{1 + subs[0]->ops.count + subs[1]->ops.count}; const auto count{1 + subs[0].ops.count + subs[1].ops.count};
const auto sat{subs[0]->ops.sat + subs[1]->ops.sat}; const auto sat{subs[0].ops.sat + subs[1].ops.sat};
const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat}; const auto dsat{subs[0].ops.dsat + subs[1].ops.dsat};
return {count, sat, dsat}; return {count, sat, dsat};
} }
case Fragment::OR_B: { case Fragment::OR_B: {
const auto count{1 + subs[0]->ops.count + subs[1]->ops.count}; const auto count{1 + subs[0].ops.count + subs[1].ops.count};
const auto sat{(subs[0]->ops.sat + subs[1]->ops.dsat) | (subs[1]->ops.sat + subs[0]->ops.dsat)}; const auto sat{(subs[0].ops.sat + subs[1].ops.dsat) | (subs[1].ops.sat + subs[0].ops.dsat)};
const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat}; const auto dsat{subs[0].ops.dsat + subs[1].ops.dsat};
return {count, sat, dsat}; return {count, sat, dsat};
} }
case Fragment::OR_D: { case Fragment::OR_D: {
const auto count{3 + subs[0]->ops.count + subs[1]->ops.count}; const auto count{3 + subs[0].ops.count + subs[1].ops.count};
const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)}; const auto sat{subs[0].ops.sat | (subs[1].ops.sat + subs[0].ops.dsat)};
const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat}; const auto dsat{subs[0].ops.dsat + subs[1].ops.dsat};
return {count, sat, dsat}; return {count, sat, dsat};
} }
case Fragment::OR_C: { case Fragment::OR_C: {
const auto count{2 + subs[0]->ops.count + subs[1]->ops.count}; const auto count{2 + subs[0].ops.count + subs[1].ops.count};
const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)}; const auto sat{subs[0].ops.sat | (subs[1].ops.sat + subs[0].ops.dsat)};
return {count, sat, {}}; return {count, sat, {}};
} }
case Fragment::OR_I: { case Fragment::OR_I: {
const auto count{3 + subs[0]->ops.count + subs[1]->ops.count}; const auto count{3 + subs[0].ops.count + subs[1].ops.count};
const auto sat{subs[0]->ops.sat | subs[1]->ops.sat}; const auto sat{subs[0].ops.sat | subs[1].ops.sat};
const auto dsat{subs[0]->ops.dsat | subs[1]->ops.dsat}; const auto dsat{subs[0].ops.dsat | subs[1].ops.dsat};
return {count, sat, dsat}; return {count, sat, dsat};
} }
case Fragment::ANDOR: { case Fragment::ANDOR: {
const auto count{3 + subs[0]->ops.count + subs[1]->ops.count + subs[2]->ops.count}; const auto count{3 + subs[0].ops.count + subs[1].ops.count + subs[2].ops.count};
const auto sat{(subs[1]->ops.sat + subs[0]->ops.sat) | (subs[0]->ops.dsat + subs[2]->ops.sat)}; const auto sat{(subs[1].ops.sat + subs[0].ops.sat) | (subs[0].ops.dsat + subs[2].ops.sat)};
const auto dsat{subs[0]->ops.dsat + subs[2]->ops.dsat}; const auto dsat{subs[0].ops.dsat + subs[2].ops.dsat};
return {count, sat, dsat}; return {count, sat, dsat};
} }
case Fragment::MULTI: return {1, (uint32_t)keys.size(), (uint32_t)keys.size()}; case Fragment::MULTI: return {1, (uint32_t)keys.size(), (uint32_t)keys.size()};
case Fragment::MULTI_A: return {(uint32_t)keys.size() + 1, 0, 0}; case Fragment::MULTI_A: return {(uint32_t)keys.size() + 1, 0, 0};
case Fragment::WRAP_S: case Fragment::WRAP_S:
case Fragment::WRAP_C: case Fragment::WRAP_C:
case Fragment::WRAP_N: return {1 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat}; case Fragment::WRAP_N: return {1 + subs[0].ops.count, subs[0].ops.sat, subs[0].ops.dsat};
case Fragment::WRAP_A: return {2 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat}; case Fragment::WRAP_A: return {2 + subs[0].ops.count, subs[0].ops.sat, subs[0].ops.dsat};
case Fragment::WRAP_D: return {3 + subs[0]->ops.count, subs[0]->ops.sat, 0}; case Fragment::WRAP_D: return {3 + subs[0].ops.count, subs[0].ops.sat, 0};
case Fragment::WRAP_J: return {4 + subs[0]->ops.count, subs[0]->ops.sat, 0}; case Fragment::WRAP_J: return {4 + subs[0].ops.count, subs[0].ops.sat, 0};
case Fragment::WRAP_V: return {subs[0]->ops.count + (subs[0]->GetType() << "x"_mst), subs[0]->ops.sat, {}}; case Fragment::WRAP_V: return {subs[0].ops.count + (subs[0].GetType() << "x"_mst), subs[0].ops.sat, {}};
case Fragment::THRESH: { case Fragment::THRESH: {
uint32_t count = 0; uint32_t count = 0;
auto sats = Vector(internal::MaxInt<uint32_t>(0)); auto sats = Vector(internal::MaxInt<uint32_t>(0));
for (const auto& sub : subs) { for (const auto& sub : subs) {
count += sub->ops.count + 1; count += sub.ops.count + 1;
auto next_sats = Vector(sats[0] + sub->ops.dsat); auto next_sats = Vector(sats[0] + sub.ops.dsat);
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ops.dsat) | (sats[j - 1] + sub->ops.sat)); for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub.ops.dsat) | (sats[j - 1] + sub.ops.sat));
next_sats.push_back(sats[sats.size() - 1] + sub->ops.sat); next_sats.push_back(sats[sats.size() - 1] + sub.ops.sat);
sats = std::move(next_sats); sats = std::move(next_sats);
} }
assert(k <= sats.size()); assert(k <= sats.size());
@ -1032,48 +1028,48 @@ private:
{} {}
}; };
case Fragment::ANDOR: { case Fragment::ANDOR: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
const auto& z{subs[2]->ss}; const auto& z{subs[2].ss};
return { return {
(x.sat + SatInfo::If() + y.sat) | (x.dsat + SatInfo::If() + z.sat), (x.sat + SatInfo::If() + y.sat) | (x.dsat + SatInfo::If() + z.sat),
x.dsat + SatInfo::If() + z.dsat x.dsat + SatInfo::If() + z.dsat
}; };
} }
case Fragment::AND_V: { case Fragment::AND_V: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
return {x.sat + y.sat, {}}; return {x.sat + y.sat, {}};
} }
case Fragment::AND_B: { case Fragment::AND_B: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
return {x.sat + y.sat + SatInfo::BinaryOp(), x.dsat + y.dsat + SatInfo::BinaryOp()}; return {x.sat + y.sat + SatInfo::BinaryOp(), x.dsat + y.dsat + SatInfo::BinaryOp()};
} }
case Fragment::OR_B: { case Fragment::OR_B: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
return { return {
((x.sat + y.dsat) | (x.dsat + y.sat)) + SatInfo::BinaryOp(), ((x.sat + y.dsat) | (x.dsat + y.sat)) + SatInfo::BinaryOp(),
x.dsat + y.dsat + SatInfo::BinaryOp() x.dsat + y.dsat + SatInfo::BinaryOp()
}; };
} }
case Fragment::OR_C: { case Fragment::OR_C: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
return {(x.sat + SatInfo::If()) | (x.dsat + SatInfo::If() + y.sat), {}}; return {(x.sat + SatInfo::If()) | (x.dsat + SatInfo::If() + y.sat), {}};
} }
case Fragment::OR_D: { case Fragment::OR_D: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
return { return {
(x.sat + SatInfo::OP_IFDUP(true) + SatInfo::If()) | (x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.sat), (x.sat + SatInfo::OP_IFDUP(true) + SatInfo::If()) | (x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.sat),
x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.dsat x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.dsat
}; };
} }
case Fragment::OR_I: { case Fragment::OR_I: {
const auto& x{subs[0]->ss}; const auto& x{subs[0].ss};
const auto& y{subs[1]->ss}; const auto& y{subs[1].ss};
return {SatInfo::If() + (x.sat | y.sat), SatInfo::If() + (x.dsat | y.dsat)}; return {SatInfo::If() + (x.sat | y.sat), SatInfo::If() + (x.dsat | y.dsat)};
} }
// multi(k, key1, key2, ..., key_n) starts off with k+1 stack elements (a 0, plus k // multi(k, key1, key2, ..., key_n) starts off with k+1 stack elements (a 0, plus k
@ -1087,18 +1083,18 @@ private:
case Fragment::MULTI_A: return {SatInfo(keys.size() - 1, keys.size())}; case Fragment::MULTI_A: return {SatInfo(keys.size() - 1, keys.size())};
case Fragment::WRAP_A: case Fragment::WRAP_A:
case Fragment::WRAP_N: case Fragment::WRAP_N:
case Fragment::WRAP_S: return subs[0]->ss; case Fragment::WRAP_S: return subs[0].ss;
case Fragment::WRAP_C: return { case Fragment::WRAP_C: return {
subs[0]->ss.sat + SatInfo::OP_CHECKSIG(), subs[0].ss.sat + SatInfo::OP_CHECKSIG(),
subs[0]->ss.dsat + SatInfo::OP_CHECKSIG() subs[0].ss.dsat + SatInfo::OP_CHECKSIG()
}; };
case Fragment::WRAP_D: return { case Fragment::WRAP_D: return {
SatInfo::OP_DUP() + SatInfo::If() + subs[0]->ss.sat, SatInfo::OP_DUP() + SatInfo::If() + subs[0].ss.sat,
SatInfo::OP_DUP() + SatInfo::If() SatInfo::OP_DUP() + SatInfo::If()
}; };
case Fragment::WRAP_V: return {subs[0]->ss.sat + SatInfo::OP_VERIFY(), {}}; case Fragment::WRAP_V: return {subs[0].ss.sat + SatInfo::OP_VERIFY(), {}};
case Fragment::WRAP_J: return { case Fragment::WRAP_J: return {
SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If() + subs[0]->ss.sat, SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If() + subs[0].ss.sat,
SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If() SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If()
}; };
case Fragment::THRESH: { case Fragment::THRESH: {
@ -1109,13 +1105,13 @@ private:
// element i we need to add OP_ADD (if i>0). // element i we need to add OP_ADD (if i>0).
auto add = i ? SatInfo::BinaryOp() : SatInfo::Empty(); auto add = i ? SatInfo::BinaryOp() : SatInfo::Empty();
// Construct a variable that will become the next sats, starting with index 0. // Construct a variable that will become the next sats, starting with index 0.
auto next_sats = Vector(sats[0] + subs[i]->ss.dsat + add); auto next_sats = Vector(sats[0] + subs[i].ss.dsat + add);
// Then loop to construct next_sats[1..i]. // Then loop to construct next_sats[1..i].
for (size_t j = 1; j < sats.size(); ++j) { for (size_t j = 1; j < sats.size(); ++j) {
next_sats.push_back(((sats[j] + subs[i]->ss.dsat) | (sats[j - 1] + subs[i]->ss.sat)) + add); next_sats.push_back(((sats[j] + subs[i].ss.dsat) | (sats[j - 1] + subs[i].ss.sat)) + add);
} }
// Finally construct next_sats[i+1]. // Finally construct next_sats[i+1].
next_sats.push_back(sats[sats.size() - 1] + subs[i]->ss.sat + add); next_sats.push_back(sats[sats.size() - 1] + subs[i].ss.sat + add);
// Switch over. // Switch over.
sats = std::move(next_sats); sats = std::move(next_sats);
} }
@ -1145,35 +1141,35 @@ private:
case Fragment::HASH256: case Fragment::HASH256:
case Fragment::HASH160: return {1 + 32, {}}; case Fragment::HASH160: return {1 + 32, {}};
case Fragment::ANDOR: { case Fragment::ANDOR: {
const auto sat{(subs[0]->ws.sat + subs[1]->ws.sat) | (subs[0]->ws.dsat + subs[2]->ws.sat)}; const auto sat{(subs[0].ws.sat + subs[1].ws.sat) | (subs[0].ws.dsat + subs[2].ws.sat)};
const auto dsat{subs[0]->ws.dsat + subs[2]->ws.dsat}; const auto dsat{subs[0].ws.dsat + subs[2].ws.dsat};
return {sat, dsat}; return {sat, dsat};
} }
case Fragment::AND_V: return {subs[0]->ws.sat + subs[1]->ws.sat, {}}; case Fragment::AND_V: return {subs[0].ws.sat + subs[1].ws.sat, {}};
case Fragment::AND_B: return {subs[0]->ws.sat + subs[1]->ws.sat, subs[0]->ws.dsat + subs[1]->ws.dsat}; case Fragment::AND_B: return {subs[0].ws.sat + subs[1].ws.sat, subs[0].ws.dsat + subs[1].ws.dsat};
case Fragment::OR_B: { case Fragment::OR_B: {
const auto sat{(subs[0]->ws.dsat + subs[1]->ws.sat) | (subs[0]->ws.sat + subs[1]->ws.dsat)}; const auto sat{(subs[0].ws.dsat + subs[1].ws.sat) | (subs[0].ws.sat + subs[1].ws.dsat)};
const auto dsat{subs[0]->ws.dsat + subs[1]->ws.dsat}; const auto dsat{subs[0].ws.dsat + subs[1].ws.dsat};
return {sat, dsat}; return {sat, dsat};
} }
case Fragment::OR_C: return {subs[0]->ws.sat | (subs[0]->ws.dsat + subs[1]->ws.sat), {}}; case Fragment::OR_C: return {subs[0].ws.sat | (subs[0].ws.dsat + subs[1].ws.sat), {}};
case Fragment::OR_D: return {subs[0]->ws.sat | (subs[0]->ws.dsat + subs[1]->ws.sat), subs[0]->ws.dsat + subs[1]->ws.dsat}; case Fragment::OR_D: return {subs[0].ws.sat | (subs[0].ws.dsat + subs[1].ws.sat), subs[0].ws.dsat + subs[1].ws.dsat};
case Fragment::OR_I: return {(subs[0]->ws.sat + 1 + 1) | (subs[1]->ws.sat + 1), (subs[0]->ws.dsat + 1 + 1) | (subs[1]->ws.dsat + 1)}; case Fragment::OR_I: return {(subs[0].ws.sat + 1 + 1) | (subs[1].ws.sat + 1), (subs[0].ws.dsat + 1 + 1) | (subs[1].ws.dsat + 1)};
case Fragment::MULTI: return {k * sig_size + 1, k + 1}; case Fragment::MULTI: return {k * sig_size + 1, k + 1};
case Fragment::MULTI_A: return {k * sig_size + static_cast<uint32_t>(keys.size()) - k, static_cast<uint32_t>(keys.size())}; case Fragment::MULTI_A: return {k * sig_size + static_cast<uint32_t>(keys.size()) - k, static_cast<uint32_t>(keys.size())};
case Fragment::WRAP_A: case Fragment::WRAP_A:
case Fragment::WRAP_N: case Fragment::WRAP_N:
case Fragment::WRAP_S: case Fragment::WRAP_S:
case Fragment::WRAP_C: return subs[0]->ws; case Fragment::WRAP_C: return subs[0].ws;
case Fragment::WRAP_D: return {1 + 1 + subs[0]->ws.sat, 1}; case Fragment::WRAP_D: return {1 + 1 + subs[0].ws.sat, 1};
case Fragment::WRAP_V: return {subs[0]->ws.sat, {}}; case Fragment::WRAP_V: return {subs[0].ws.sat, {}};
case Fragment::WRAP_J: return {subs[0]->ws.sat, 1}; case Fragment::WRAP_J: return {subs[0].ws.sat, 1};
case Fragment::THRESH: { case Fragment::THRESH: {
auto sats = Vector(internal::MaxInt<uint32_t>(0)); auto sats = Vector(internal::MaxInt<uint32_t>(0));
for (const auto& sub : subs) { for (const auto& sub : subs) {
auto next_sats = Vector(sats[0] + sub->ws.dsat); auto next_sats = Vector(sats[0] + sub.ws.dsat);
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ws.dsat) | (sats[j - 1] + sub->ws.sat)); for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub.ws.dsat) | (sats[j - 1] + sub.ws.sat));
next_sats.push_back(sats[sats.size() - 1] + sub->ws.sat); next_sats.push_back(sats[sats.size() - 1] + sub.ws.sat);
sats = std::move(next_sats); sats = std::move(next_sats);
} }
assert(k <= sats.size()); assert(k <= sats.size());
@ -1656,29 +1652,29 @@ public:
bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; } bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; }
// Constructors with various argument combinations, which bypass the duplicate key check. // Constructors with various argument combinations, which bypass the duplicate key check.
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Node<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0)
: fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0)
: fragment(nt), k(val), data(std::move(arg)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), data(std::move(arg)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Node<Key>> sub, std::vector<Key> key, uint32_t val = 0)
: fragment(nt), k(val), keys(std::move(key)), m_script_ctx{script_ctx}, subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), keys(std::move(key)), m_script_ctx{script_ctx}, subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0)
: fragment(nt), k(val), keys(std::move(key)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), keys(std::move(key)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Node<Key>> sub, uint32_t val = 0)
: fragment(nt), k(val), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, uint32_t val = 0) Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, uint32_t val = 0)
: fragment(nt), k(val), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} : fragment(nt), k(val), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
// Constructors with various argument combinations, which do perform the duplicate key check. // Constructors with various argument combinations, which do perform the duplicate key check.
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Node<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0)
: Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); } : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); }
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0)
: Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(arg), val) { DuplicateKeyCheck(ctx);} : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(arg), val) { DuplicateKeyCheck(ctx);}
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Node<Key>> sub, std::vector<Key> key, uint32_t val = 0)
: Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); } : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); }
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0)
: Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(key), val) { DuplicateKeyCheck(ctx); } : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(key), val) { DuplicateKeyCheck(ctx); }
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Node<Key>> sub, uint32_t val = 0)
: Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), val) { DuplicateKeyCheck(ctx); } : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), val) { DuplicateKeyCheck(ctx); }
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0)
: Node(internal::NoDupCheck{}, ctx.MsContext(), nt, val) { DuplicateKeyCheck(ctx); } : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, val) { DuplicateKeyCheck(ctx); }
@ -1686,6 +1682,10 @@ public:
// Delete copy constructor and assignment operator, use Clone() instead // Delete copy constructor and assignment operator, use Clone() instead
Node(const Node&) = delete; Node(const Node&) = delete;
Node& operator=(const Node&) = delete; Node& operator=(const Node&) = delete;
// subs is movable, circumventing recursion, so these are permitted.
Node(Node&&) = default;
Node& operator=(Node&&) = default;
}; };
namespace internal { namespace internal {
@ -1773,14 +1773,14 @@ std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<co
/** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */ /** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */
template<typename Key> template<typename Key>
void BuildBack(const MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) void BuildBack(const MiniscriptContext script_ctx, Fragment nt, std::vector<Node<Key>>& constructed, const bool reverse = false)
{ {
NodeRef<Key> child = std::move(constructed.back()); Node<Key> child{std::move(constructed.back())};
constructed.pop_back(); constructed.pop_back();
if (reverse) { if (reverse) {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, script_ctx, nt, Vector(std::move(child), std::move(constructed.back()))); constructed.back() = Node<Key>{internal::NoDupCheck{}, script_ctx, nt, Vector(std::move(child), std::move(constructed.back()))};
} else { } else {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, script_ctx, nt, Vector(std::move(constructed.back()), std::move(child))); constructed.back() = Node<Key>{internal::NoDupCheck{}, script_ctx, nt, Vector(std::move(constructed.back()), std::move(child))};
} }
} }
@ -1790,7 +1790,7 @@ void BuildBack(const MiniscriptContext script_ctx, Fragment nt, std::vector<Node
* the `IsValidTopLevel()` and `IsSaneTopLevel()` to check for these properties on the node. * the `IsValidTopLevel()` and `IsSaneTopLevel()` to check for these properties on the node.
*/ */
template<typename Key, typename Ctx> template<typename Key, typename Ctx>
inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) inline std::optional<Node<Key>> Parse(Span<const char> in, const Ctx& ctx)
{ {
using namespace script; using namespace script;
@ -1809,7 +1809,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
// The two integers are used to hold state for thresh() // The two integers are used to hold state for thresh()
std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse; std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse;
std::vector<NodeRef<Key>> constructed; std::vector<Node<Key>> constructed;
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
@ -1841,10 +1841,10 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
if (is_multi_a) { if (is_multi_a) {
// (push + xonly-key + CHECKSIG[ADD]) * n + k + OP_NUMEQUAL(VERIFY), minus one. // (push + xonly-key + CHECKSIG[ADD]) * n + k + OP_NUMEQUAL(VERIFY), minus one.
script_size += (1 + 32 + 1) * keys.size() + BuildScript(k).size(); script_size += (1 + 32 + 1) * keys.size() + BuildScript(k).size();
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI_A, std::move(keys), k)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI_A, std::move(keys), k);
} else { } else {
script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size(); script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size();
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI, std::move(keys), k)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI, std::move(keys), k);
} }
return true; return true;
}; };
@ -1902,7 +1902,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
} else if (in[j] == 'l') { } else if (in[j] == 'l') {
// The l: wrapper is equivalent to or_i(0,X) // The l: wrapper is equivalent to or_i(0,X)
script_size += 4; script_size += 4;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0);
to_parse.emplace_back(ParseContext::OR_I, -1, -1); to_parse.emplace_back(ParseContext::OR_I, -1, -1);
} else { } else {
return {}; return {};
@ -1915,63 +1915,63 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
} }
case ParseContext::EXPR: { case ParseContext::EXPR: {
if (Const("0", in)) { if (Const("0", in)) {
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0);
} else if (Const("1", in)) { } else if (Const("1", in)) {
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1);
} else if (Const("pk(", in)) { } else if (Const("pk(", in)) {
auto res = ParseKeyEnd<Key, Ctx>(in, ctx); auto res = ParseKeyEnd<Key, Ctx>(in, ctx);
if (!res) return {}; if (!res) return {};
auto& [key, key_size] = *res; auto& [key, key_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(key)))))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(Node<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(key)))));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
script_size += IsTapscript(ctx.MsContext()) ? 33 : 34; script_size += IsTapscript(ctx.MsContext()) ? 33 : 34;
} else if (Const("pkh(", in)) { } else if (Const("pkh(", in)) {
auto res = ParseKeyEnd<Key>(in, ctx); auto res = ParseKeyEnd<Key>(in, ctx);
if (!res) return {}; if (!res) return {};
auto& [key, key_size] = *res; auto& [key, key_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(key)))))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(Node<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(key)))));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
script_size += 24; script_size += 24;
} else if (Const("pk_k(", in)) { } else if (Const("pk_k(", in)) {
auto res = ParseKeyEnd<Key>(in, ctx); auto res = ParseKeyEnd<Key>(in, ctx);
if (!res) return {}; if (!res) return {};
auto& [key, key_size] = *res; auto& [key, key_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(key)))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(key)));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
script_size += IsTapscript(ctx.MsContext()) ? 32 : 33; script_size += IsTapscript(ctx.MsContext()) ? 32 : 33;
} else if (Const("pk_h(", in)) { } else if (Const("pk_h(", in)) {
auto res = ParseKeyEnd<Key>(in, ctx); auto res = ParseKeyEnd<Key>(in, ctx);
if (!res) return {}; if (!res) return {};
auto& [key, key_size] = *res; auto& [key, key_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(key)))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(key)));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
script_size += 23; script_size += 23;
} else if (Const("sha256(", in)) { } else if (Const("sha256(", in)) {
auto res = ParseHexStrEnd(in, 32, ctx); auto res = ParseHexStrEnd(in, 32, ctx);
if (!res) return {}; if (!res) return {};
auto& [hash, hash_size] = *res; auto& [hash, hash_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::SHA256, std::move(hash))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::SHA256, std::move(hash));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
script_size += 38; script_size += 38;
} else if (Const("ripemd160(", in)) { } else if (Const("ripemd160(", in)) {
auto res = ParseHexStrEnd(in, 20, ctx); auto res = ParseHexStrEnd(in, 20, ctx);
if (!res) return {}; if (!res) return {};
auto& [hash, hash_size] = *res; auto& [hash, hash_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::RIPEMD160, std::move(hash))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::RIPEMD160, std::move(hash));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
script_size += 26; script_size += 26;
} else if (Const("hash256(", in)) { } else if (Const("hash256(", in)) {
auto res = ParseHexStrEnd(in, 32, ctx); auto res = ParseHexStrEnd(in, 32, ctx);
if (!res) return {}; if (!res) return {};
auto& [hash, hash_size] = *res; auto& [hash, hash_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH256, std::move(hash))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH256, std::move(hash));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
script_size += 38; script_size += 38;
} else if (Const("hash160(", in)) { } else if (Const("hash160(", in)) {
auto res = ParseHexStrEnd(in, 20, ctx); auto res = ParseHexStrEnd(in, 20, ctx);
if (!res) return {}; if (!res) return {};
auto& [hash, hash_size] = *res; auto& [hash, hash_size] = *res;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH160, std::move(hash))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH160, std::move(hash));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
script_size += 26; script_size += 26;
} else if (Const("after(", in)) { } else if (Const("after(", in)) {
@ -1979,7 +1979,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
if (arg_size < 1) return {}; if (arg_size < 1) return {};
const auto num{ToIntegral<int64_t>(std::string_view(in.data(), arg_size))}; const auto num{ToIntegral<int64_t>(std::string_view(in.data(), arg_size))};
if (!num.has_value() || *num < 1 || *num >= 0x80000000L) return {}; if (!num.has_value() || *num < 1 || *num >= 0x80000000L) return {};
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AFTER, *num)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AFTER, *num);
in = in.subspan(arg_size + 1); in = in.subspan(arg_size + 1);
script_size += 1 + (*num > 16) + (*num > 0x7f) + (*num > 0x7fff) + (*num > 0x7fffff); script_size += 1 + (*num > 16) + (*num > 0x7f) + (*num > 0x7fff) + (*num > 0x7fffff);
} else if (Const("older(", in)) { } else if (Const("older(", in)) {
@ -1987,7 +1987,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
if (arg_size < 1) return {}; if (arg_size < 1) return {};
const auto num{ToIntegral<int64_t>(std::string_view(in.data(), arg_size))}; const auto num{ToIntegral<int64_t>(std::string_view(in.data(), arg_size))};
if (!num.has_value() || *num < 1 || *num >= 0x80000000L) return {}; if (!num.has_value() || *num < 1 || *num >= 0x80000000L) return {};
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OLDER, *num)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OLDER, *num);
in = in.subspan(arg_size + 1); in = in.subspan(arg_size + 1);
script_size += 1 + (*num > 16) + (*num > 0x7f) + (*num > 0x7fff) + (*num > 0x7fffff); script_size += 1 + (*num > 16) + (*num > 0x7f) + (*num > 0x7fff) + (*num > 0x7fffff);
} else if (Const("multi(", in)) { } else if (Const("multi(", in)) {
@ -2046,40 +2046,40 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
break; break;
} }
case ParseContext::ALT: { case ParseContext::ALT: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_A, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_A, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::SWAP: { case ParseContext::SWAP: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_S, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_S, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::CHECK: { case ParseContext::CHECK: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::DUP_IF: { case ParseContext::DUP_IF: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_D, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_D, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::NON_ZERO: { case ParseContext::NON_ZERO: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_J, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_J, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::ZERO_NOTEQUAL: { case ParseContext::ZERO_NOTEQUAL: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_N, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_N, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::VERIFY: { case ParseContext::VERIFY: {
script_size += (constructed.back()->GetType() << "x"_mst); script_size += (constructed.back().GetType() << "x"_mst);
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_V, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_V, Vector(std::move(constructed.back()))};
break; break;
} }
case ParseContext::WRAP_U: { case ParseContext::WRAP_U: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::OR_I, Vector(std::move(constructed.back()), Node<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0))};
break; break;
} }
case ParseContext::WRAP_T: { case ParseContext::WRAP_T: {
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::AND_V, Vector(std::move(constructed.back()), Node<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1))};
break; break;
} }
case ParseContext::AND_B: { case ParseContext::AND_B: {
@ -2089,7 +2089,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
case ParseContext::AND_N: { case ParseContext::AND_N: {
auto mid = std::move(constructed.back()); auto mid = std::move(constructed.back());
constructed.pop_back(); constructed.pop_back();
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), Node<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0))};
break; break;
} }
case ParseContext::AND_V: { case ParseContext::AND_V: {
@ -2117,7 +2117,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
constructed.pop_back(); constructed.pop_back();
auto mid = std::move(constructed.back()); auto mid = std::move(constructed.back());
constructed.pop_back(); constructed.pop_back();
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))};
break; break;
} }
case ParseContext::THRESH: { case ParseContext::THRESH: {
@ -2131,13 +2131,13 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
if (k > n) return {}; if (k > n) return {};
in = in.subspan(1); in = in.subspan(1);
// Children are constructed in reverse order, so iterate from end to beginning // Children are constructed in reverse order, so iterate from end to beginning
std::vector<NodeRef<Key>> subs; std::vector<Node<Key>> subs;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
subs.push_back(std::move(constructed.back())); subs.push_back(std::move(constructed.back()));
constructed.pop_back(); constructed.pop_back();
} }
std::reverse(subs.begin(), subs.end()); std::reverse(subs.begin(), subs.end());
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::THRESH, std::move(subs), k)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::THRESH, std::move(subs), k);
} else { } else {
return {}; return {};
} }
@ -2158,10 +2158,10 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
// Sanity checks on the produced miniscript // Sanity checks on the produced miniscript
assert(constructed.size() == 1); assert(constructed.size() == 1);
assert(constructed[0]->ScriptSize() == script_size); assert(constructed[0].ScriptSize() == script_size);
if (in.size() > 0) return {}; if (in.size() > 0) return {};
NodeRef<Key> tl_node = std::move(constructed.front()); Node<Key> tl_node{std::move(constructed.front())};
tl_node->DuplicateKeyCheck(ctx); tl_node.DuplicateKeyCheck(ctx);
return tl_node; return tl_node;
} }
@ -2248,11 +2248,11 @@ enum class DecodeContext {
//! Parse a miniscript from a bitcoin script //! Parse a miniscript from a bitcoin script
template<typename Key, typename Ctx, typename I> template<typename Key, typename Ctx, typename I>
inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) inline std::optional<Node<Key>> DecodeScript(I& in, I last, const Ctx& ctx)
{ {
// The two integers are used to hold state for thresh() // The two integers are used to hold state for thresh()
std::vector<std::tuple<DecodeContext, int64_t, int64_t>> to_parse; std::vector<std::tuple<DecodeContext, int64_t, int64_t>> to_parse;
std::vector<NodeRef<Key>> constructed; std::vector<Node<Key>> constructed;
// This is the top level, so we assume the type is B // This is the top level, so we assume the type is B
// (in particular, disallowing top level W expressions) // (in particular, disallowing top level W expressions)
@ -2260,7 +2260,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
while (!to_parse.empty()) { while (!to_parse.empty()) {
// Exit early if the Miniscript is not going to be valid. // Exit early if the Miniscript is not going to be valid.
if (!constructed.empty() && !constructed.back()->IsValid()) return {}; if (!constructed.empty() && !constructed.back().IsValid()) return {};
// Get the current context we are decoding within // Get the current context we are decoding within
auto [cur_context, n, k] = to_parse.back(); auto [cur_context, n, k] = to_parse.back();
@ -2273,12 +2273,12 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
// Constants // Constants
if (in[0].first == OP_1) { if (in[0].first == OP_1) {
++in; ++in;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1);
break; break;
} }
if (in[0].first == OP_0) { if (in[0].first == OP_0) {
++in; ++in;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0);
break; break;
} }
// Public keys // Public keys
@ -2286,14 +2286,14 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end()); auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end());
if (!key) return {}; if (!key) return {};
++in; ++in;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(*key)))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(*key)));
break; break;
} }
if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) { if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) {
auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end()); auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end());
if (!key) return {}; if (!key) return {};
in += 5; in += 5;
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(*key)))); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(*key)));
break; break;
} }
// Time locks // Time locks
@ -2301,31 +2301,31 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) { if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) {
in += 2; in += 2;
if (*num < 1 || *num > 0x7FFFFFFFL) return {}; if (*num < 1 || *num > 0x7FFFFFFFL) return {};
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OLDER, *num)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OLDER, *num);
break; break;
} }
if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) { if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) {
in += 2; in += 2;
if (num < 1 || num > 0x7FFFFFFFL) return {}; if (num < 1 || num > 0x7FFFFFFFL) return {};
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AFTER, *num)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AFTER, *num);
break; break;
} }
// Hashes // Hashes
if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) { if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) {
if (in[2].first == OP_SHA256 && in[1].second.size() == 32) { if (in[2].first == OP_SHA256 && in[1].second.size() == 32) {
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::SHA256, in[1].second)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::SHA256, in[1].second);
in += 7; in += 7;
break; break;
} else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) { } else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) {
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::RIPEMD160, in[1].second)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::RIPEMD160, in[1].second);
in += 7; in += 7;
break; break;
} else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) { } else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) {
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH256, in[1].second)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH256, in[1].second);
in += 7; in += 7;
break; break;
} else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) { } else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) {
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH160, in[1].second)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH160, in[1].second);
in += 7; in += 7;
break; break;
} }
@ -2347,7 +2347,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (!k || *k < 1 || *k > *n) return {}; if (!k || *k < 1 || *k > *n) return {};
in += 3 + *n; in += 3 + *n;
std::reverse(keys.begin(), keys.end()); std::reverse(keys.begin(), keys.end());
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI, std::move(keys), *k)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI, std::move(keys), *k);
break; break;
} }
// Tapscript's equivalent of multi // Tapscript's equivalent of multi
@ -2377,7 +2377,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (keys.size() < (size_t)*k) return {}; if (keys.size() < (size_t)*k) return {};
in += 2 + keys.size() * 2; in += 2 + keys.size() * 2;
std::reverse(keys.begin(), keys.end()); std::reverse(keys.begin(), keys.end());
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI_A, std::move(keys), *k)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI_A, std::move(keys), *k);
break; break;
} }
/** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather
@ -2472,38 +2472,38 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
case DecodeContext::SWAP: { case DecodeContext::SWAP: {
if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {}; if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {};
++in; ++in;
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_S, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_S, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::ALT: { case DecodeContext::ALT: {
if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {}; if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {};
++in; ++in;
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_A, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_A, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::CHECK: { case DecodeContext::CHECK: {
if (constructed.empty()) return {}; if (constructed.empty()) return {};
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::DUP_IF: { case DecodeContext::DUP_IF: {
if (constructed.empty()) return {}; if (constructed.empty()) return {};
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_D, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_D, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::VERIFY: { case DecodeContext::VERIFY: {
if (constructed.empty()) return {}; if (constructed.empty()) return {};
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_V, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_V, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::NON_ZERO: { case DecodeContext::NON_ZERO: {
if (constructed.empty()) return {}; if (constructed.empty()) return {};
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_J, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_J, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::ZERO_NOTEQUAL: { case DecodeContext::ZERO_NOTEQUAL: {
if (constructed.empty()) return {}; if (constructed.empty()) return {};
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_N, Vector(std::move(constructed.back()))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_N, Vector(std::move(constructed.back()))};
break; break;
} }
case DecodeContext::AND_V: { case DecodeContext::AND_V: {
@ -2533,12 +2533,12 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
} }
case DecodeContext::ANDOR: { case DecodeContext::ANDOR: {
if (constructed.size() < 3) return {}; if (constructed.size() < 3) return {};
NodeRef<Key> left = std::move(constructed.back()); Node left{std::move(constructed.back())};
constructed.pop_back(); constructed.pop_back();
NodeRef<Key> right = std::move(constructed.back()); Node right{std::move(constructed.back())};
constructed.pop_back(); constructed.pop_back();
NodeRef<Key> mid = std::move(constructed.back()); Node mid{std::move(constructed.back())};
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); constructed.back() = Node{internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))};
break; break;
} }
case DecodeContext::THRESH_W: { case DecodeContext::THRESH_W: {
@ -2556,13 +2556,13 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
} }
case DecodeContext::THRESH_E: { case DecodeContext::THRESH_E: {
if (k < 1 || k > n || constructed.size() < static_cast<size_t>(n)) return {}; if (k < 1 || k > n || constructed.size() < static_cast<size_t>(n)) return {};
std::vector<NodeRef<Key>> subs; std::vector<Node<Key>> subs;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
NodeRef<Key> sub = std::move(constructed.back()); Node sub{std::move(constructed.back())};
constructed.pop_back(); constructed.pop_back();
subs.push_back(std::move(sub)); subs.push_back(std::move(sub));
} }
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::THRESH, std::move(subs), k)); constructed.emplace_back(internal::NoDupCheck{}, ctx.MsContext(), Fragment::THRESH, std::move(subs), k);
break; break;
} }
case DecodeContext::ENDIF: { case DecodeContext::ENDIF: {
@ -2626,23 +2626,25 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
} }
} }
if (constructed.size() != 1) return {}; if (constructed.size() != 1) return {};
NodeRef<Key> tl_node = std::move(constructed.front()); Node tl_node{std::move(constructed.front())};
tl_node->DuplicateKeyCheck(ctx); tl_node.DuplicateKeyCheck(ctx);
// Note that due to how ComputeType works (only assign the type to the node if the // Note that due to how ComputeType works (only assign the type to the node if the
// subs' types are valid) this would fail if any node of tree is badly typed. // subs' types are valid) this would fail if any node of tree is badly typed.
if (!tl_node->IsValidTopLevel()) return {}; if (!tl_node.IsValidTopLevel()) return {};
return tl_node; return tl_node;
} }
} // namespace internal } // namespace internal
template<typename Ctx> template<typename Ctx>
inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx& ctx) { inline std::optional<Node<typename Ctx::Key>> FromString(const std::string& str, const Ctx& ctx)
{
return internal::Parse<typename Ctx::Key>(str, ctx); return internal::Parse<typename Ctx::Key>(str, ctx);
} }
template<typename Ctx> template<typename Ctx>
inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { inline std::optional<Node<typename Ctx::Key>> FromScript(const CScript& script, const Ctx& ctx)
{
using namespace internal; using namespace internal;
// A too large Script is necessarily invalid, don't bother parsing it. // A too large Script is necessarily invalid, don't bother parsing it.
if (script.size() > MaxScriptSize(ctx.MsContext())) return {}; if (script.size() > MaxScriptSize(ctx.MsContext())) return {};

View File

@ -14,11 +14,11 @@
#include <util/strencodings.h> #include <util/strencodings.h>
#include <algorithm> #include <algorithm>
#include <optional>
namespace { namespace {
using Fragment = miniscript::Fragment; using Fragment = miniscript::Fragment;
using NodeRef = miniscript::NodeRef<CPubKey>;
using Node = miniscript::Node<CPubKey>; using Node = miniscript::Node<CPubKey>;
using Type = miniscript::Type; using Type = miniscript::Type;
using MsCtx = miniscript::MiniscriptContext; using MsCtx = miniscript::MiniscriptContext;
@ -311,11 +311,6 @@ const struct KeyComparator {
// A dummy scriptsig to pass to VerifyScript (we always use Segwit v0). // A dummy scriptsig to pass to VerifyScript (we always use Segwit v0).
const CScript DUMMY_SCRIPTSIG; const CScript DUMMY_SCRIPTSIG;
//! Construct a miniscript node as a shared_ptr.
template<typename... Args> NodeRef MakeNodeRef(Args&&... args) {
return miniscript::MakeNodeRef<CPubKey>(miniscript::internal::NoDupCheck{}, std::forward<Args>(args)...);
}
/** Information about a yet to be constructed Miniscript node. */ /** Information about a yet to be constructed Miniscript node. */
struct NodeInfo { struct NodeInfo {
//! The type of this node //! The type of this node
@ -851,14 +846,14 @@ std::optional<NodeInfo> ConsumeNodeSmart(MsCtx script_ctx, FuzzedDataProvider& p
* Generate a Miniscript node based on the fuzzer's input. * Generate a Miniscript node based on the fuzzer's input.
* *
* - ConsumeNode is a function object taking a Type, and returning an std::optional<NodeInfo>. * - ConsumeNode is a function object taking a Type, and returning an std::optional<NodeInfo>.
* - root_type is the required type properties of the constructed NodeRef. * - root_type is the required type properties of the constructed Node.
* - strict_valid sets whether ConsumeNode is expected to guarantee a NodeInfo that results in * - strict_valid sets whether ConsumeNode is expected to guarantee a NodeInfo that results in
* a NodeRef whose Type() matches the type fed to ConsumeNode. * a Node whose Type() matches the type fed to ConsumeNode.
*/ */
template<typename F> template<typename F>
NodeRef GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_valid = false) { std::optional<Node> GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_valid = false) {
/** A stack of miniscript Nodes being built up. */ /** A stack of miniscript Nodes being built up. */
std::vector<NodeRef> stack; std::vector<Node> stack;
/** The queue of instructions. */ /** The queue of instructions. */
std::vector<std::pair<Type, std::optional<NodeInfo>>> todo{{root_type, {}}}; std::vector<std::pair<Type, std::optional<NodeInfo>>> todo{{root_type, {}}};
/** Predict the number of (static) script ops. */ /** Predict the number of (static) script ops. */
@ -961,24 +956,24 @@ NodeRef GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_val
} else { } else {
// The back of todo has fragment and number of children decided, and // The back of todo has fragment and number of children decided, and
// those children have been constructed at the back of stack. Pop // those children have been constructed at the back of stack. Pop
// that entry off todo, and use it to construct a new NodeRef on // that entry off todo, and use it to construct a new Node on
// stack. // stack.
NodeInfo& info = *todo.back().second; NodeInfo& info = *todo.back().second;
// Gather children from the back of stack. // Gather children from the back of stack.
std::vector<NodeRef> sub; std::vector<Node> sub;
sub.reserve(info.subtypes.size()); sub.reserve(info.subtypes.size());
for (size_t i = 0; i < info.subtypes.size(); ++i) { for (size_t i = 0; i < info.subtypes.size(); ++i) {
sub.push_back(std::move(*(stack.end() - info.subtypes.size() + i))); sub.push_back(std::move(*(stack.end() - info.subtypes.size() + i)));
} }
stack.erase(stack.end() - info.subtypes.size(), stack.end()); stack.erase(stack.end() - info.subtypes.size(), stack.end());
// Construct new NodeRef. // Construct new Node.
NodeRef node; std::optional<Node> node;
if (info.keys.empty()) { if (info.keys.empty()) {
node = MakeNodeRef(script_ctx, info.fragment, std::move(sub), std::move(info.hash), info.k); node = Node{miniscript::internal::NoDupCheck{}, script_ctx, info.fragment, std::move(sub), std::move(info.hash), info.k};
} else { } else {
assert(sub.empty()); assert(sub.empty());
assert(info.hash.empty()); assert(info.hash.empty());
node = MakeNodeRef(script_ctx, info.fragment, std::move(info.keys), info.k); node = Node{miniscript::internal::NoDupCheck{}, script_ctx, info.fragment, std::move(info.keys), info.k};
} }
// Verify acceptability. // Verify acceptability.
if (!node || (node->GetType() & "KVWB"_mst) == ""_mst) { if (!node || (node->GetType() & "KVWB"_mst) == ""_mst) {
@ -990,7 +985,7 @@ NodeRef GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_val
} }
if (!node->IsValid()) return {}; if (!node->IsValid()) return {};
// Update resource predictions. // Update resource predictions.
if (node->fragment == Fragment::WRAP_V && node->subs[0]->GetType() << "x"_mst) { if (node->fragment == Fragment::WRAP_V && node->subs[0].GetType() << "x"_mst) {
ops += 1; ops += 1;
scriptsize += 1; scriptsize += 1;
} }
@ -999,14 +994,14 @@ NodeRef GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_val
return {}; return {};
} }
// Move it to the stack. // Move it to the stack.
stack.push_back(std::move(node)); stack.push_back(std::move(*node));
todo.pop_back(); todo.pop_back();
} }
} }
assert(stack.size() == 1); assert(stack.size() == 1);
assert(stack[0]->GetStaticOps() == ops); assert(stack[0].GetStaticOps() == ops);
assert(stack[0]->ScriptSize() == scriptsize); assert(stack[0].ScriptSize() == scriptsize);
stack[0]->DuplicateKeyCheck(KEY_COMP); stack[0].DuplicateKeyCheck(KEY_COMP);
return std::move(stack[0]); return std::move(stack[0]);
} }
@ -1031,7 +1026,7 @@ void SatisfactionToWitness(MsCtx ctx, CScriptWitness& witness, const CScript& sc
} }
/** Perform various applicable tests on a miniscript Node. */ /** Perform various applicable tests on a miniscript Node. */
void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& provider) void TestNode(const MsCtx script_ctx, const std::optional<Node> node, FuzzedDataProvider& provider)
{ {
if (!node) return; if (!node) return;

View File

@ -292,31 +292,31 @@ public:
}; };
using Fragment = miniscript::Fragment; using Fragment = miniscript::Fragment;
using NodeRef = miniscript::NodeRef<CPubKey>;
using miniscript::operator""_mst; using miniscript::operator""_mst;
using Node = miniscript::Node<CPubKey>; using Node = miniscript::Node<CPubKey>;
/** Compute all challenges (pubkeys, hashes, timelocks) that occur in a given Miniscript. */ /** Compute all challenges (pubkeys, hashes, timelocks) that occur in a given Miniscript. */
// NOLINTNEXTLINE(misc-no-recursion) // NOLINTNEXTLINE(misc-no-recursion)
std::set<Challenge> FindChallenges(const NodeRef& ref) { std::set<Challenge> FindChallenges(const Node& node)
{
std::set<Challenge> chal; std::set<Challenge> chal;
for (const auto& key : ref->keys) { for (const auto& key : node.keys) {
chal.emplace(ChallengeType::PK, ChallengeNumber(key)); chal.emplace(ChallengeType::PK, ChallengeNumber(key));
} }
if (ref->fragment == miniscript::Fragment::OLDER) { if (node.fragment == miniscript::Fragment::OLDER) {
chal.emplace(ChallengeType::OLDER, ref->k); chal.emplace(ChallengeType::OLDER, node.k);
} else if (ref->fragment == miniscript::Fragment::AFTER) { } else if (node.fragment == miniscript::Fragment::AFTER) {
chal.emplace(ChallengeType::AFTER, ref->k); chal.emplace(ChallengeType::AFTER, node.k);
} else if (ref->fragment == miniscript::Fragment::SHA256) { } else if (node.fragment == miniscript::Fragment::SHA256) {
chal.emplace(ChallengeType::SHA256, ChallengeNumber(ref->data)); chal.emplace(ChallengeType::SHA256, ChallengeNumber(node.data));
} else if (ref->fragment == miniscript::Fragment::RIPEMD160) { } else if (node.fragment == miniscript::Fragment::RIPEMD160) {
chal.emplace(ChallengeType::RIPEMD160, ChallengeNumber(ref->data)); chal.emplace(ChallengeType::RIPEMD160, ChallengeNumber(node.data));
} else if (ref->fragment == miniscript::Fragment::HASH256) { } else if (node.fragment == miniscript::Fragment::HASH256) {
chal.emplace(ChallengeType::HASH256, ChallengeNumber(ref->data)); chal.emplace(ChallengeType::HASH256, ChallengeNumber(node.data));
} else if (ref->fragment == miniscript::Fragment::HASH160) { } else if (node.fragment == miniscript::Fragment::HASH160) {
chal.emplace(ChallengeType::HASH160, ChallengeNumber(ref->data)); chal.emplace(ChallengeType::HASH160, ChallengeNumber(node.data));
} }
for (const auto& sub : ref->subs) { for (const auto& sub : node.subs) {
auto sub_chal = FindChallenges(sub); auto sub_chal = FindChallenges(sub);
chal.insert(sub_chal.begin(), sub_chal.end()); chal.insert(sub_chal.begin(), sub_chal.end());
} }
@ -345,8 +345,9 @@ void SatisfactionToWitness(miniscript::MiniscriptContext ctx, CScriptWitness& wi
struct MiniScriptTest : BasicTestingSetup { struct MiniScriptTest : BasicTestingSetup {
/** Run random satisfaction tests. */ /** Run random satisfaction tests. */
void TestSatisfy(const KeyConverter& converter, const std::string& testcase, const NodeRef& node) { void TestSatisfy(const KeyConverter& converter, const std::string& testcase, const Node& node)
auto script = node->ToScript(converter); {
auto script = node.ToScript(converter);
auto challenges = FindChallenges(node); // Find all challenges in the generated miniscript. auto challenges = FindChallenges(node); // Find all challenges in the generated miniscript.
std::vector<Challenge> challist(challenges.begin(), challenges.end()); std::vector<Challenge> challist(challenges.begin(), challenges.end());
for (int iter = 0; iter < 3; ++iter) { for (int iter = 0; iter < 3; ++iter) {
@ -364,12 +365,12 @@ void TestSatisfy(const KeyConverter& converter, const std::string& testcase, con
// Run malleable satisfaction algorithm. // Run malleable satisfaction algorithm.
CScriptWitness witness_mal; CScriptWitness witness_mal;
const bool mal_success = node->Satisfy(satisfier, witness_mal.stack, false) == miniscript::Availability::YES; const bool mal_success = node.Satisfy(satisfier, witness_mal.stack, false) == miniscript::Availability::YES;
SatisfactionToWitness(converter.MsContext(), witness_mal, script, builder); SatisfactionToWitness(converter.MsContext(), witness_mal, script, builder);
// Run non-malleable satisfaction algorithm. // Run non-malleable satisfaction algorithm.
CScriptWitness witness_nonmal; CScriptWitness witness_nonmal;
const bool nonmal_success = node->Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES; const bool nonmal_success = node.Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES;
// Compute witness size (excluding script push, control block, and witness count encoding). // Compute witness size (excluding script push, control block, and witness count encoding).
const size_t wit_size = GetSerializeSize(witness_nonmal.stack) - GetSizeOfCompactSize(witness_nonmal.stack.size()); const size_t wit_size = GetSerializeSize(witness_nonmal.stack) - GetSizeOfCompactSize(witness_nonmal.stack.size());
SatisfactionToWitness(converter.MsContext(), witness_nonmal, script, builder); SatisfactionToWitness(converter.MsContext(), witness_nonmal, script, builder);
@ -378,23 +379,23 @@ void TestSatisfy(const KeyConverter& converter, const std::string& testcase, con
// Non-malleable satisfactions are bounded by the satisfaction size plus: // Non-malleable satisfactions are bounded by the satisfaction size plus:
// - For P2WSH spends, the witness script // - For P2WSH spends, the witness script
// - For Tapscript spends, both the witness script and the control block // - For Tapscript spends, both the witness script and the control block
const size_t max_stack_size{*node->GetStackSize() + 1 + miniscript::IsTapscript(converter.MsContext())}; const size_t max_stack_size{*node.GetStackSize() + 1 + miniscript::IsTapscript(converter.MsContext())};
BOOST_CHECK(witness_nonmal.stack.size() <= max_stack_size); BOOST_CHECK(witness_nonmal.stack.size() <= max_stack_size);
// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it. // If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.
BOOST_CHECK(mal_success); BOOST_CHECK(mal_success);
BOOST_CHECK(witness_nonmal.stack == witness_mal.stack); BOOST_CHECK(witness_nonmal.stack == witness_mal.stack);
assert(wit_size <= *node->GetWitnessSize()); assert(wit_size <= *node.GetWitnessSize());
// Test non-malleable satisfaction. // Test non-malleable satisfaction.
ScriptError serror; ScriptError serror;
bool res = VerifyScript(CScript(), script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, checker, &serror); bool res = VerifyScript(CScript(), script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, checker, &serror);
// Non-malleable satisfactions are guaranteed to be valid if ValidSatisfactions(). // Non-malleable satisfactions are guaranteed to be valid if ValidSatisfactions().
if (node->ValidSatisfactions()) BOOST_CHECK(res); if (node.ValidSatisfactions()) BOOST_CHECK(res);
// More detailed: non-malleable satisfactions must be valid, or could fail with ops count error (if CheckOpsLimit failed), // More detailed: non-malleable satisfactions must be valid, or could fail with ops count error (if CheckOpsLimit failed),
// or with a stack size error (if CheckStackSize check fails). // or with a stack size error (if CheckStackSize check fails).
BOOST_CHECK(res || BOOST_CHECK(res ||
(!node->CheckOpsLimit() && serror == ScriptError::SCRIPT_ERR_OP_COUNT) || (!node.CheckOpsLimit() && serror == ScriptError::SCRIPT_ERR_OP_COUNT) ||
(!node->CheckStackSize() && serror == ScriptError::SCRIPT_ERR_STACK_SIZE)); (!node.CheckStackSize() && serror == ScriptError::SCRIPT_ERR_STACK_SIZE));
} }
if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) { if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) {
@ -406,7 +407,7 @@ void TestSatisfy(const KeyConverter& converter, const std::string& testcase, con
BOOST_CHECK(res || serror == ScriptError::SCRIPT_ERR_OP_COUNT || serror == ScriptError::SCRIPT_ERR_STACK_SIZE); BOOST_CHECK(res || serror == ScriptError::SCRIPT_ERR_OP_COUNT || serror == ScriptError::SCRIPT_ERR_STACK_SIZE);
} }
if (node->IsSane()) { if (node.IsSane()) {
// For sane nodes, the two algorithms behave identically. // For sane nodes, the two algorithms behave identically.
BOOST_CHECK_EQUAL(mal_success, nonmal_success); BOOST_CHECK_EQUAL(mal_success, nonmal_success);
} }
@ -416,7 +417,7 @@ void TestSatisfy(const KeyConverter& converter, const std::string& testcase, con
// For nonmalleable solutions this is only true if the added condition is PK; // For nonmalleable solutions this is only true if the added condition is PK;
// for other conditions, adding one may make an valid satisfaction become malleable. If the script // for other conditions, adding one may make an valid satisfaction become malleable. If the script
// is sane, this cannot happen however. // is sane, this cannot happen however.
if (node->IsSane() || add < 0 || challist[add].first == ChallengeType::PK) { if (node.IsSane() || add < 0 || challist[add].first == ChallengeType::PK) {
BOOST_CHECK(nonmal_success >= prev_nonmal_success); BOOST_CHECK(nonmal_success >= prev_nonmal_success);
} }
// Remember results for the next added challenge. // Remember results for the next added challenge.
@ -424,11 +425,11 @@ void TestSatisfy(const KeyConverter& converter, const std::string& testcase, con
prev_nonmal_success = nonmal_success; prev_nonmal_success = nonmal_success;
} }
bool satisfiable = node->IsSatisfiable([](const Node&) { return true; }); bool satisfiable = node.IsSatisfiable([](const Node&) { return true; });
// If the miniscript was satisfiable at all, a satisfaction must be found after all conditions are added. // If the miniscript was satisfiable at all, a satisfaction must be found after all conditions are added.
BOOST_CHECK_EQUAL(prev_mal_success, satisfiable); BOOST_CHECK_EQUAL(prev_mal_success, satisfiable);
// If the miniscript is sane and satisfiable, a nonmalleable satisfaction must eventually be found. // If the miniscript is sane and satisfiable, a nonmalleable satisfaction must eventually be found.
if (node->IsSane()) BOOST_CHECK_EQUAL(prev_nonmal_success, satisfiable); if (node.IsSane()) BOOST_CHECK_EQUAL(prev_nonmal_success, satisfiable);
} }
} }
@ -471,7 +472,7 @@ void Test(const std::string& ms, const std::string& hexscript, int mode, const K
if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << *node->GetStackSize() << " vs " << stacklimit << ")"); if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << *node->GetStackSize() << " vs " << stacklimit << ")");
if (max_wit_size) BOOST_CHECK_MESSAGE(*node->GetWitnessSize() == *max_wit_size, "Witness size limit mismatch: " << ms << " (" << *node->GetWitnessSize() << " vs " << *max_wit_size << ")"); if (max_wit_size) BOOST_CHECK_MESSAGE(*node->GetWitnessSize() == *max_wit_size, "Witness size limit mismatch: " << ms << " (" << *node->GetWitnessSize() << " vs " << *max_wit_size << ")");
if (stack_exec) BOOST_CHECK_MESSAGE(*node->GetExecStackSize() == *stack_exec, "Stack execution limit mismatch: " << ms << " (" << *node->GetExecStackSize() << " vs " << *stack_exec << ")"); if (stack_exec) BOOST_CHECK_MESSAGE(*node->GetExecStackSize() == *stack_exec, "Stack execution limit mismatch: " << ms << " (" << *node->GetExecStackSize() << " vs " << *stack_exec << ")");
TestSatisfy(converter, ms, node); TestSatisfy(converter, ms, *node);
} }
} }
@ -599,11 +600,11 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
constexpr KeyConverter tap_converter{miniscript::MiniscriptContext::TAPSCRIPT}; constexpr KeyConverter tap_converter{miniscript::MiniscriptContext::TAPSCRIPT};
constexpr KeyConverter wsh_converter{miniscript::MiniscriptContext::P2WSH}; constexpr KeyConverter wsh_converter{miniscript::MiniscriptContext::P2WSH};
const auto no_pubkey{"ac519c"_hex_u8}; const auto no_pubkey{"ac519c"_hex_u8};
BOOST_CHECK(miniscript::FromScript({no_pubkey.begin(), no_pubkey.end()}, tap_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript({no_pubkey.begin(), no_pubkey.end()}, tap_converter));
const auto incomplete_multi_a{"ba20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c"_hex_u8}; const auto incomplete_multi_a{"ba20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c"_hex_u8};
BOOST_CHECK(miniscript::FromScript({incomplete_multi_a.begin(), incomplete_multi_a.end()}, tap_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript({incomplete_multi_a.begin(), incomplete_multi_a.end()}, tap_converter));
const auto incomplete_multi_a_2{"ac2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c"_hex_u8}; const auto incomplete_multi_a_2{"ac2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c"_hex_u8};
BOOST_CHECK(miniscript::FromScript({incomplete_multi_a_2.begin(), incomplete_multi_a_2.end()}, tap_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript({incomplete_multi_a_2.begin(), incomplete_multi_a_2.end()}, tap_converter));
// Can use multi_a under Tapscript but not P2WSH. // Can use multi_a under Tapscript but not P2WSH.
Test("and_v(v:multi_a(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "?", "20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aac205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba529d0400046749b1", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4, 2, {}, {}, 3); Test("and_v(v:multi_a(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "?", "20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aac205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba529d0400046749b1", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4, 2, {}, {}, 3);
// Can use more than 20 keys in a multi_a. // Can use more than 20 keys in a multi_a.
@ -649,13 +650,13 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
// A Script with a non minimal push is invalid // A Script with a non minimal push is invalid
constexpr auto nonminpush{"0000210232780000feff00ffffffffffff21ff005f00ae21ae00000000060602060406564c2102320000060900fe00005f00ae21ae00100000060606060606000000000000000000000000000000000000000000000000000000000000000000"_hex_u8}; constexpr auto nonminpush{"0000210232780000feff00ffffffffffff21ff005f00ae21ae00000000060602060406564c2102320000060900fe00005f00ae21ae00100000060606060606000000000000000000000000000000000000000000000000000000000000000000"_hex_u8};
const CScript nonminpush_script(nonminpush.begin(), nonminpush.end()); const CScript nonminpush_script(nonminpush.begin(), nonminpush.end());
BOOST_CHECK(miniscript::FromScript(nonminpush_script, wsh_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript(nonminpush_script, wsh_converter));
BOOST_CHECK(miniscript::FromScript(nonminpush_script, tap_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript(nonminpush_script, tap_converter));
// A non-minimal VERIFY (<key> CHECKSIG VERIFY 1) // A non-minimal VERIFY (<key> CHECKSIG VERIFY 1)
constexpr auto nonminverify{"2103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7ac6951"_hex_u8}; constexpr auto nonminverify{"2103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7ac6951"_hex_u8};
const CScript nonminverify_script(nonminverify.begin(), nonminverify.end()); const CScript nonminverify_script(nonminverify.begin(), nonminverify.end());
BOOST_CHECK(miniscript::FromScript(nonminverify_script, wsh_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript(nonminverify_script, wsh_converter));
BOOST_CHECK(miniscript::FromScript(nonminverify_script, tap_converter) == nullptr); BOOST_CHECK(!miniscript::FromScript(nonminverify_script, tap_converter));
// A threshold as large as the number of subs is valid. // A threshold as large as the number of subs is valid.
Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
// A threshold of 1 is valid. // A threshold of 1 is valid.