Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions bitcoin/script/miniscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,51 +35,51 @@ Type SanitizeType(Type e) {
return e;
}

Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
// Sanity check on data
if (nodetype == Fragment::SHA256 || nodetype == Fragment::HASH256) {
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
assert(data_size == 32);
} else if (nodetype == Fragment::RIPEMD160 || nodetype == Fragment::HASH160) {
} else if (fragment == Fragment::RIPEMD160 || fragment == Fragment::HASH160) {
assert(data_size == 20);
} else {
assert(data_size == 0);
}
// Sanity check on k
if (nodetype == Fragment::OLDER || nodetype == Fragment::AFTER) {
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
assert(k >= 1 && k < 0x80000000UL);
} else if (nodetype == Fragment::MULTI) {
} else if (fragment == Fragment::MULTI) {
assert(k >= 1 && k <= n_keys);
} else if (nodetype == Fragment::THRESH) {
} else if (fragment == Fragment::THRESH) {
assert(k >= 1 && k <= n_subs);
} else {
assert(k == 0);
}
// Sanity check on subs
if (nodetype == Fragment::AND_V || nodetype == Fragment::AND_B || nodetype == Fragment::OR_B ||
nodetype == Fragment::OR_C || nodetype == Fragment::OR_I || nodetype == Fragment::OR_D) {
if (fragment == Fragment::AND_V || fragment == Fragment::AND_B || fragment == Fragment::OR_B ||
fragment == Fragment::OR_C || fragment == Fragment::OR_I || fragment == Fragment::OR_D) {
assert(n_subs == 2);
} else if (nodetype == Fragment::ANDOR) {
} else if (fragment == Fragment::ANDOR) {
assert(n_subs == 3);
} else if (nodetype == Fragment::WRAP_A || nodetype == Fragment::WRAP_S || nodetype == Fragment::WRAP_C ||
nodetype == Fragment::WRAP_D || nodetype == Fragment::WRAP_V || nodetype == Fragment::WRAP_J ||
nodetype == Fragment::WRAP_N) {
} else if (fragment == Fragment::WRAP_A || fragment == Fragment::WRAP_S || fragment == Fragment::WRAP_C ||
fragment == Fragment::WRAP_D || fragment == Fragment::WRAP_V || fragment == Fragment::WRAP_J ||
fragment == Fragment::WRAP_N) {
assert(n_subs == 1);
} else if (nodetype != Fragment::THRESH) {
} else if (fragment != Fragment::THRESH) {
assert(n_subs == 0);
}
// Sanity check on keys
if (nodetype == Fragment::PK_K || nodetype == Fragment::PK_H) {
if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) {
assert(n_keys == 1);
} else if (nodetype == Fragment::MULTI) {
} else if (fragment == Fragment::MULTI) {
assert(n_keys >= 1 && n_keys <= 20);
} else {
assert(n_keys == 0);
}

// Below is the per-nodetype logic for computing the expression types.
// Below is the per-fragment logic for computing the expression types.
// It heavily relies on Type's << operator (where "X << a_mst" means
// "X has all properties listed in a").
switch (nodetype) {
switch (fragment) {
case Fragment::PK_K: return "Konudemsxk"_mst;
case Fragment::PK_H: return "Knudemsxk"_mst;
case Fragment::OLDER: return
Expand Down Expand Up @@ -249,8 +249,8 @@ Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Ty
return ""_mst;
}

size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
switch (nodetype) {
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
switch (fragment) {
case Fragment::JUST_1:
case Fragment::JUST_0: return 1;
case Fragment::PK_K: return 34;
Expand Down
113 changes: 78 additions & 35 deletions bitcoin/script/miniscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,10 @@ enum class Availability {
namespace internal {

//! Helper function for Node::CalcType.
Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys);
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys);

//! Helper function for Node::CalcScriptLen.
size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys);
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys);

//! A helper sanitizer/checker for the output of CalcType.
Type SanitizeType(Type x);
Expand Down Expand Up @@ -333,7 +333,7 @@ struct StackSize {
template<typename Key>
struct Node {
//! What node type this node is.
const Fragment nodetype;
const Fragment fragment;
//! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M))
const uint32_t k = 0;
//! The keys used by this expression (only for PK_K/PK_H/MULTI)
Expand All @@ -360,7 +360,7 @@ struct Node {
subsize += sub->ScriptSize();
}
Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst;
return internal::ComputeScriptLen(nodetype, sub0type, subsize, k, subs.size(), keys.size());
return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size());
}

/* Apply a recursive algorithm to a Miniscript tree, without actual recursive calls.
Expand All @@ -383,6 +383,8 @@ struct Node {
* computes the result of the node. If std::nullopt is returned by upfn,
* TreeEvalMaybe() immediately returns std::nullopt.
* The return value of TreeEvalMaybe is the result of the root node.
*
* Result type cannot be bool due to the std::vector<bool> specialization.
*/
template<typename Result, typename State, typename DownFn, typename UpFn>
std::optional<Result> TreeEvalMaybe(State root_state, DownFn downfn, UpFn upfn) const
Expand Down Expand Up @@ -483,15 +485,15 @@ struct Node {

// THRESH has a variable number of subexpressions
std::vector<Type> sub_types;
if (nodetype == Fragment::THRESH) {
if (fragment == Fragment::THRESH) {
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.
Type x = subs.size() > 0 ? subs[0]->GetType() : ""_mst;
Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst;
Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst;

return SanitizeType(ComputeType(nodetype, x, y, z, sub_types, k, data.size(), subs.size(), keys.size()));
return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size()));
}

public:
Expand All @@ -503,17 +505,17 @@ struct Node {
// by an OP_VERIFY (which may need to be combined with the last script opcode).
auto downfn = [](bool verify, const Node& node, size_t index) {
// For WRAP_V, the subexpression is certainly followed by OP_VERIFY.
if (node.nodetype == Fragment::WRAP_V) return true;
if (node.fragment == Fragment::WRAP_V) return true;
// The subexpression of WRAP_S, and the last subexpression of AND_V
// inherit the followed-by-OP_VERIFY property from the parent.
if (node.nodetype == Fragment::WRAP_S ||
(node.nodetype == Fragment::AND_V && index == 1)) return verify;
if (node.fragment == Fragment::WRAP_S ||
(node.fragment == Fragment::AND_V && index == 1)) return verify;
return false;
};
// The upward function computes for a node, given its followed-by-OP_VERIFY status
// and the CScripts of its child nodes, the CScript of the node.
auto upfn = [&ctx](bool verify, const Node& node, Span<CScript> subs) -> CScript {
switch (node.nodetype) {
switch (node.fragment) {
case Fragment::PK_K: return BuildScript(ctx.ToPKBytes(node.keys[0]));
case Fragment::PK_H: return BuildScript(OP_DUP, OP_HASH160, ctx.ToPKHBytes(node.keys[0]), OP_EQUALVERIFY);
case Fragment::OLDER: return BuildScript(node.k, OP_CHECKSEQUENCEVERIFY);
Expand Down Expand Up @@ -571,30 +573,30 @@ struct Node {
// the TreeEvalMaybe algorithm. The State is a boolean: whether the parent node is a
// wrapper. If so, non-wrapper expressions must be prefixed with a ":".
auto downfn = [](bool, const Node& node, size_t) {
return (node.nodetype == Fragment::WRAP_A || node.nodetype == Fragment::WRAP_S ||
node.nodetype == Fragment::WRAP_D || node.nodetype == Fragment::WRAP_V ||
node.nodetype == Fragment::WRAP_J || node.nodetype == Fragment::WRAP_N ||
node.nodetype == Fragment::WRAP_C ||
(node.nodetype == Fragment::AND_V && node.subs[1]->nodetype == Fragment::JUST_1) ||
(node.nodetype == Fragment::OR_I && node.subs[0]->nodetype == Fragment::JUST_0) ||
(node.nodetype == Fragment::OR_I && node.subs[1]->nodetype == Fragment::JUST_0));
return (node.fragment == Fragment::WRAP_A || node.fragment == Fragment::WRAP_S ||
node.fragment == Fragment::WRAP_D || node.fragment == Fragment::WRAP_V ||
node.fragment == Fragment::WRAP_J || node.fragment == Fragment::WRAP_N ||
node.fragment == Fragment::WRAP_C ||
(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[1]->fragment == Fragment::JUST_0));
};
// 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.
auto upfn = [&ctx](bool wrapped, const Node& node, Span<std::string> subs) -> std::optional<std::string> {
std::string ret = wrapped ? ":" : "";

switch (node.nodetype) {
switch (node.fragment) {
case Fragment::WRAP_A: return "a" + std::move(subs[0]);
case Fragment::WRAP_S: return "s" + std::move(subs[0]);
case Fragment::WRAP_C:
if (node.subs[0]->nodetype == Fragment::PK_K) {
if (node.subs[0]->fragment == Fragment::PK_K) {
// pk(K) is syntactic sugar for c:pk_k(K)
std::string key_str;
if (!ctx.ToString(node.subs[0]->keys[0], key_str)) return {};
return std::move(ret) + "pk(" + std::move(key_str) + ")";
}
if (node.subs[0]->nodetype == Fragment::PK_H) {
if (node.subs[0]->fragment == Fragment::PK_H) {
// pkh(K) is syntactic sugar for c:pk_h(K)
std::string key_str;
if (!ctx.ToString(node.subs[0]->keys[0], key_str)) return {};
Expand All @@ -607,15 +609,15 @@ struct Node {
case Fragment::WRAP_N: return "n" + std::move(subs[0]);
case Fragment::AND_V:
// t:X is syntactic sugar for and_v(X,1).
if (node.subs[1]->nodetype == Fragment::JUST_1) return "t" + std::move(subs[0]);
if (node.subs[1]->fragment == Fragment::JUST_1) return "t" + std::move(subs[0]);
break;
case Fragment::OR_I:
if (node.subs[0]->nodetype == Fragment::JUST_0) return "l" + std::move(subs[1]);
if (node.subs[1]->nodetype == Fragment::JUST_0) return "u" + std::move(subs[0]);
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]);
break;
default: break;
}
switch (node.nodetype) {
switch (node.fragment) {
case Fragment::PK_K: {
std::string key_str;
if (!ctx.ToString(node.keys[0], key_str)) return {};
Expand All @@ -642,7 +644,7 @@ struct Node {
case Fragment::OR_I: return std::move(ret) + "or_i(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
case Fragment::ANDOR:
// and_n(X,Y) is syntactic sugar for andor(X,Y,0).
if (node.subs[2]->nodetype == 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]) + ")";
case Fragment::MULTI: {
auto str = std::move(ret) + "multi(" + ::ToString(node.k);
Expand Down Expand Up @@ -671,7 +673,7 @@ struct Node {
}

internal::Ops CalcOps() const {
switch (nodetype) {
switch (fragment) {
case Fragment::JUST_1: return {0, 0, {}};
case Fragment::JUST_0: return {0, {}, 0};
case Fragment::PK_K: return {0, 0, 0};
Expand Down Expand Up @@ -745,7 +747,7 @@ struct Node {
}

internal::StackSize CalcStackSize() const {
switch (nodetype) {
switch (fragment) {
case Fragment::JUST_0: return {{}, 0};
case Fragment::JUST_1:
case Fragment::OLDER:
Expand Down Expand Up @@ -801,7 +803,7 @@ struct Node {
using namespace internal;

auto helper = [&ctx](const Node& node, Span<InputResult> subres) -> InputResult {
switch (node.nodetype) {
switch (node.fragment) {
case Fragment::PK_K: {
std::vector<unsigned char> sig;
Availability avail = ctx.Sign(node.keys[0], sig);
Expand Down Expand Up @@ -1008,6 +1010,47 @@ struct Node {
});
}

//! Determine whether a Miniscript node is satisfiable. fn(node) will be invoked for all
//! key, time, and hashing nodes, and should return their satisfiability.
template<typename F>
bool IsSatisfiable(F fn) const
{
// TreeEval() doesn't support bool as NodeType, so use int instead.
return TreeEval<int>([&fn](const Node& node, Span<int> subs) {
switch (node.fragment) {
case Fragment::JUST_0:
return false;
case Fragment::JUST_1:
return true;
case Fragment::PK_K:
case Fragment::PK_H:
case Fragment::MULTI:
case Fragment::AFTER:
case Fragment::OLDER:
case Fragment::HASH256:
case Fragment::HASH160:
case Fragment::SHA256:
case Fragment::RIPEMD160:
return bool{fn(node)};
case Fragment::ANDOR:
return (subs[0] && subs[1]) || subs[2];
case Fragment::AND_V:
case Fragment::AND_B:
return subs[0] && subs[1];
case Fragment::OR_B:
case Fragment::OR_C:
case Fragment::OR_D:
case Fragment::OR_I:
return subs[0] || subs[1];
case Fragment::THRESH:
return std::count(subs.begin(), subs.end(), true) >= node.k;
default: // wrappers
assert(subs.size() == 1);
return !!subs[0];
}
});
}

//! Check whether this node is valid at all.
bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; }

Expand Down Expand Up @@ -1047,7 +1090,7 @@ struct Node {
//! Equality testing.
bool operator==(const Node<Key>& arg) const
{
if (nodetype != arg.nodetype) return false;
if (fragment != arg.fragment) return false;
if (k != arg.k) return false;
if (data != arg.data) return false;
if (keys != arg.keys) return false;
Expand All @@ -1061,12 +1104,12 @@ struct Node {
}

// Constructors with various argument combinations.
Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : nodetype(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, uint32_t val = 0) : nodetype(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
};

namespace internal {
Expand Down
Loading