mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
Merge 695ca641a4 into merged_master (Bitcoin PR bitcoin/bitcoin#24860)
This commit is contained in:
commit
a52fff5abe
6 changed files with 481 additions and 306 deletions
|
|
@ -277,7 +277,7 @@ test_fuzz_fuzz_SOURCES = \
|
|||
test/fuzz/locale.cpp \
|
||||
test/fuzz/merkleblock.cpp \
|
||||
test/fuzz/message.cpp \
|
||||
test/fuzz/miniscript_decode.cpp \
|
||||
test/fuzz/miniscript.cpp \
|
||||
test/fuzz/minisketch.cpp \
|
||||
test/fuzz/muhash.cpp \
|
||||
test/fuzz/multiplication_overflow.cpp \
|
||||
|
|
|
|||
|
|
@ -17,69 +17,67 @@ Type SanitizeType(Type e) {
|
|||
int num_types = (e << "K"_mst) + (e << "V"_mst) + (e << "B"_mst) + (e << "W"_mst);
|
||||
if (num_types == 0) return ""_mst; // No valid type, don't care about the rest
|
||||
assert(num_types == 1); // K, V, B, W all conflict with each other
|
||||
bool ok = // Work around a GCC 4.8 bug that breaks user-defined literals in macro calls.
|
||||
(!(e << "z"_mst) || !(e << "o"_mst)) && // z conflicts with o
|
||||
(!(e << "n"_mst) || !(e << "z"_mst)) && // n conflicts with z
|
||||
(!(e << "n"_mst) || !(e << "W"_mst)) && // n conflicts with W
|
||||
(!(e << "V"_mst) || !(e << "d"_mst)) && // V conflicts with d
|
||||
(!(e << "K"_mst) || (e << "u"_mst)) && // K implies u
|
||||
(!(e << "V"_mst) || !(e << "u"_mst)) && // V conflicts with u
|
||||
(!(e << "e"_mst) || !(e << "f"_mst)) && // e conflicts with f
|
||||
(!(e << "e"_mst) || (e << "d"_mst)) && // e implies d
|
||||
(!(e << "V"_mst) || !(e << "e"_mst)) && // V conflicts with e
|
||||
(!(e << "d"_mst) || !(e << "f"_mst)) && // d conflicts with f
|
||||
(!(e << "V"_mst) || (e << "f"_mst)) && // V implies f
|
||||
(!(e << "K"_mst) || (e << "s"_mst)) && // K implies s
|
||||
(!(e << "z"_mst) || (e << "m"_mst)); // z implies m
|
||||
assert(ok);
|
||||
assert(!(e << "z"_mst) || !(e << "o"_mst)); // z conflicts with o
|
||||
assert(!(e << "n"_mst) || !(e << "z"_mst)); // n conflicts with z
|
||||
assert(!(e << "n"_mst) || !(e << "W"_mst)); // n conflicts with W
|
||||
assert(!(e << "V"_mst) || !(e << "d"_mst)); // V conflicts with d
|
||||
assert(!(e << "K"_mst) || (e << "u"_mst)); // K implies u
|
||||
assert(!(e << "V"_mst) || !(e << "u"_mst)); // V conflicts with u
|
||||
assert(!(e << "e"_mst) || !(e << "f"_mst)); // e conflicts with f
|
||||
assert(!(e << "e"_mst) || (e << "d"_mst)); // e implies d
|
||||
assert(!(e << "V"_mst) || !(e << "e"_mst)); // V conflicts with e
|
||||
assert(!(e << "d"_mst) || !(e << "f"_mst)); // d conflicts with f
|
||||
assert(!(e << "V"_mst) || (e << "f"_mst)); // V implies f
|
||||
assert(!(e << "K"_mst) || (e << "s"_mst)); // K implies s
|
||||
assert(!(e << "z"_mst) || (e << "m"_mst)); // z implies m
|
||||
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
|
||||
|
|
@ -247,11 +245,10 @@ Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Ty
|
|||
}
|
||||
}
|
||||
assert(false);
|
||||
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;
|
||||
|
|
@ -262,7 +259,7 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_
|
|||
case Fragment::SHA256: return 4 + 2 + 33;
|
||||
case Fragment::HASH160:
|
||||
case Fragment::RIPEMD160: return 4 + 2 + 21;
|
||||
case Fragment::MULTI: return 3 + (n_keys > 16) + (k > 16) + 34 * n_keys;
|
||||
case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys;
|
||||
case Fragment::AND_V: return subsize;
|
||||
case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
|
||||
case Fragment::WRAP_S:
|
||||
|
|
@ -280,19 +277,17 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_
|
|||
case Fragment::THRESH: return subsize + n_subs + BuildScript(k).size();
|
||||
}
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out)
|
||||
std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script)
|
||||
{
|
||||
out.clear();
|
||||
std::vector<Opcode> out;
|
||||
CScript::const_iterator it = script.begin(), itend = script.end();
|
||||
while (it != itend) {
|
||||
std::vector<unsigned char> push_data;
|
||||
opcodetype opcode;
|
||||
if (!script.GetOp(it, opcode, push_data)) {
|
||||
out.clear();
|
||||
return false;
|
||||
return {};
|
||||
} else if (opcode >= OP_1 && opcode <= OP_16) {
|
||||
// Deal with OP_n (GetOp does not turn them into pushes).
|
||||
push_data.assign(1, CScript::DecodeOP_N(opcode));
|
||||
|
|
@ -309,30 +304,28 @@ bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, st
|
|||
out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
|
||||
opcode = OP_VERIFY;
|
||||
} else if (IsPushdataOp(opcode)) {
|
||||
if (!CheckMinimalPush(push_data, opcode)) return false;
|
||||
if (!CheckMinimalPush(push_data, opcode)) return {};
|
||||
} else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) {
|
||||
// Rule out non minimal VERIFY sequences
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
out.emplace_back(opcode, std::move(push_data));
|
||||
}
|
||||
std::reverse(out.begin(), out.end());
|
||||
return true;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k) {
|
||||
std::optional<int64_t> ParseScriptNumber(const Opcode& in) {
|
||||
if (in.first == OP_0) {
|
||||
k = 0;
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
if (!in.second.empty()) {
|
||||
if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return false;
|
||||
if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return {};
|
||||
try {
|
||||
k = CScriptNum(in.second, true).GetInt64();
|
||||
return true;
|
||||
return CScriptNum(in.second, true).GetInt64();
|
||||
} catch(const scriptnum_error&) {}
|
||||
}
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
int FindNextChar(Span<const char> sp, const char m)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#define BITCOIN_SCRIPT_MINISCRIPT_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
|
@ -40,7 +41,7 @@ namespace miniscript {
|
|||
* - For example: older(n) = <n> OP_CHECKSEQUENCEVERIFY.
|
||||
* - "V" Verify:
|
||||
* - Takes its inputs from the top of the stack.
|
||||
* - When satisfactied, pushes nothing.
|
||||
* - When satisfied, pushes nothing.
|
||||
* - Cannot be dissatisfied.
|
||||
* - This can be obtained by adding an OP_VERIFY to a B, modifying the last opcode
|
||||
* of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY
|
||||
|
|
@ -179,6 +180,8 @@ inline constexpr Type operator"" _mst(const char* c, size_t l) {
|
|||
return typ;
|
||||
}
|
||||
|
||||
using Opcode = std::pair<opcodetype, std::vector<unsigned char>>;
|
||||
|
||||
template<typename Key> struct Node;
|
||||
template<typename Key> using NodeRef = std::shared_ptr<const Node<Key>>;
|
||||
|
||||
|
|
@ -224,10 +227,10 @@ enum class Fragment {
|
|||
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);
|
||||
|
|
@ -279,7 +282,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)
|
||||
|
|
@ -298,6 +301,8 @@ private:
|
|||
const Type typ;
|
||||
//! Cached script length (computed by CalcScriptLen).
|
||||
const size_t scriptlen;
|
||||
//! Whether a public key appears more than once in this node.
|
||||
const bool duplicate_key;
|
||||
|
||||
//! Compute the length of the script for this miniscript (including children).
|
||||
size_t CalcScriptLen() const {
|
||||
|
|
@ -306,7 +311,7 @@ private:
|
|||
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.
|
||||
|
|
@ -329,6 +334,8 @@ private:
|
|||
* 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
|
||||
|
|
@ -393,6 +400,20 @@ private:
|
|||
return std::move(results[0]);
|
||||
}
|
||||
|
||||
/** Like TreeEvalMaybe, but without downfn or State type.
|
||||
* upfn takes (const Node&, Span<Result>) and returns std::optional<Result>. */
|
||||
template<typename Result, typename UpFn>
|
||||
std::optional<Result> TreeEvalMaybe(UpFn upfn) const
|
||||
{
|
||||
struct DummyState {};
|
||||
return TreeEvalMaybe<Result>(DummyState{},
|
||||
[](DummyState, const Node&, size_t) { return DummyState{}; },
|
||||
[&upfn](DummyState, const Node& node, Span<Result> subs) {
|
||||
return upfn(node, subs);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Like TreeEvalMaybe, but always produces a result. upfn must return Result. */
|
||||
template<typename Result, typename State, typename DownFn, typename UpFn>
|
||||
Result TreeEval(State root_state, DownFn&& downfn, UpFn upfn) const
|
||||
|
|
@ -408,13 +429,33 @@ private:
|
|||
));
|
||||
}
|
||||
|
||||
/** Compare two miniscript subtrees, using a non-recursive algorithm. */
|
||||
friend int Compare(const Node<Key>& node1, const Node<Key>& node2)
|
||||
{
|
||||
std::vector<std::pair<const Node<Key>&, const Node<Key>&>> queue;
|
||||
queue.emplace_back(node1, node2);
|
||||
while (!queue.empty()) {
|
||||
const auto& [a, b] = queue.back();
|
||||
queue.pop_back();
|
||||
if (std::tie(a.fragment, a.k, a.keys, a.data) < std::tie(b.fragment, b.k, b.keys, b.data)) return -1;
|
||||
if (std::tie(b.fragment, b.k, b.keys, b.data) < std::tie(a.fragment, a.k, a.keys, a.data)) return 1;
|
||||
if (a.subs.size() < b.subs.size()) return -1;
|
||||
if (b.subs.size() < a.subs.size()) return 1;
|
||||
size_t n = a.subs.size();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
queue.emplace_back(*a.subs[n - 1 - i], *b.subs[n - 1 - i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//! Compute the type for this miniscript.
|
||||
Type CalcType() const {
|
||||
using namespace internal;
|
||||
|
||||
// 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.
|
||||
|
|
@ -422,7 +463,7 @@ private:
|
|||
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:
|
||||
|
|
@ -434,17 +475,17 @@ public:
|
|||
// 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);
|
||||
|
|
@ -491,45 +532,44 @@ public:
|
|||
}
|
||||
}
|
||||
assert(false);
|
||||
return {};
|
||||
};
|
||||
return TreeEval<CScript>(false, downfn, upfn);
|
||||
}
|
||||
|
||||
template<typename CTx>
|
||||
bool ToString(const CTx& ctx, std::string& ret) const {
|
||||
std::optional<std::string> ToString(const CTx& ctx) const {
|
||||
// To construct the std::string representation for a Miniscript object, we use
|
||||
// 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) + ")";
|
||||
auto key_str = ctx.ToString(node.subs[0]->keys[0]);
|
||||
if (!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 {};
|
||||
return std::move(ret) + "pkh(" + std::move(key_str) + ")";
|
||||
auto key_str = ctx.ToString(node.subs[0]->keys[0]);
|
||||
if (!key_str) return {};
|
||||
return std::move(ret) + "pkh(" + std::move(*key_str) + ")";
|
||||
}
|
||||
return "c" + std::move(subs[0]);
|
||||
case Fragment::WRAP_D: return "d" + std::move(subs[0]);
|
||||
|
|
@ -538,24 +578,24 @@ public:
|
|||
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 {};
|
||||
return std::move(ret) + "pk_k(" + std::move(key_str) + ")";
|
||||
auto key_str = ctx.ToString(node.keys[0]);
|
||||
if (!key_str) return {};
|
||||
return std::move(ret) + "pk_k(" + std::move(*key_str) + ")";
|
||||
}
|
||||
case Fragment::PK_H: {
|
||||
std::string key_str;
|
||||
if (!ctx.ToString(node.keys[0], key_str)) return {};
|
||||
return std::move(ret) + "pk_h(" + std::move(key_str) + ")";
|
||||
auto key_str = ctx.ToString(node.keys[0]);
|
||||
if (!key_str) return {};
|
||||
return std::move(ret) + "pk_h(" + std::move(*key_str) + ")";
|
||||
}
|
||||
case Fragment::AFTER: return std::move(ret) + "after(" + ::ToString(node.k) + ")";
|
||||
case Fragment::OLDER: return std::move(ret) + "older(" + ::ToString(node.k) + ")";
|
||||
|
|
@ -573,14 +613,14 @@ public:
|
|||
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);
|
||||
for (const auto& key : node.keys) {
|
||||
std::string key_str;
|
||||
if (!ctx.ToString(key, key_str)) return {};
|
||||
str += "," + std::move(key_str);
|
||||
auto key_str = ctx.ToString(key);
|
||||
if (!key_str) return {};
|
||||
str += "," + std::move(*key_str);
|
||||
}
|
||||
return std::move(str) + ")";
|
||||
}
|
||||
|
|
@ -591,18 +631,16 @@ public:
|
|||
}
|
||||
return std::move(str) + ")";
|
||||
}
|
||||
default: assert(false);
|
||||
default: break;
|
||||
}
|
||||
return ""; // Should never be reached.
|
||||
assert(false);
|
||||
};
|
||||
|
||||
auto res = TreeEvalMaybe<std::string>(false, downfn, upfn);
|
||||
if (res.has_value()) ret = std::move(*res);
|
||||
return res.has_value();
|
||||
return TreeEvalMaybe<std::string>(false, downfn, upfn);
|
||||
}
|
||||
|
||||
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};
|
||||
|
|
@ -672,11 +710,10 @@ public:
|
|||
}
|
||||
}
|
||||
assert(false);
|
||||
return {0, {}, {}};
|
||||
}
|
||||
|
||||
internal::StackSize CalcStackSize() const {
|
||||
switch (nodetype) {
|
||||
switch (fragment) {
|
||||
case Fragment::JUST_0: return {{}, 0};
|
||||
case Fragment::JUST_1:
|
||||
case Fragment::OLDER:
|
||||
|
|
@ -723,7 +760,42 @@ public:
|
|||
}
|
||||
}
|
||||
assert(false);
|
||||
return {{}, {}};
|
||||
}
|
||||
|
||||
/** Check whether any key is repeated.
|
||||
* This uses a custom key comparator provided by the context in order to still detect duplicates
|
||||
* for more complicated types.
|
||||
*/
|
||||
template<typename Ctx> bool ContainsDuplicateKey(const Ctx& ctx) const {
|
||||
// We cannot use a lambda here, as lambdas are non assignable, and the set operations
|
||||
// below require moving the comparators around.
|
||||
struct Comp {
|
||||
const Ctx* ctx_ptr;
|
||||
Comp(const Ctx& ctx) : ctx_ptr(&ctx) {}
|
||||
bool operator()(const Key& a, const Key& b) const { return ctx_ptr->KeyCompare(a, b); }
|
||||
};
|
||||
using set = std::set<Key, Comp>;
|
||||
|
||||
auto upfn = [this, &ctx](const Node& node, Span<set> subs) -> std::optional<set> {
|
||||
if (&node != this && node.duplicate_key) return {};
|
||||
|
||||
size_t keys_count = node.keys.size();
|
||||
set key_set{node.keys.begin(), node.keys.end(), Comp(ctx)};
|
||||
if (key_set.size() != keys_count) return {};
|
||||
|
||||
for (auto& sub: subs) {
|
||||
keys_count += sub.size();
|
||||
// Small optimization: std::set::merge is linear in the size of the second arg but
|
||||
// logarithmic in the size of the first.
|
||||
if (key_set.size() < sub.size()) std::swap(key_set, sub);
|
||||
key_set.merge(sub);
|
||||
if (key_set.size() != keys_count) return {};
|
||||
}
|
||||
|
||||
return key_set;
|
||||
};
|
||||
|
||||
return !TreeEvalMaybe<set>(upfn);
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -758,35 +830,31 @@ public:
|
|||
//! Check whether this script always needs a signature.
|
||||
bool NeedsSignature() const { return GetType() << "s"_mst; }
|
||||
|
||||
//! Do all sanity checks.
|
||||
bool IsSane() const { return IsValid() && GetType() << "mk"_mst && CheckOpsLimit() && CheckStackSize(); }
|
||||
//! Check whether there is no satisfaction path that contains both timelocks and heightlocks
|
||||
bool CheckTimeLocksMix() const { return GetType() << "k"_mst; }
|
||||
|
||||
//! Check whether there is no duplicate key across this fragment and all its sub-fragments.
|
||||
bool CheckDuplicateKey() const { return !duplicate_key; }
|
||||
|
||||
//! Whether successful non-malleable satisfactions are guaranteed to be valid.
|
||||
bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); }
|
||||
|
||||
//! Whether the apparent policy of this node matches its script semantics. Doesn't guarantee it is a safe script on its own.
|
||||
bool IsSaneSubexpression() const { return ValidSatisfactions() && IsNonMalleable() && CheckTimeLocksMix() && CheckDuplicateKey(); }
|
||||
|
||||
//! Check whether this node is safe as a script on its own.
|
||||
bool IsSaneTopLevel() const { return IsValidTopLevel() && IsSane() && NeedsSignature(); }
|
||||
bool IsSane() const { return IsValidTopLevel() && IsSaneSubexpression() && NeedsSignature(); }
|
||||
|
||||
//! Equality testing.
|
||||
bool operator==(const Node<Key>& arg) const
|
||||
{
|
||||
if (nodetype != arg.nodetype) return false;
|
||||
if (k != arg.k) return false;
|
||||
if (data != arg.data) return false;
|
||||
if (keys != arg.keys) return false;
|
||||
if (subs.size() != arg.subs.size()) return false;
|
||||
for (size_t i = 0; i < subs.size(); ++i) {
|
||||
if (!(*subs[i] == *arg.subs[i])) return false;
|
||||
}
|
||||
assert(scriptlen == arg.scriptlen);
|
||||
assert(typ == arg.typ);
|
||||
return true;
|
||||
}
|
||||
bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; }
|
||||
|
||||
// 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()) {}
|
||||
template <typename Ctx> Node(const Ctx& ctx, 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()), duplicate_key(ContainsDuplicateKey(ctx)) {}
|
||||
template <typename Ctx> Node(const Ctx& ctx, 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()), duplicate_key(ContainsDuplicateKey(ctx)) {}
|
||||
template <typename Ctx> Node(const Ctx& ctx, 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()), duplicate_key(ContainsDuplicateKey(ctx)) {}
|
||||
template <typename Ctx> Node(const Ctx& ctx, 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()), duplicate_key(ContainsDuplicateKey(ctx)) {}
|
||||
template <typename Ctx> Node(const Ctx& ctx, 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()), duplicate_key(ContainsDuplicateKey(ctx)) {}
|
||||
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
|
@ -847,15 +915,15 @@ enum class ParseContext {
|
|||
|
||||
int FindNextChar(Span<const char> in, const char m);
|
||||
|
||||
/** Parse a key string ending with a ')' or ','. */
|
||||
/** Parse a key string ending at the end of the fragment's text representation. */
|
||||
template<typename Key, typename Ctx>
|
||||
std::optional<std::pair<Key, int>> ParseKeyEnd(Span<const char> in, const Ctx& ctx)
|
||||
{
|
||||
Key key;
|
||||
int key_size = FindNextChar(in, ')');
|
||||
if (key_size < 1) return {};
|
||||
if (!ctx.FromString(in.begin(), in.begin() + key_size, key)) return {};
|
||||
return {{std::move(key), key_size}};
|
||||
auto key = ctx.FromString(in.begin(), in.begin() + key_size);
|
||||
if (!key) return {};
|
||||
return {{std::move(*key), key_size}};
|
||||
}
|
||||
|
||||
/** Parse a hex string ending at the end of the fragment's text representation. */
|
||||
|
|
@ -873,15 +941,15 @@ 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 */
|
||||
template<typename Key>
|
||||
void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false)
|
||||
template<typename Key, typename Ctx>
|
||||
void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false)
|
||||
{
|
||||
NodeRef<Key> child = std::move(constructed.back());
|
||||
constructed.pop_back();
|
||||
if (reverse) {
|
||||
constructed.back() = MakeNodeRef<Key>(nt, Vector(std::move(child), std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(child), std::move(constructed.back())));
|
||||
} else {
|
||||
constructed.back() = MakeNodeRef<Key>(nt, Vector(std::move(constructed.back()), std::move(child)));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -934,7 +1002,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
to_parse.emplace_back(ParseContext::WRAP_T, -1, -1);
|
||||
} else if (in[j] == 'l') {
|
||||
// The l: wrapper is equivalent to or_i(0,X)
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0));
|
||||
to_parse.emplace_back(ParseContext::OR_I, -1, -1);
|
||||
} else {
|
||||
return {};
|
||||
|
|
@ -946,56 +1014,56 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
}
|
||||
case ParseContext::EXPR: {
|
||||
if (Const("0", in)) {
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0));
|
||||
} else if (Const("1", in)) {
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_1));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1));
|
||||
} else if (Const("pk(", in)) {
|
||||
auto res = ParseKeyEnd<Key, Ctx>(in, ctx);
|
||||
if (!res) return {};
|
||||
auto& [key, key_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::WRAP_C, Vector(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key))))));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key))))));
|
||||
in = in.subspan(key_size + 1);
|
||||
} else if (Const("pkh(", in)) {
|
||||
auto res = ParseKeyEnd<Key>(in, ctx);
|
||||
if (!res) return {};
|
||||
auto& [key, key_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::WRAP_C, Vector(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key))))));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key))))));
|
||||
in = in.subspan(key_size + 1);
|
||||
} else if (Const("pk_k(", in)) {
|
||||
auto res = ParseKeyEnd<Key>(in, ctx);
|
||||
if (!res) return {};
|
||||
auto& [key, key_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key))));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key))));
|
||||
in = in.subspan(key_size + 1);
|
||||
} else if (Const("pk_h(", in)) {
|
||||
auto res = ParseKeyEnd<Key>(in, ctx);
|
||||
if (!res) return {};
|
||||
auto& [key, key_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key))));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key))));
|
||||
in = in.subspan(key_size + 1);
|
||||
} else if (Const("sha256(", in)) {
|
||||
auto res = ParseHexStrEnd(in, 32, ctx);
|
||||
if (!res) return {};
|
||||
auto& [hash, hash_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::SHA256, std::move(hash)));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, std::move(hash)));
|
||||
in = in.subspan(hash_size + 1);
|
||||
} else if (Const("ripemd160(", in)) {
|
||||
auto res = ParseHexStrEnd(in, 20, ctx);
|
||||
if (!res) return {};
|
||||
auto& [hash, hash_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::RIPEMD160, std::move(hash)));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, std::move(hash)));
|
||||
in = in.subspan(hash_size + 1);
|
||||
} else if (Const("hash256(", in)) {
|
||||
auto res = ParseHexStrEnd(in, 32, ctx);
|
||||
if (!res) return {};
|
||||
auto& [hash, hash_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::HASH256, std::move(hash)));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, std::move(hash)));
|
||||
in = in.subspan(hash_size + 1);
|
||||
} else if (Const("hash160(", in)) {
|
||||
auto res = ParseHexStrEnd(in, 20, ctx);
|
||||
if (!res) return {};
|
||||
auto& [hash, hash_size] = *res;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::HASH160, std::move(hash)));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, std::move(hash)));
|
||||
in = in.subspan(hash_size + 1);
|
||||
} else if (Const("after(", in)) {
|
||||
int arg_size = FindNextChar(in, ')');
|
||||
|
|
@ -1003,7 +1071,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
int64_t num;
|
||||
if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
|
||||
if (num < 1 || num >= 0x80000000L) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::AFTER, num));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, num));
|
||||
in = in.subspan(arg_size + 1);
|
||||
} else if (Const("older(", in)) {
|
||||
int arg_size = FindNextChar(in, ')');
|
||||
|
|
@ -1011,7 +1079,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
int64_t num;
|
||||
if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
|
||||
if (num < 1 || num >= 0x80000000L) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::OLDER, num));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, num));
|
||||
in = in.subspan(arg_size + 1);
|
||||
} else if (Const("multi(", in)) {
|
||||
// Get threshold
|
||||
|
|
@ -1022,17 +1090,17 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
// Get keys
|
||||
std::vector<Key> keys;
|
||||
while (next_comma != -1) {
|
||||
Key key;
|
||||
next_comma = FindNextChar(in, ',');
|
||||
int key_length = (next_comma == -1) ? FindNextChar(in, ')') : next_comma;
|
||||
if (key_length < 1) return {};
|
||||
if (!ctx.FromString(in.begin(), in.begin() + key_length, key)) return {};
|
||||
keys.push_back(std::move(key));
|
||||
auto key = ctx.FromString(in.begin(), in.begin() + key_length);
|
||||
if (!key) return {};
|
||||
keys.push_back(std::move(*key));
|
||||
in = in.subspan(key_length + 1);
|
||||
}
|
||||
if (keys.size() < 1 || keys.size() > 20) return {};
|
||||
if (k < 1 || k > (int64_t)keys.size()) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::MULTI, std::move(keys), k));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), k));
|
||||
} else if (Const("thresh(", in)) {
|
||||
int next_comma = FindNextChar(in, ',');
|
||||
if (next_comma < 1) return {};
|
||||
|
|
@ -1076,69 +1144,69 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
break;
|
||||
}
|
||||
case ParseContext::ALT: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_A, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::SWAP: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_S, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::CHECK: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_C, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::DUP_IF: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_D, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::NON_ZERO: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_J, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::ZERO_NOTEQUAL: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_N, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::VERIFY: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_V, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case ParseContext::WRAP_U: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(Fragment::JUST_0)));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_0)));
|
||||
break;
|
||||
}
|
||||
case ParseContext::WRAP_T: {
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(Fragment::JUST_1)));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1)));
|
||||
break;
|
||||
}
|
||||
case ParseContext::AND_B: {
|
||||
BuildBack(Fragment::AND_B, constructed);
|
||||
BuildBack(ctx, Fragment::AND_B, constructed);
|
||||
break;
|
||||
}
|
||||
case ParseContext::AND_N: {
|
||||
auto mid = std::move(constructed.back());
|
||||
constructed.pop_back();
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(Fragment::JUST_0)));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0)));
|
||||
break;
|
||||
}
|
||||
case ParseContext::AND_V: {
|
||||
BuildBack(Fragment::AND_V, constructed);
|
||||
BuildBack(ctx, Fragment::AND_V, constructed);
|
||||
break;
|
||||
}
|
||||
case ParseContext::OR_B: {
|
||||
BuildBack(Fragment::OR_B, constructed);
|
||||
BuildBack(ctx, Fragment::OR_B, constructed);
|
||||
break;
|
||||
}
|
||||
case ParseContext::OR_C: {
|
||||
BuildBack(Fragment::OR_C, constructed);
|
||||
BuildBack(ctx, Fragment::OR_C, constructed);
|
||||
break;
|
||||
}
|
||||
case ParseContext::OR_D: {
|
||||
BuildBack(Fragment::OR_D, constructed);
|
||||
BuildBack(ctx, Fragment::OR_D, constructed);
|
||||
break;
|
||||
}
|
||||
case ParseContext::OR_I: {
|
||||
BuildBack(Fragment::OR_I, constructed);
|
||||
BuildBack(ctx, Fragment::OR_I, constructed);
|
||||
break;
|
||||
}
|
||||
case ParseContext::ANDOR: {
|
||||
|
|
@ -1146,7 +1214,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
constructed.pop_back();
|
||||
auto mid = std::move(constructed.back());
|
||||
constructed.pop_back();
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right)));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right)));
|
||||
break;
|
||||
}
|
||||
case ParseContext::THRESH: {
|
||||
|
|
@ -1165,7 +1233,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
constructed.pop_back();
|
||||
}
|
||||
std::reverse(subs.begin(), subs.end());
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::THRESH, std::move(subs), k));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -1200,10 +1268,10 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
|
|||
* and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, OP_EQUAL
|
||||
* respectively, plus OP_VERIFY.
|
||||
*/
|
||||
bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out);
|
||||
std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script);
|
||||
|
||||
/** Determine whether the passed pair (created by DecomposeScript) is pushing a number. */
|
||||
bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k);
|
||||
std::optional<int64_t> ParseScriptNumber(const Opcode& in);
|
||||
|
||||
enum class DecodeContext {
|
||||
/** A single expression of type B, K, or V. Specifically, this can't be an
|
||||
|
|
@ -1300,58 +1368,59 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
// Constants
|
||||
if (in[0].first == OP_1) {
|
||||
++in;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_1));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1));
|
||||
break;
|
||||
}
|
||||
if (in[0].first == OP_0) {
|
||||
++in;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0));
|
||||
break;
|
||||
}
|
||||
// Public keys
|
||||
if (in[0].second.size() == 33) {
|
||||
Key key;
|
||||
if (!ctx.FromPKBytes(in[0].second.begin(), in[0].second.end(), key)) return {};
|
||||
auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end());
|
||||
if (!key) return {};
|
||||
++in;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key))));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(*key))));
|
||||
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) {
|
||||
Key key;
|
||||
if (!ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end(), key)) return {};
|
||||
auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end());
|
||||
if (!key) return {};
|
||||
in += 5;
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key))));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(*key))));
|
||||
break;
|
||||
}
|
||||
// Time locks
|
||||
if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && ParseScriptNumber(in[1], k)) {
|
||||
std::optional<int64_t> num;
|
||||
if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) {
|
||||
in += 2;
|
||||
if (k < 1 || k > 0x7FFFFFFFL) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::OLDER, k));
|
||||
if (*num < 1 || *num > 0x7FFFFFFFL) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, *num));
|
||||
break;
|
||||
}
|
||||
if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && ParseScriptNumber(in[1], k)) {
|
||||
if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) {
|
||||
in += 2;
|
||||
if (k < 1 || k > 0x7FFFFFFFL) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::AFTER, k));
|
||||
if (num < 1 || num > 0x7FFFFFFFL) return {};
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, *num));
|
||||
break;
|
||||
}
|
||||
// Hashes
|
||||
if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && ParseScriptNumber(in[5], k) && k == 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) {
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::SHA256, in[1].second));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, in[1].second));
|
||||
in += 7;
|
||||
break;
|
||||
} else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) {
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::RIPEMD160, in[1].second));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, in[1].second));
|
||||
in += 7;
|
||||
break;
|
||||
} else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) {
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::HASH256, in[1].second));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, in[1].second));
|
||||
in += 7;
|
||||
break;
|
||||
} else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) {
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::HASH160, in[1].second));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, in[1].second));
|
||||
in += 7;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1359,20 +1428,20 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
// Multi
|
||||
if (last - in >= 3 && in[0].first == OP_CHECKMULTISIG) {
|
||||
std::vector<Key> keys;
|
||||
if (!ParseScriptNumber(in[1], n)) return {};
|
||||
if (last - in < 3 + n) return {};
|
||||
if (n < 1 || n > 20) return {};
|
||||
for (int i = 0; i < n; ++i) {
|
||||
Key key;
|
||||
const auto n = ParseScriptNumber(in[1]);
|
||||
if (!n || last - in < 3 + *n) return {};
|
||||
if (*n < 1 || *n > 20) return {};
|
||||
for (int i = 0; i < *n; ++i) {
|
||||
if (in[2 + i].second.size() != 33) return {};
|
||||
if (!ctx.FromPKBytes(in[2 + i].second.begin(), in[2 + i].second.end(), key)) return {};
|
||||
keys.push_back(std::move(key));
|
||||
auto key = ctx.FromPKBytes(in[2 + i].second.begin(), in[2 + i].second.end());
|
||||
if (!key) return {};
|
||||
keys.push_back(std::move(*key));
|
||||
}
|
||||
if (!ParseScriptNumber(in[2 + n], k)) return {};
|
||||
if (k < 1 || k > n) return {};
|
||||
in += 3 + n;
|
||||
const auto k = ParseScriptNumber(in[2 + *n]);
|
||||
if (!k || *k < 1 || *k > *n) return {};
|
||||
in += 3 + *n;
|
||||
std::reverse(keys.begin(), keys.end());
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::MULTI, std::move(keys), k));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), *k));
|
||||
break;
|
||||
}
|
||||
/** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather
|
||||
|
|
@ -1400,10 +1469,10 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
break;
|
||||
}
|
||||
// Thresh
|
||||
if (last - in >= 3 && in[0].first == OP_EQUAL && ParseScriptNumber(in[1], k)) {
|
||||
if (k < 1) return {};
|
||||
if (last - in >= 3 && in[0].first == OP_EQUAL && (num = ParseScriptNumber(in[1]))) {
|
||||
if (*num < 1) return {};
|
||||
in += 2;
|
||||
to_parse.emplace_back(DecodeContext::THRESH_W, 0, k);
|
||||
to_parse.emplace_back(DecodeContext::THRESH_W, 0, *num);
|
||||
break;
|
||||
}
|
||||
// OP_ENDIF can be WRAP_J, WRAP_D, ANDOR, OR_C, OR_D, or OR_I
|
||||
|
|
@ -1467,63 +1536,63 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
case DecodeContext::SWAP: {
|
||||
if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {};
|
||||
++in;
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_S, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::ALT: {
|
||||
if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {};
|
||||
++in;
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_A, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::CHECK: {
|
||||
if (constructed.empty()) return {};
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_C, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::DUP_IF: {
|
||||
if (constructed.empty()) return {};
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_D, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::VERIFY: {
|
||||
if (constructed.empty()) return {};
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_V, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::NON_ZERO: {
|
||||
if (constructed.empty()) return {};
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_J, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::ZERO_NOTEQUAL: {
|
||||
if (constructed.empty()) return {};
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_N, Vector(std::move(constructed.back())));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back())));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::AND_V: {
|
||||
if (constructed.size() < 2) return {};
|
||||
BuildBack(Fragment::AND_V, constructed, /*reverse=*/true);
|
||||
BuildBack(ctx, Fragment::AND_V, constructed, /*reverse=*/true);
|
||||
break;
|
||||
}
|
||||
case DecodeContext::AND_B: {
|
||||
if (constructed.size() < 2) return {};
|
||||
BuildBack(Fragment::AND_B, constructed, /*reverse=*/true);
|
||||
BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true);
|
||||
break;
|
||||
}
|
||||
case DecodeContext::OR_B: {
|
||||
if (constructed.size() < 2) return {};
|
||||
BuildBack(Fragment::OR_B, constructed, /*reverse=*/true);
|
||||
BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true);
|
||||
break;
|
||||
}
|
||||
case DecodeContext::OR_C: {
|
||||
if (constructed.size() < 2) return {};
|
||||
BuildBack(Fragment::OR_C, constructed, /*reverse=*/true);
|
||||
BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true);
|
||||
break;
|
||||
}
|
||||
case DecodeContext::OR_D: {
|
||||
if (constructed.size() < 2) return {};
|
||||
BuildBack(Fragment::OR_D, constructed, /*reverse=*/true);
|
||||
BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true);
|
||||
break;
|
||||
}
|
||||
case DecodeContext::ANDOR: {
|
||||
|
|
@ -1533,7 +1602,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
NodeRef<Key> right = std::move(constructed.back());
|
||||
constructed.pop_back();
|
||||
NodeRef<Key> mid = std::move(constructed.back());
|
||||
constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right)));
|
||||
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right)));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::THRESH_W: {
|
||||
|
|
@ -1557,7 +1626,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
constructed.pop_back();
|
||||
subs.push_back(std::move(sub));
|
||||
}
|
||||
constructed.push_back(MakeNodeRef<Key>(Fragment::THRESH, std::move(subs), k));
|
||||
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k));
|
||||
break;
|
||||
}
|
||||
case DecodeContext::ENDIF: {
|
||||
|
|
@ -1607,7 +1676,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
|
|||
if (in >= last) return {};
|
||||
if (in[0].first == OP_IF) {
|
||||
++in;
|
||||
BuildBack(Fragment::OR_I, constructed, /*reverse=*/true);
|
||||
BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true);
|
||||
} else if (in[0].first == OP_NOTIF) {
|
||||
++in;
|
||||
to_parse.emplace_back(DecodeContext::ANDOR, -1, -1);
|
||||
|
|
@ -1638,12 +1707,12 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx&
|
|||
template<typename Ctx>
|
||||
inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) {
|
||||
using namespace internal;
|
||||
std::vector<std::pair<opcodetype, std::vector<unsigned char>>> decomposed;
|
||||
if (!DecomposeScript(script, decomposed)) return {};
|
||||
auto it = decomposed.begin();
|
||||
auto ret = DecodeScript<typename Ctx::Key>(it, decomposed.end(), ctx);
|
||||
auto decomposed = DecomposeScript(script);
|
||||
if (!decomposed) return {};
|
||||
auto it = decomposed->begin();
|
||||
auto ret = DecodeScript<typename Ctx::Key>(it, decomposed->end(), ctx);
|
||||
if (!ret) return {};
|
||||
if (it != decomposed.end()) return {};
|
||||
if (it != decomposed->end()) return {};
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
167
src/test/fuzz/miniscript.cpp
Normal file
167
src/test/fuzz/miniscript.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright (c) 2021 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <core_io.h>
|
||||
#include <hash.h>
|
||||
#include <key.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
namespace {
|
||||
|
||||
//! Some pre-computed data for more efficient string roundtrips.
|
||||
struct TestData {
|
||||
typedef CPubKey Key;
|
||||
|
||||
// Precomputed public keys.
|
||||
std::vector<Key> dummy_keys;
|
||||
std::map<Key, int> dummy_key_idx_map;
|
||||
std::map<CKeyID, Key> dummy_keys_map;
|
||||
|
||||
//! Set the precomputed data.
|
||||
void Init() {
|
||||
unsigned char keydata[32] = {1};
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
keydata[31] = i;
|
||||
CKey privkey;
|
||||
privkey.Set(keydata, keydata + 32, true);
|
||||
const Key pubkey = privkey.GetPubKey();
|
||||
|
||||
dummy_keys.push_back(pubkey);
|
||||
dummy_key_idx_map.emplace(pubkey, i);
|
||||
dummy_keys_map.insert({pubkey.GetID(), pubkey});
|
||||
}
|
||||
}
|
||||
} TEST_DATA;
|
||||
|
||||
/**
|
||||
* Context to parse a Miniscript node to and from Script or text representation.
|
||||
* Uses an integer (an index in the dummy keys array from the test data) as keys in order
|
||||
* to focus on fuzzing the Miniscript nodes' test representation, not the key representation.
|
||||
*/
|
||||
struct ParserContext {
|
||||
typedef CPubKey Key;
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
std::optional<std::string> ToString(const Key& key) const
|
||||
{
|
||||
auto it = TEST_DATA.dummy_key_idx_map.find(key);
|
||||
if (it == TEST_DATA.dummy_key_idx_map.end()) return {};
|
||||
uint8_t idx = it->second;
|
||||
return HexStr(Span{&idx, 1});
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromString(I first, I last) const {
|
||||
if (last - first != 2) return {};
|
||||
auto idx = ParseHex(std::string(first, last));
|
||||
if (idx.size() != 1) return {};
|
||||
return TEST_DATA.dummy_keys[idx[0]];
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||
Key key;
|
||||
key.Set(first, last);
|
||||
if (!key.IsValid()) return {};
|
||||
return key;
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKHBytes(I first, I last) const {
|
||||
assert(last - first == 20);
|
||||
CKeyID keyid;
|
||||
std::copy(first, last, keyid.begin());
|
||||
const auto it = TEST_DATA.dummy_keys_map.find(keyid);
|
||||
if (it == TEST_DATA.dummy_keys_map.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
} PARSER_CTX;
|
||||
|
||||
//! Context that implements naive conversion from/to script only, for roundtrip testing.
|
||||
struct ScriptParserContext {
|
||||
//! For Script roundtrip we never need the key from a key hash.
|
||||
struct Key {
|
||||
bool is_hash;
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a.data < b.data;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& ToPKBytes(const Key& key) const
|
||||
{
|
||||
assert(!key.is_hash);
|
||||
return key.data;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char> ToPKHBytes(const Key& key) const
|
||||
{
|
||||
if (key.is_hash) return key.data;
|
||||
const auto h = Hash160(key.data);
|
||||
return {h.begin(), h.end()};
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const
|
||||
{
|
||||
Key key;
|
||||
key.data.assign(first, last);
|
||||
key.is_hash = false;
|
||||
return key;
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKHBytes(I first, I last) const
|
||||
{
|
||||
Key key;
|
||||
key.data.assign(first, last);
|
||||
key.is_hash = true;
|
||||
return key;
|
||||
}
|
||||
} SCRIPT_PARSER_CONTEXT;
|
||||
|
||||
} // namespace
|
||||
|
||||
void FuzzInit()
|
||||
{
|
||||
ECC_Start();
|
||||
TEST_DATA.Init();
|
||||
}
|
||||
|
||||
/* Fuzz tests that test parsing from a string, and roundtripping via string. */
|
||||
FUZZ_TARGET_INIT(miniscript_string, FuzzInit)
|
||||
{
|
||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||
auto str = provider.ConsumeRemainingBytesAsString();
|
||||
auto parsed = miniscript::FromString(str, PARSER_CTX);
|
||||
if (!parsed) return;
|
||||
|
||||
const auto str2 = parsed->ToString(PARSER_CTX);
|
||||
assert(str2);
|
||||
auto parsed2 = miniscript::FromString(*str2, PARSER_CTX);
|
||||
assert(parsed2);
|
||||
assert(*parsed == *parsed2);
|
||||
}
|
||||
|
||||
/* Fuzz tests that test parsing from a script, and roundtripping via script. */
|
||||
FUZZ_TARGET(miniscript_script)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
||||
if (!script) return;
|
||||
|
||||
const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT);
|
||||
if (!ms) return;
|
||||
|
||||
assert(ms->ToScript(SCRIPT_PARSER_CONTEXT) == *script);
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <core_io.h>
|
||||
#include <hash.h>
|
||||
#include <key.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
#include <span.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
using miniscript::operator""_mst;
|
||||
|
||||
|
||||
struct Converter {
|
||||
typedef CPubKey Key;
|
||||
|
||||
bool ToString(const Key& key, std::string& ret) const {
|
||||
ret = HexStr(key);
|
||||
return true;
|
||||
}
|
||||
const std::vector<unsigned char> ToPKBytes(const Key& key) const {
|
||||
return {key.begin(), key.end()};
|
||||
}
|
||||
const std::vector<unsigned char> ToPKHBytes(const Key& key) const {
|
||||
const auto h = Hash160(key);
|
||||
return {h.begin(), h.end()};
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
bool FromString(I first, I last, Key& key) const {
|
||||
const auto bytes = ParseHex(std::string(first, last));
|
||||
key.Set(bytes.begin(), bytes.end());
|
||||
return key.IsValid();
|
||||
}
|
||||
template<typename I>
|
||||
bool FromPKBytes(I first, I last, CPubKey& key) const {
|
||||
key.Set(first, last);
|
||||
return key.IsValid();
|
||||
}
|
||||
template<typename I>
|
||||
bool FromPKHBytes(I first, I last, CPubKey& key) const {
|
||||
assert(last - first == 20);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const Converter CONVERTER;
|
||||
|
||||
FUZZ_TARGET(miniscript_decode)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
||||
if (!script) return;
|
||||
|
||||
const auto ms = miniscript::FromScript(*script, CONVERTER);
|
||||
if (!ms) return;
|
||||
|
||||
// We can roundtrip it to its string representation.
|
||||
std::string ms_str;
|
||||
assert(ms->ToString(CONVERTER, ms_str));
|
||||
assert(*miniscript::FromString(ms_str, CONVERTER) == *ms);
|
||||
// The Script representation must roundtrip since we parsed it this way the first time.
|
||||
const CScript ms_script = ms->ToScript(CONVERTER);
|
||||
assert(ms_script == *script);
|
||||
}
|
||||
|
|
@ -71,6 +71,10 @@ std::unique_ptr<const TestData> g_testdata;
|
|||
struct KeyConverter {
|
||||
typedef CPubKey Key;
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
//! Convert a public key to bytes.
|
||||
std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; }
|
||||
|
||||
|
|
@ -84,27 +88,28 @@ struct KeyConverter {
|
|||
|
||||
//! Parse a public key from a range of hex characters.
|
||||
template<typename I>
|
||||
bool FromString(I first, I last, CPubKey& key) const {
|
||||
std::optional<Key> FromString(I first, I last) const {
|
||||
auto bytes = ParseHex(std::string(first, last));
|
||||
key.Set(bytes.begin(), bytes.end());
|
||||
return key.IsValid();
|
||||
Key key{bytes.begin(), bytes.end()};
|
||||
if (key.IsValid()) return key;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
bool FromPKBytes(I first, I last, CPubKey& key) const {
|
||||
key.Set(first, last);
|
||||
return key.IsValid();
|
||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||
Key key{first, last};
|
||||
if (key.IsValid()) return key;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
bool FromPKHBytes(I first, I last, CPubKey& key) const {
|
||||
std::optional<Key> FromPKHBytes(I first, I last) const {
|
||||
assert(last - first == 20);
|
||||
CKeyID keyid;
|
||||
std::copy(first, last, keyid.begin());
|
||||
auto it = g_testdata->pkmap.find(keyid);
|
||||
assert(it != g_testdata->pkmap.end());
|
||||
key = it->second;
|
||||
return true;
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -272,6 +277,19 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
|||
// its subs to all be 'u' (taken from https://github.com/rust-bitcoin/rust-miniscript/discussions/341).
|
||||
const auto ms_minimalif = miniscript::FromString("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),sc:pk_k(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798),sdv:older(32))", CONVERTER);
|
||||
BOOST_CHECK(!ms_minimalif);
|
||||
// A Miniscript with duplicate keys is not sane
|
||||
const auto ms_dup1 = miniscript::FromString("and_v(v:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER);
|
||||
BOOST_CHECK(ms_dup1);
|
||||
BOOST_CHECK(!ms_dup1->IsSane() && !ms_dup1->CheckDuplicateKey());
|
||||
// Same with a disjunction, and different key nodes (pk and pkh)
|
||||
const auto ms_dup2 = miniscript::FromString("or_b(c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER);
|
||||
BOOST_CHECK(ms_dup2 && !ms_dup2->IsSane() && !ms_dup2->CheckDuplicateKey());
|
||||
// Same when the duplicates are leaves or a larger tree
|
||||
const auto ms_dup3 = miniscript::FromString("or_i(and_b(pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)),and_b(older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER);
|
||||
BOOST_CHECK(ms_dup3 && !ms_dup3->IsSane() && !ms_dup3->CheckDuplicateKey());
|
||||
// Same when the duplicates are on different levels in the tree
|
||||
const auto ms_dup4 = miniscript::FromString("thresh(2,pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),a:and_b(dv:older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER);
|
||||
BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey());
|
||||
|
||||
// Timelock tests
|
||||
Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue