Compare commits

...

4 Commits

Author SHA1 Message Date
hodlinator
f1b9371d2b
Merge 58e9c7ca89654abdae2e37fe848ed8afc1e250e1 into cd8089c20baaaee329cbf7951195953a9f86d7cf 2025-03-16 17:18:25 +01:00
Hodlinator
58e9c7ca89
miniscript refactor: Remove superfluous unique_ptr-indirection
Functional parity is achieved through making Node move-able.

(Also makes Node::subs no longer mutable).
2025-01-23 08:48:57 +01:00
Hodlinator
c7f1829b4b
miniscript refactor: Make fields non-const
Makes a lot of fields in miniscript.h non-const in order to allow move-operations in next commit.
2025-01-23 08:48:51 +01:00
Hodlinator
531aab627c
miniscript doc: Remove mention of shared pointers
Correct destructor implementation comment to no longer refer to shared pointers and also move it into the function body, in symmetry with Clone() right below.
2025-01-23 08:48:42 +01:00
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.