Permit delaying duplicate key check in miniscript::Node construction

This commit is contained in:
Pieter Wuille 2022-06-10 17:30:22 -04:00 committed by Antoine Poinsot
parent a688ff9046
commit 4cb8f9a92c
No known key found for this signature in database
GPG key ID: E13FC145CD3F4304
2 changed files with 139 additions and 90 deletions

View file

@ -276,6 +276,8 @@ struct StackSize {
StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {}; StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
}; };
struct NoDupCheck {};
} // namespace internal } // namespace internal
//! A node in a miniscript expression. //! A node in a miniscript expression.
@ -301,8 +303,13 @@ private:
const Type typ; const Type typ;
//! Cached script length (computed by CalcScriptLen). //! Cached script length (computed by CalcScriptLen).
const size_t scriptlen; const size_t scriptlen;
//! Whether a public key appears more than once in this node. //! Whether a public key appears more than once in this node. This value is initialized
const bool duplicate_key; //! by all constructors except the NoDupCheck ones. The NoDupCheck ones skip the
//! computation, requiring it to be done manually by invoking DuplicateKeyCheck().
//! DuplicateKeyCheck(), or a non-NoDupCheck constructor, will compute has_duplicate_keys
//! for all subnodes as well.
mutable std::optional<bool> has_duplicate_keys;
//! 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 {
@ -654,6 +661,7 @@ public:
return TreeEvalMaybe<std::string>(false, downfn, upfn); return TreeEvalMaybe<std::string>(false, downfn, upfn);
} }
private:
internal::Ops CalcOps() const { internal::Ops CalcOps() const {
switch (fragment) { switch (fragment) {
case Fragment::JUST_1: return {0, 0, {}}; case Fragment::JUST_1: return {0, 0, {}};
@ -777,11 +785,14 @@ public:
assert(false); assert(false);
} }
/** Check whether any key is repeated. public:
/** Update duplicate key information in this Node.
*
* This uses a custom key comparator provided by the context in order to still detect duplicates * This uses a custom key comparator provided by the context in order to still detect duplicates
* for more complicated types. * for more complicated types.
*/ */
template<typename Ctx> bool ContainsDuplicateKey(const Ctx& ctx) const { template<typename Ctx> void DuplicateKeyCheck(const Ctx& ctx) const
{
// We cannot use a lambda here, as lambdas are non assignable, and the set operations // We cannot use a lambda here, as lambdas are non assignable, and the set operations
// below require moving the comparators around. // below require moving the comparators around.
struct Comp { struct Comp {
@ -789,31 +800,55 @@ public:
Comp(const Ctx& ctx) : ctx_ptr(&ctx) {} Comp(const Ctx& ctx) : ctx_ptr(&ctx) {}
bool operator()(const Key& a, const Key& b) const { return ctx_ptr->KeyCompare(a, b); } 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> { // state in the recursive computation:
if (&node != this && node.duplicate_key) return {}; // - std::nullopt means "this node has duplicates"
// - an std::set means "this node has no duplicate keys, and they are: ...".
using keyset = std::set<Key, Comp>;
using state = std::optional<keyset>;
size_t keys_count = node.keys.size(); auto upfn = [&ctx](const Node& node, Span<state> subs) -> state {
set key_set{node.keys.begin(), node.keys.end(), Comp(ctx)}; // If this node is already known to have duplicates, nothing left to do.
if (key_set.size() != keys_count) return {}; if (node.has_duplicate_keys.has_value() && *node.has_duplicate_keys) return {};
// Check if one of the children is already known to have duplicates.
for (auto& sub : subs) { for (auto& sub : subs) {
keys_count += sub.size(); if (!sub.has_value()) {
// Small optimization: std::set::merge is linear in the size of the second arg but node.has_duplicate_keys = true;
// logarithmic in the size of the first. return {};
if (key_set.size() < sub.size()) std::swap(key_set, sub); }
key_set.merge(sub);
if (key_set.size() != keys_count) return {};
} }
// Start building the set of keys involved in this node and children.
// Start by keys in this node directly.
size_t keys_count = node.keys.size();
keyset key_set{node.keys.begin(), node.keys.end(), Comp(ctx)};
if (key_set.size() != keys_count) {
// It already has duplicates; bail out.
node.has_duplicate_keys = true;
return {};
}
// Merge the keys from the children into this set.
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) {
node.has_duplicate_keys = true;
return {};
}
}
node.has_duplicate_keys = false;
return key_set; return key_set;
}; };
return !TreeEvalMaybe<set>(upfn); TreeEval<state>(upfn);
} }
public:
//! Return the size of the script for this expression (faster than ToScript().size()). //! Return the size of the script for this expression (faster than ToScript().size()).
size_t ScriptSize() const { return scriptlen; } size_t ScriptSize() const { return scriptlen; }
@ -858,7 +893,7 @@ public:
bool CheckTimeLocksMix() const { return GetType() << "k"_mst; } bool CheckTimeLocksMix() const { return GetType() << "k"_mst; }
//! Check whether there is no duplicate key across this fragment and all its sub-fragments. //! Check whether there is no duplicate key across this fragment and all its sub-fragments.
bool CheckDuplicateKey() const { return !duplicate_key; } bool CheckDuplicateKey() const { return has_duplicate_keys && !*has_duplicate_keys; }
//! Whether successful non-malleable satisfactions are guaranteed to be valid. //! Whether successful non-malleable satisfactions are guaranteed to be valid.
bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); } bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); }
@ -872,13 +907,21 @@ public:
//! Equality testing. //! Equality testing.
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. // Constructors with various argument combinations, which bypass 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) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} Node(internal::NoDupCheck, 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()) {}
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)) {} Node(internal::NoDupCheck, 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()) {}
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)) {} Node(internal::NoDupCheck, 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()) {}
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)) {} Node(internal::NoDupCheck, 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()) {}
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)) {} Node(internal::NoDupCheck, 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()) {}
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)) {} Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
// 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) : Node(internal::NoDupCheck{}, 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) : Node(internal::NoDupCheck{}, 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) : Node(internal::NoDupCheck{}, 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) : Node(internal::NoDupCheck{}, 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) : Node(internal::NoDupCheck{}, nt, std::move(sub), val) { DuplicateKeyCheck(ctx); }
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, val) { DuplicateKeyCheck(ctx); }
}; };
namespace internal { namespace internal {
@ -965,15 +1008,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 */ /** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */
template<typename Key, typename Ctx> template<typename Key>
void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false)
{ {
NodeRef<Key> child = std::move(constructed.back()); NodeRef<Key> child = std::move(constructed.back());
constructed.pop_back(); constructed.pop_back();
if (reverse) { if (reverse) {
constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(child), std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(child), std::move(constructed.back())));
} else { } else {
constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(constructed.back()), std::move(child)));
} }
} }
@ -1030,7 +1073,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
to_parse.emplace_back(ParseContext::WRAP_T, -1, -1); to_parse.emplace_back(ParseContext::WRAP_T, -1, -1);
} 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)
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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 {};
@ -1042,56 +1085,56 @@ 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>(ctx, Fragment::JUST_0)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
} else if (Const("1", in)) { } else if (Const("1", in)) {
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key))))));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
} 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>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key))))));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
} 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>(ctx, Fragment::PK_K, Vector(std::move(key)))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key))));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
} 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>(ctx, Fragment::PK_H, Vector(std::move(key)))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key))));
in = in.subspan(key_size + 1); in = in.subspan(key_size + 1);
} 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>(ctx, Fragment::SHA256, std::move(hash))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move(hash)));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
} 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>(ctx, Fragment::RIPEMD160, std::move(hash))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move(hash)));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
} 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>(ctx, Fragment::HASH256, std::move(hash))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move(hash)));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
} 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>(ctx, Fragment::HASH160, std::move(hash))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move(hash)));
in = in.subspan(hash_size + 1); in = in.subspan(hash_size + 1);
} else if (Const("after(", in)) { } else if (Const("after(", in)) {
int arg_size = FindNextChar(in, ')'); int arg_size = FindNextChar(in, ')');
@ -1099,7 +1142,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
int64_t num; int64_t num;
if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
if (num < 1 || num >= 0x80000000L) return {}; if (num < 1 || num >= 0x80000000L) return {};
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, num)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num));
in = in.subspan(arg_size + 1); in = in.subspan(arg_size + 1);
} else if (Const("older(", in)) { } else if (Const("older(", in)) {
int arg_size = FindNextChar(in, ')'); int arg_size = FindNextChar(in, ')');
@ -1107,7 +1150,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
int64_t num; int64_t num;
if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
if (num < 1 || num >= 0x80000000L) return {}; if (num < 1 || num >= 0x80000000L) return {};
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, num)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num));
in = in.subspan(arg_size + 1); in = in.subspan(arg_size + 1);
} else if (Const("multi(", in)) { } else if (Const("multi(", in)) {
// Get threshold // Get threshold
@ -1128,7 +1171,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
} }
if (keys.size() < 1 || keys.size() > 20) return {}; if (keys.size() < 1 || keys.size() > 20) return {};
if (k < 1 || k > (int64_t)keys.size()) return {}; if (k < 1 || k > (int64_t)keys.size()) return {};
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), k)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), k));
} else if (Const("thresh(", in)) { } else if (Const("thresh(", in)) {
int next_comma = FindNextChar(in, ','); int next_comma = FindNextChar(in, ',');
if (next_comma < 1) return {}; if (next_comma < 1) return {};
@ -1172,69 +1215,69 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
break; break;
} }
case ParseContext::ALT: { case ParseContext::ALT: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::SWAP: { case ParseContext::SWAP: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::CHECK: { case ParseContext::CHECK: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::DUP_IF: { case ParseContext::DUP_IF: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::NON_ZERO: { case ParseContext::NON_ZERO: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::ZERO_NOTEQUAL: { case ParseContext::ZERO_NOTEQUAL: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::VERIFY: { case ParseContext::VERIFY: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back())));
break; break;
} }
case ParseContext::WRAP_U: { case ParseContext::WRAP_U: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)));
break; break;
} }
case ParseContext::WRAP_T: { case ParseContext::WRAP_T: {
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)));
break; break;
} }
case ParseContext::AND_B: { case ParseContext::AND_B: {
BuildBack(ctx, Fragment::AND_B, constructed); BuildBack(Fragment::AND_B, constructed);
break; break;
} }
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>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0)));
break; break;
} }
case ParseContext::AND_V: { case ParseContext::AND_V: {
BuildBack(ctx, Fragment::AND_V, constructed); BuildBack(Fragment::AND_V, constructed);
break; break;
} }
case ParseContext::OR_B: { case ParseContext::OR_B: {
BuildBack(ctx, Fragment::OR_B, constructed); BuildBack(Fragment::OR_B, constructed);
break; break;
} }
case ParseContext::OR_C: { case ParseContext::OR_C: {
BuildBack(ctx, Fragment::OR_C, constructed); BuildBack(Fragment::OR_C, constructed);
break; break;
} }
case ParseContext::OR_D: { case ParseContext::OR_D: {
BuildBack(ctx, Fragment::OR_D, constructed); BuildBack(Fragment::OR_D, constructed);
break; break;
} }
case ParseContext::OR_I: { case ParseContext::OR_I: {
BuildBack(ctx, Fragment::OR_I, constructed); BuildBack(Fragment::OR_I, constructed);
break; break;
} }
case ParseContext::ANDOR: { case ParseContext::ANDOR: {
@ -1242,7 +1285,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>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right)));
break; break;
} }
case ParseContext::THRESH: { case ParseContext::THRESH: {
@ -1261,7 +1304,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
constructed.pop_back(); constructed.pop_back();
} }
std::reverse(subs.begin(), subs.end()); std::reverse(subs.begin(), subs.end());
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k));
} else { } else {
return {}; return {};
} }
@ -1283,7 +1326,9 @@ 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);
if (in.size() > 0) return {}; if (in.size() > 0) return {};
return std::move(constructed.front()); const NodeRef<Key> tl_node = std::move(constructed.front());
tl_node->DuplicateKeyCheck(ctx);
return tl_node;
} }
/** Decode a script into opcode/push pairs. /** Decode a script into opcode/push pairs.
@ -1394,12 +1439,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>(ctx, Fragment::JUST_1)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1));
break; break;
} }
if (in[0].first == OP_0) { if (in[0].first == OP_0) {
++in; ++in;
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
break; break;
} }
// Public keys // Public keys
@ -1407,14 +1452,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>(ctx, Fragment::PK_K, Vector(std::move(*key)))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::PK_H, Vector(std::move(*key)))); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(*key))));
break; break;
} }
// Time locks // Time locks
@ -1422,31 +1467,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>(ctx, Fragment::OLDER, *num)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::AFTER, *num)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::SHA256, in[1].second)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::RIPEMD160, in[1].second)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::HASH256, in[1].second)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::HASH160, in[1].second)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, in[1].second));
in += 7; in += 7;
break; break;
} }
@ -1467,7 +1512,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>(ctx, Fragment::MULTI, std::move(keys), *k)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, 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
@ -1562,63 +1607,63 @@ 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>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, 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>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back())));
break; break;
} }
case DecodeContext::AND_V: { case DecodeContext::AND_V: {
if (constructed.size() < 2) return {}; if (constructed.size() < 2) return {};
BuildBack(ctx, Fragment::AND_V, constructed, /*reverse=*/true); BuildBack(Fragment::AND_V, constructed, /*reverse=*/true);
break; break;
} }
case DecodeContext::AND_B: { case DecodeContext::AND_B: {
if (constructed.size() < 2) return {}; if (constructed.size() < 2) return {};
BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true); BuildBack(Fragment::AND_B, constructed, /*reverse=*/true);
break; break;
} }
case DecodeContext::OR_B: { case DecodeContext::OR_B: {
if (constructed.size() < 2) return {}; if (constructed.size() < 2) return {};
BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true); BuildBack(Fragment::OR_B, constructed, /*reverse=*/true);
break; break;
} }
case DecodeContext::OR_C: { case DecodeContext::OR_C: {
if (constructed.size() < 2) return {}; if (constructed.size() < 2) return {};
BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true); BuildBack(Fragment::OR_C, constructed, /*reverse=*/true);
break; break;
} }
case DecodeContext::OR_D: { case DecodeContext::OR_D: {
if (constructed.size() < 2) return {}; if (constructed.size() < 2) return {};
BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true); BuildBack(Fragment::OR_D, constructed, /*reverse=*/true);
break; break;
} }
case DecodeContext::ANDOR: { case DecodeContext::ANDOR: {
@ -1628,7 +1673,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
NodeRef<Key> right = std::move(constructed.back()); NodeRef<Key> right = std::move(constructed.back());
constructed.pop_back(); constructed.pop_back();
NodeRef<Key> mid = std::move(constructed.back()); NodeRef<Key> mid = std::move(constructed.back());
constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right)));
break; break;
} }
case DecodeContext::THRESH_W: { case DecodeContext::THRESH_W: {
@ -1652,7 +1697,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
constructed.pop_back(); constructed.pop_back();
subs.push_back(std::move(sub)); subs.push_back(std::move(sub));
} }
constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k));
break; break;
} }
case DecodeContext::ENDIF: { case DecodeContext::ENDIF: {
@ -1702,7 +1747,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (in >= last) return {}; if (in >= last) return {};
if (in[0].first == OP_IF) { if (in[0].first == OP_IF) {
++in; ++in;
BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true); BuildBack(Fragment::OR_I, constructed, /*reverse=*/true);
} else if (in[0].first == OP_NOTIF) { } else if (in[0].first == OP_NOTIF) {
++in; ++in;
to_parse.emplace_back(DecodeContext::ANDOR, -1, -1); to_parse.emplace_back(DecodeContext::ANDOR, -1, -1);
@ -1717,6 +1762,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
} }
if (constructed.size() != 1) return {}; if (constructed.size() != 1) return {};
const NodeRef<Key> tl_node = std::move(constructed.front()); const NodeRef<Key> tl_node = std::move(constructed.front());
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 {};

View file

@ -296,6 +296,9 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
// Same when the duplicates are on different levels in the tree // 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); 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()); BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey());
// Sanity check the opposite is true, too. An otherwise sane Miniscript with no duplicate keys is sane.
const auto ms_nondup = miniscript::FromString("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", CONVERTER);
BOOST_CHECK(ms_nondup && ms_nondup->CheckDuplicateKey() && ms_nondup->IsSane());
// Test we find the first insane sub closer to be a leaf node. This fragment is insane for two reasons: // Test we find the first insane sub closer to be a leaf node. This fragment is insane for two reasons:
// 1. It can be spent without a signature // 1. It can be spent without a signature
// 2. It contains timelock mixes // 2. It contains timelock mixes