Skip to content

Commit 770ba5b

Browse files
darosiorsipa
andcommitted
miniscript: check maximum stack size during execution
Under Tapscript, due to the lifting of some standardness and consensus limits, scripts can now run into the maximum stack size during execution. Any Miniscript that may hit the limit on any of its spending paths must be marked as unsafe. Co-Authored-By: Pieter Wuille <[email protected]>
1 parent 574523d commit 770ba5b

File tree

1 file changed

+216
-38
lines changed

1 file changed

+216
-38
lines changed

src/script/miniscript.h

Lines changed: 216 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -373,13 +373,112 @@ struct Ops {
373373
Ops(uint32_t in_count, MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : count(in_count), sat(in_sat), dsat(in_dsat) {};
374374
};
375375

376+
/** A data structure to help the calculation of stack size limits.
377+
*
378+
* Conceptually, every SatInfo object corresponds to a (possibly empty) set of script execution
379+
* traces (sequences of opcodes).
380+
* - SatInfo{} corresponds to the empty set.
381+
* - SatInfo{n, e} corresponds to a single trace whose net effect is removing n elements from the
382+
* stack (may be negative for a net increase), and reaches a maximum of e stack elements more
383+
* than it ends with.
384+
* - operator| is the union operation: (a | b) corresponds to the union of the traces in a and the
385+
* traces in b.
386+
* - operator+ is the concatenation operator: (a + b) corresponds to the set of traces formed by
387+
* concatenating any trace in a with any trace in b.
388+
*
389+
* Its fields are:
390+
* - valid is true if the set is non-empty.
391+
* - netdiff (if valid) is the largest difference between stack size at the beginning and at the
392+
* end of the script across all traces in the set.
393+
* - exec (if valid) is the largest difference between stack size anywhere during execution and at
394+
* the end of the script, across all traces in the set (note that this is not necessarily due
395+
* to the same trace as the one that resulted in the value for netdiff).
396+
*
397+
* This allows us to build up stack size limits for any script efficiently, by starting from the
398+
* individual opcodes miniscripts correspond to, using concatenation to construct scripts, and
399+
* using the union operation to choose between execution branches. Since any top-level script
400+
* satisfaction ends with a single stack element, we know that for a full script:
401+
* - netdiff+1 is the maximal initial stack size (relevant for P2WSH stack limits).
402+
* - exec+1 is the maximal stack size reached during execution (relevant for P2TR stack limits).
403+
*
404+
* Mathematically, SatInfo forms a semiring:
405+
* - operator| is the semiring addition operator, with identity SatInfo{}, and which is commutative
406+
* and associative.
407+
* - operator+ is the semiring multiplication operator, with identity SatInfo{0}, and which is
408+
* associative.
409+
* - operator+ is distributive over operator|, so (a + (b | c)) = (a+b | a+c). This means we do not
410+
* need to actually materialize all possible full execution traces over the whole script (which
411+
* may be exponential in the length of the script); instead we can use the union operation at the
412+
* individual subexpression level, and concatenate the result with subexpressions before and
413+
* after it.
414+
* - It is not a commutative semiring, because a+b can differ from b+a. For example, "OP_1 OP_DROP"
415+
* has exec=1, while "OP_DROP OP_1" has exec=0.
416+
*/
417+
struct SatInfo {
418+
//! Whether a canonical satisfaction/dissatisfaction is possible at all.
419+
const bool valid;
420+
//! How much higher the stack size at start of execution can be compared to at the end.
421+
const int32_t netdiff;
422+
//! Mow much higher the stack size can be during execution compared to at the end.
423+
const int32_t exec;
424+
425+
/** Empty script set. */
426+
constexpr SatInfo() noexcept : valid(false), netdiff(0), exec(0) {}
427+
428+
/** Script set with a single script in it, with specified netdiff and exec. */
429+
constexpr SatInfo(int32_t in_netdiff, int32_t in_exec) noexcept :
430+
valid{true}, netdiff{in_netdiff}, exec{in_exec} {}
431+
432+
/** Script set union. */
433+
constexpr friend SatInfo operator|(const SatInfo& a, const SatInfo& b) noexcept
434+
{
435+
// Union with an empty set is itself.
436+
if (!a.valid) return b;
437+
if (!b.valid) return a;
438+
// Otherwise the netdiff and exec of the union is the maximum of the individual values.
439+
return {std::max(a.netdiff, b.netdiff), std::max(a.exec, b.exec)};
440+
}
441+
442+
/** Script set concatenation. */
443+
constexpr friend SatInfo operator+(const SatInfo& a, const SatInfo& b) noexcept
444+
{
445+
// Concatenation with an empty set yields an empty set.
446+
if (!a.valid || !b.valid) return {};
447+
// Otherwise, the maximum stack size difference for the combined scripts is the sum of the
448+
// netdiffs, and the maximum stack size difference anywhere is either b.exec (if the
449+
// maximum occurred in b) or b.netdiff+a.exec (if the maximum occurred in a).
450+
return {a.netdiff + b.netdiff, std::max(b.exec, b.netdiff + a.exec)};
451+
}
452+
453+
/** The empty script. */
454+
static constexpr SatInfo Empty() noexcept { return {0, 0}; }
455+
/** A script consisting of a single push opcode. */
456+
static constexpr SatInfo Push() noexcept { return {-1, 0}; }
457+
/** A script consisting of a single hash opcode. */
458+
static constexpr SatInfo Hash() noexcept { return {0, 0}; }
459+
/** A script consisting of just a repurposed nop (OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY). */
460+
static constexpr SatInfo Nop() noexcept { return {0, 0}; }
461+
/** A script consisting of just OP_IF or OP_NOTIF. Note that OP_ELSE and OP_ENDIF have no stack effect. */
462+
static constexpr SatInfo If() noexcept { return {1, 1}; }
463+
/** A script consisting of just a binary operator (OP_BOOLAND, OP_BOOLOR, OP_ADD). */
464+
static constexpr SatInfo BinaryOp() noexcept { return {1, 1}; }
465+
466+
// Scripts for specific individual opcodes.
467+
static constexpr SatInfo OP_DUP() noexcept { return {-1, 0}; }
468+
static constexpr SatInfo OP_IFDUP(bool nonzero) noexcept { return {nonzero ? -1 : 0, 0}; }
469+
static constexpr SatInfo OP_EQUALVERIFY() noexcept { return {2, 2}; }
470+
static constexpr SatInfo OP_EQUAL() noexcept { return {1, 1}; }
471+
static constexpr SatInfo OP_SIZE() noexcept { return {-1, 0}; }
472+
static constexpr SatInfo OP_CHECKSIG() noexcept { return {1, 1}; }
473+
static constexpr SatInfo OP_0NOTEQUAL() noexcept { return {0, 0}; }
474+
static constexpr SatInfo OP_VERIFY() noexcept { return {1, 1}; }
475+
};
476+
376477
struct StackSize {
377-
//! Maximum stack size to satisfy;
378-
MaxInt<uint32_t> sat;
379-
//! Maximum stack size to dissatisfy;
380-
MaxInt<uint32_t> dsat;
478+
const SatInfo sat, dsat;
381479

382-
StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
480+
constexpr StackSize(SatInfo in_sat, SatInfo in_dsat) noexcept : sat(in_sat), dsat(in_dsat) {};
481+
constexpr StackSize(SatInfo in_both) noexcept : sat(in_both), dsat(in_both) {};
383482
};
384483

385484
struct WitnessSize {
@@ -878,51 +977,115 @@ struct Node {
878977
}
879978

880979
internal::StackSize CalcStackSize() const {
980+
using namespace internal;
881981
switch (fragment) {
882-
case Fragment::JUST_0: return {{}, 0};
883-
case Fragment::JUST_1:
982+
case Fragment::JUST_0: return {{}, SatInfo::Push()};
983+
case Fragment::JUST_1: return {SatInfo::Push(), {}};
884984
case Fragment::OLDER:
885-
case Fragment::AFTER: return {0, {}};
886-
case Fragment::PK_K: return {0, 0};
887-
case Fragment::PK_H: return {1, 1};
985+
case Fragment::AFTER: return {SatInfo::Push() + SatInfo::Nop(), {}};
986+
case Fragment::PK_K: return {SatInfo::Push()};
987+
case Fragment::PK_H: return {SatInfo::OP_DUP() + SatInfo::Hash() + SatInfo::Push() + SatInfo::OP_EQUALVERIFY()};
888988
case Fragment::SHA256:
889989
case Fragment::RIPEMD160:
890990
case Fragment::HASH256:
891-
case Fragment::HASH160: return {1, {}};
991+
case Fragment::HASH160: return {
992+
SatInfo::OP_SIZE() + SatInfo::Push() + SatInfo::OP_EQUALVERIFY() + SatInfo::Hash() + SatInfo::Push() + SatInfo::OP_EQUAL(),
993+
{}
994+
};
892995
case Fragment::ANDOR: {
893-
const auto sat{(subs[0]->ss.sat + subs[1]->ss.sat) | (subs[0]->ss.dsat + subs[2]->ss.sat)};
894-
const auto dsat{subs[0]->ss.dsat + subs[2]->ss.dsat};
895-
return {sat, dsat};
996+
const auto& x{subs[0]->ss};
997+
const auto& y{subs[1]->ss};
998+
const auto& z{subs[2]->ss};
999+
return {
1000+
(x.sat + SatInfo::If() + y.sat) | (x.dsat + SatInfo::If() + z.sat),
1001+
x.dsat + SatInfo::If() + z.dsat
1002+
};
1003+
}
1004+
case Fragment::AND_V: {
1005+
const auto& x{subs[0]->ss};
1006+
const auto& y{subs[1]->ss};
1007+
return {x.sat + y.sat, {}};
1008+
}
1009+
case Fragment::AND_B: {
1010+
const auto& x{subs[0]->ss};
1011+
const auto& y{subs[1]->ss};
1012+
return {x.sat + y.sat + SatInfo::BinaryOp(), x.dsat + y.dsat + SatInfo::BinaryOp()};
8961013
}
897-
case Fragment::AND_V: return {subs[0]->ss.sat + subs[1]->ss.sat, {}};
898-
case Fragment::AND_B: return {subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.dsat};
8991014
case Fragment::OR_B: {
900-
const auto sat{(subs[0]->ss.dsat + subs[1]->ss.sat) | (subs[0]->ss.sat + subs[1]->ss.dsat)};
901-
const auto dsat{subs[0]->ss.dsat + subs[1]->ss.dsat};
902-
return {sat, dsat};
1015+
const auto& x{subs[0]->ss};
1016+
const auto& y{subs[1]->ss};
1017+
return {
1018+
((x.sat + y.dsat) | (x.dsat + y.sat)) + SatInfo::BinaryOp(),
1019+
x.dsat + y.dsat + SatInfo::BinaryOp()
1020+
};
1021+
}
1022+
case Fragment::OR_C: {
1023+
const auto& x{subs[0]->ss};
1024+
const auto& y{subs[1]->ss};
1025+
return {(x.sat + SatInfo::If()) | (x.dsat + SatInfo::If() + y.sat), {}};
1026+
}
1027+
case Fragment::OR_D: {
1028+
const auto& x{subs[0]->ss};
1029+
const auto& y{subs[1]->ss};
1030+
return {
1031+
(x.sat + SatInfo::OP_IFDUP(true) + SatInfo::If()) | (x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.sat),
1032+
x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.dsat
1033+
};
1034+
}
1035+
case Fragment::OR_I: {
1036+
const auto& x{subs[0]->ss};
1037+
const auto& y{subs[1]->ss};
1038+
return {SatInfo::If() + (x.sat | y.sat), SatInfo::If() + (x.dsat | y.dsat)};
9031039
}
904-
case Fragment::OR_C: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), {}};
905-
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};
906-
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)};
907-
case Fragment::MULTI: return {k + 1, k + 1};
908-
case Fragment::MULTI_A: return {keys.size(), keys.size()};
1040+
// multi(k, key1, key2, ..., key_n) starts off with k+1 stack elements (a 0, plus k
1041+
// signatures), then reaches n+k+3 stack elements after pushing the n keys, plus k and
1042+
// n itself, and ends with 1 stack element (success or failure). Thus, it net removes
1043+
// k elements (from k+1 to 1), while reaching k+n+2 more than it ends with.
1044+
case Fragment::MULTI: return {SatInfo(k, k + keys.size() + 2)};
1045+
// multi_a(k, key1, key2, ..., key_n) starts off with n stack elements (the
1046+
// signatures), reaches 1 more (after the first key push), and ends with 1. Thus it net
1047+
// removes n-1 elements (from n to 1) while reaching n more than it ends with.
1048+
case Fragment::MULTI_A: return {SatInfo(keys.size() - 1, keys.size())};
9091049
case Fragment::WRAP_A:
9101050
case Fragment::WRAP_N:
9111051
case Fragment::WRAP_S: return subs[0]->ss;
912-
case Fragment::WRAP_C: return {subs[0]->ss.sat + 1, subs[0]->ss.dsat + 1};
913-
case Fragment::WRAP_D: return {1 + subs[0]->ss.sat, 1};
914-
case Fragment::WRAP_V: return {subs[0]->ss.sat, {}};
915-
case Fragment::WRAP_J: return {subs[0]->ss.sat, 1};
1052+
case Fragment::WRAP_C: return {
1053+
subs[0]->ss.sat + SatInfo::OP_CHECKSIG(),
1054+
subs[0]->ss.dsat + SatInfo::OP_CHECKSIG()
1055+
};
1056+
case Fragment::WRAP_D: return {
1057+
SatInfo::OP_DUP() + SatInfo::If() + subs[0]->ss.sat,
1058+
SatInfo::OP_DUP() + SatInfo::If()
1059+
};
1060+
case Fragment::WRAP_V: return {subs[0]->ss.sat + SatInfo::OP_VERIFY(), {}};
1061+
case Fragment::WRAP_J: return {
1062+
SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If() + subs[0]->ss.sat,
1063+
SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If()
1064+
};
9161065
case Fragment::THRESH: {
917-
auto sats = Vector(internal::MaxInt<uint32_t>(0));
918-
for (const auto& sub : subs) {
919-
auto next_sats = Vector(sats[0] + sub->ss.dsat);
920-
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ss.dsat) | (sats[j - 1] + sub->ss.sat));
921-
next_sats.push_back(sats[sats.size() - 1] + sub->ss.sat);
1066+
// sats[j] is the SatInfo corresponding to all traces reaching j satisfactions.
1067+
auto sats = Vector(SatInfo::Empty());
1068+
for (size_t i = 0; i < subs.size(); ++i) {
1069+
// Loop over the subexpressions, processing them one by one. After adding
1070+
// element i we need to add OP_ADD (if i>0).
1071+
auto add = i ? SatInfo::BinaryOp() : SatInfo::Empty();
1072+
// Construct a variable that will become the next sats, starting with index 0.
1073+
auto next_sats = Vector(sats[0] + subs[i]->ss.dsat + add);
1074+
// Then loop to construct next_sats[1..i].
1075+
for (size_t j = 1; j < sats.size(); ++j) {
1076+
next_sats.push_back(((sats[j] + subs[i]->ss.dsat) | (sats[j - 1] + subs[i]->ss.sat)) + add);
1077+
}
1078+
// Finally construct next_sats[i+1].
1079+
next_sats.push_back(sats[sats.size() - 1] + subs[i]->ss.sat + add);
1080+
// Switch over.
9221081
sats = std::move(next_sats);
9231082
}
924-
assert(k <= sats.size());
925-
return {sats[k], sats[0]};
1083+
// To satisfy thresh we need k satisfactions; to dissatisfy we need 0. In both
1084+
// cases a push of k and an OP_EQUAL follow.
1085+
return {
1086+
sats[k] + SatInfo::Push() + SatInfo::OP_EQUAL(),
1087+
sats[0] + SatInfo::Push() + SatInfo::OP_EQUAL()
1088+
};
9261089
}
9271090
}
9281091
assert(false);
@@ -1310,17 +1473,32 @@ struct Node {
13101473
return true;
13111474
}
13121475

1476+
/** Whether this node is of type B, K or W. (That is, anything but V.) */
1477+
bool IsBKW() const {
1478+
return !((GetType() & "BKW"_mst) == ""_mst);
1479+
}
1480+
13131481
/** Return the maximum number of stack elements needed to satisfy this script non-malleably.
13141482
* This does not account for the P2WSH script push. */
13151483
std::optional<uint32_t> GetStackSize() const {
13161484
if (!ss.sat.valid) return {};
1317-
return ss.sat.value;
1485+
return ss.sat.netdiff + static_cast<int32_t>(IsBKW());
1486+
}
1487+
1488+
//! Return the maximum size of the stack during execution of this script.
1489+
std::optional<uint32_t> GetExecStackSize() const {
1490+
if (!ss.sat.valid) return {};
1491+
return ss.sat.exec + static_cast<int32_t>(IsBKW());
13181492
}
13191493

13201494
//! Check the maximum stack size for this script against the policy limit.
13211495
bool CheckStackSize() const {
1322-
// TODO: MAX_STACK_SIZE during script execution under Tapscript.
1323-
if (IsTapscript(m_script_ctx)) return true;
1496+
// Since in Tapscript there is no standardness limit on the script and witness sizes, we may run
1497+
// into the maximum stack size while executing the script. Make sure it doesn't happen.
1498+
if (IsTapscript(m_script_ctx)) {
1499+
if (const auto exec_ss = GetExecStackSize()) return exec_ss <= MAX_STACK_SIZE;
1500+
return true;
1501+
}
13241502
if (const auto ss = GetStackSize()) return *ss <= MAX_STANDARD_P2WSH_STACK_ITEMS;
13251503
return true;
13261504
}

0 commit comments

Comments
 (0)