Skip to content

Commit f836999

Browse files
sipadarosior
andcommitted
Miniscript: ops limit and stack size computation
Co-Authored-By: Antoine Poinsot <[email protected]>
1 parent 2e55e88 commit f836999

File tree

2 files changed

+229
-41
lines changed

2 files changed

+229
-41
lines changed

src/script/miniscript.h

Lines changed: 190 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,47 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_
232232
//! A helper sanitizer/checker for the output of CalcType.
233233
Type SanitizeType(Type x);
234234

235+
//! Class whose objects represent the maximum of a list of integers.
236+
template<typename I>
237+
struct MaxInt {
238+
const bool valid;
239+
const I value;
240+
241+
MaxInt() : valid(false), value(0) {}
242+
MaxInt(I val) : valid(true), value(val) {}
243+
244+
friend MaxInt<I> operator+(const MaxInt<I>& a, const MaxInt<I>& b) {
245+
if (!a.valid || !b.valid) return {};
246+
return a.value + b.value;
247+
}
248+
249+
friend MaxInt<I> operator|(const MaxInt<I>& a, const MaxInt<I>& b) {
250+
if (!a.valid) return b;
251+
if (!b.valid) return a;
252+
return std::max(a.value, b.value);
253+
}
254+
};
255+
256+
struct Ops {
257+
//! Non-push opcodes.
258+
uint32_t count;
259+
//! Number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to satisfy.
260+
MaxInt<uint32_t> sat;
261+
//! Number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to dissatisfy.
262+
MaxInt<uint32_t> dsat;
263+
264+
Ops(uint32_t in_count, MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : count(in_count), sat(in_sat), dsat(in_dsat) {};
265+
};
266+
267+
struct StackSize {
268+
//! Maximum stack size to satisfy;
269+
MaxInt<uint32_t> sat;
270+
//! Maximum stack size to dissatisfy;
271+
MaxInt<uint32_t> dsat;
272+
273+
StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
274+
};
275+
235276
} // namespace internal
236277

237278
//! A node in a miniscript expression.
@@ -249,6 +290,10 @@ struct Node {
249290
const std::vector<NodeRef<Key>> subs;
250291

251292
private:
293+
//! Cached ops counts.
294+
const internal::Ops ops;
295+
//! Cached stack size bounds.
296+
const internal::StackSize ss;
252297
//! Cached expression type (computed by CalcType and fed through SanitizeType).
253298
const Type typ;
254299
//! Cached script length (computed by CalcScriptLen).
@@ -556,10 +601,148 @@ struct Node {
556601
return res.has_value();
557602
}
558603

604+
internal::Ops CalcOps() const {
605+
switch (nodetype) {
606+
case Fragment::JUST_1: return {0, 0, {}};
607+
case Fragment::JUST_0: return {0, {}, 0};
608+
case Fragment::PK_K: return {0, 0, 0};
609+
case Fragment::PK_H: return {3, 0, 0};
610+
case Fragment::OLDER:
611+
case Fragment::AFTER: return {1, 0, {}};
612+
case Fragment::SHA256:
613+
case Fragment::RIPEMD160:
614+
case Fragment::HASH256:
615+
case Fragment::HASH160: return {4, 0, {}};
616+
case Fragment::AND_V: return {subs[0]->ops.count + subs[1]->ops.count, subs[0]->ops.sat + subs[1]->ops.sat, {}};
617+
case Fragment::AND_B: {
618+
const auto count{1 + subs[0]->ops.count + subs[1]->ops.count};
619+
const auto sat{subs[0]->ops.sat + subs[1]->ops.sat};
620+
const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat};
621+
return {count, sat, dsat};
622+
}
623+
case Fragment::OR_B: {
624+
const auto count{1 + subs[0]->ops.count + subs[1]->ops.count};
625+
const auto sat{(subs[0]->ops.sat + subs[1]->ops.dsat) | (subs[1]->ops.sat + subs[0]->ops.dsat)};
626+
const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat};
627+
return {count, sat, dsat};
628+
}
629+
case Fragment::OR_D: {
630+
const auto count{3 + subs[0]->ops.count + subs[1]->ops.count};
631+
const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)};
632+
const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat};
633+
return {count, sat, dsat};
634+
}
635+
case Fragment::OR_C: {
636+
const auto count{2 + subs[0]->ops.count + subs[1]->ops.count};
637+
const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)};
638+
return {count, sat, {}};
639+
}
640+
case Fragment::OR_I: {
641+
const auto count{3 + subs[0]->ops.count + subs[1]->ops.count};
642+
const auto sat{subs[0]->ops.sat | subs[1]->ops.sat};
643+
const auto dsat{subs[0]->ops.dsat | subs[1]->ops.dsat};
644+
return {count, sat, dsat};
645+
}
646+
case Fragment::ANDOR: {
647+
const auto count{3 + subs[0]->ops.count + subs[1]->ops.count + subs[2]->ops.count};
648+
const auto sat{(subs[1]->ops.sat + subs[0]->ops.sat) | (subs[0]->ops.dsat + subs[2]->ops.sat)};
649+
const auto dsat{subs[0]->ops.dsat + subs[2]->ops.dsat};
650+
return {count, sat, dsat};
651+
}
652+
case Fragment::MULTI: return {1, (uint32_t)keys.size(), (uint32_t)keys.size()};
653+
case Fragment::WRAP_S:
654+
case Fragment::WRAP_C:
655+
case Fragment::WRAP_N: return {1 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat};
656+
case Fragment::WRAP_A: return {2 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat};
657+
case Fragment::WRAP_D: return {3 + subs[0]->ops.count, subs[0]->ops.sat, 0};
658+
case Fragment::WRAP_J: return {4 + subs[0]->ops.count, subs[0]->ops.sat, 0};
659+
case Fragment::WRAP_V: return {subs[0]->ops.count + (subs[0]->GetType() << "x"_mst), subs[0]->ops.sat, {}};
660+
case Fragment::THRESH: {
661+
uint32_t count = 0;
662+
auto sats = Vector(internal::MaxInt<uint32_t>(0));
663+
for (const auto& sub : subs) {
664+
count += sub->ops.count + 1;
665+
auto next_sats = Vector(sats[0] + sub->ops.dsat);
666+
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ops.dsat) | (sats[j - 1] + sub->ops.sat));
667+
next_sats.push_back(sats[sats.size() - 1] + sub->ops.sat);
668+
sats = std::move(next_sats);
669+
}
670+
assert(k <= sats.size());
671+
return {count, sats[k], sats[0]};
672+
}
673+
}
674+
assert(false);
675+
return {0, {}, {}};
676+
}
677+
678+
internal::StackSize CalcStackSize() const {
679+
switch (nodetype) {
680+
case Fragment::JUST_0: return {{}, 0};
681+
case Fragment::JUST_1:
682+
case Fragment::OLDER:
683+
case Fragment::AFTER: return {0, {}};
684+
case Fragment::PK_K: return {1, 1};
685+
case Fragment::PK_H: return {2, 2};
686+
case Fragment::SHA256:
687+
case Fragment::RIPEMD160:
688+
case Fragment::HASH256:
689+
case Fragment::HASH160: return {1, {}};
690+
case Fragment::ANDOR: {
691+
const auto sat{(subs[0]->ss.sat + subs[1]->ss.sat) | (subs[0]->ss.dsat + subs[2]->ss.sat)};
692+
const auto dsat{subs[0]->ss.dsat + subs[2]->ss.dsat};
693+
return {sat, dsat};
694+
}
695+
case Fragment::AND_V: return {subs[0]->ss.sat + subs[1]->ss.sat, {}};
696+
case Fragment::AND_B: return {subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.dsat};
697+
case Fragment::OR_B: {
698+
const auto sat{(subs[0]->ss.dsat + subs[1]->ss.sat) | (subs[0]->ss.sat + subs[1]->ss.dsat)};
699+
const auto dsat{subs[0]->ss.dsat + subs[1]->ss.dsat};
700+
return {sat, dsat};
701+
}
702+
case Fragment::OR_C: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), {}};
703+
case Fragment::OR_D: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), subs[0]->ss.dsat + subs[1]->ss.dsat};
704+
case Fragment::OR_I: return {(subs[0]->ss.sat + 1) | (subs[1]->ss.sat + 1), (subs[0]->ss.dsat + 1) | (subs[1]->ss.dsat + 1)};
705+
case Fragment::MULTI: return {k + 1, k + 1};
706+
case Fragment::WRAP_A:
707+
case Fragment::WRAP_N:
708+
case Fragment::WRAP_S:
709+
case Fragment::WRAP_C: return subs[0]->ss;
710+
case Fragment::WRAP_D: return {1 + subs[0]->ss.sat, 1};
711+
case Fragment::WRAP_V: return {subs[0]->ss.sat, {}};
712+
case Fragment::WRAP_J: return {subs[0]->ss.sat, 1};
713+
case Fragment::THRESH: {
714+
auto sats = Vector(internal::MaxInt<uint32_t>(0));
715+
for (const auto& sub : subs) {
716+
auto next_sats = Vector(sats[0] + sub->ss.dsat);
717+
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ss.dsat) | (sats[j - 1] + sub->ss.sat));
718+
next_sats.push_back(sats[sats.size() - 1] + sub->ss.sat);
719+
sats = std::move(next_sats);
720+
}
721+
assert(k <= sats.size());
722+
return {sats[k], sats[0]};
723+
}
724+
}
725+
assert(false);
726+
return {{}, {}};
727+
}
728+
559729
public:
560730
//! Return the size of the script for this expression (faster than ToScript().size()).
561731
size_t ScriptSize() const { return scriptlen; }
562732

733+
//! Return the maximum number of ops needed to satisfy this script non-malleably.
734+
uint32_t GetOps() const { return ops.count + ops.sat.value; }
735+
736+
//! Check the ops limit of this script against the consensus limit.
737+
bool CheckOpsLimit() const { return GetOps() <= MAX_OPS_PER_SCRIPT; }
738+
739+
/** Return the maximum number of stack elements needed to satisfy this script non-malleably, including
740+
* the script push. */
741+
uint32_t GetStackSize() const { return ss.sat.value + 1; }
742+
743+
//! Check the maximum stack size for this script against the policy limit.
744+
bool CheckStackSize() const { return GetStackSize() - 1 <= MAX_STANDARD_P2WSH_STACK_ITEMS; }
745+
563746
//! Return the expression type.
564747
Type GetType() const { return typ; }
565748

@@ -576,7 +759,7 @@ struct Node {
576759
bool NeedsSignature() const { return GetType() << "s"_mst; }
577760

578761
//! Do all sanity checks.
579-
bool IsSane() const { return IsValid() && GetType() << "mk"_mst; }
762+
bool IsSane() const { return IsValid() && GetType() << "mk"_mst && CheckOpsLimit() && CheckStackSize(); }
580763

581764
//! Check whether this node is safe as a script on its own.
582765
bool IsSaneTopLevel() const { return IsValidTopLevel() && IsSane() && NeedsSignature(); }
@@ -598,12 +781,12 @@ struct Node {
598781
}
599782

600783
// Constructors with various argument combinations.
601-
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)), typ(CalcType()), scriptlen(CalcScriptLen()) {}
602-
Node(Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), typ(CalcType()), scriptlen(CalcScriptLen()) {}
603-
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)), typ(CalcType()), scriptlen(CalcScriptLen()) {}
604-
Node(Fragment nt, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), typ(CalcType()), scriptlen(CalcScriptLen()) {}
605-
Node(Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : nodetype(nt), k(val), subs(std::move(sub)), typ(CalcType()), scriptlen(CalcScriptLen()) {}
606-
Node(Fragment nt, uint32_t val = 0) : nodetype(nt), k(val), typ(CalcType()), scriptlen(CalcScriptLen()) {}
784+
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()) {}
785+
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()) {}
786+
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()) {}
787+
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()) {}
788+
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()) {}
789+
Node(Fragment nt, uint32_t val = 0) : nodetype(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
607790
};
608791

609792
namespace internal {

0 commit comments

Comments
 (0)