Skip to content

Commit e8cc2e4

Browse files
sipadarosior
andcommitted
Make miniscript string parsing account for exact script size as bound
Co-Authored-by: Antoine Poinsot <[email protected]>
1 parent 4cb8f9a commit e8cc2e4

File tree

1 file changed

+58
-4
lines changed

1 file changed

+58
-4
lines changed

src/script/miniscript.h

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,57 +1030,86 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
10301030
{
10311031
using namespace spanparsing;
10321032

1033+
// Account for the minimum script size for all parsed fragments so far. It "borrows" 1
1034+
// script byte from all leaf nodes, counting it instead whenever a space for a recursive
1035+
// expression is added (through andor, and_*, or_*, thresh). This guarantees that all fragments
1036+
// increment the script_size by at least one, except for:
1037+
// - "0", "1": these leafs are only a single byte, so their subtracted-from increment is 0.
1038+
// This is not an issue however, as "space" for them has to be created by combinators,
1039+
// which do increment script_size.
1040+
// - "v:": the v wrapper adds nothing as in some cases it results in no opcode being added
1041+
// (instead transforming another opcode into its VERIFY form). However, the v: wrapper has
1042+
// to be interleaved with other fragments to be valid, so this is not a concern.
1043+
size_t script_size{1};
1044+
10331045
// The two integers are used to hold state for thresh()
10341046
std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse;
10351047
std::vector<NodeRef<Key>> constructed;
10361048

10371049
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
10381050

10391051
while (!to_parse.empty()) {
1052+
if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
1053+
10401054
// Get the current context we are decoding within
10411055
auto [cur_context, n, k] = to_parse.back();
10421056
to_parse.pop_back();
10431057

10441058
switch (cur_context) {
10451059
case ParseContext::WRAPPED_EXPR: {
1046-
int colon_index = -1;
1047-
for (int i = 1; i < (int)in.size(); ++i) {
1060+
std::optional<size_t> colon_index{};
1061+
for (size_t i = 1; i < in.size(); ++i) {
10481062
if (in[i] == ':') {
10491063
colon_index = i;
10501064
break;
10511065
}
10521066
if (in[i] < 'a' || in[i] > 'z') break;
10531067
}
10541068
// If there is no colon, this loop won't execute
1055-
for (int j = 0; j < colon_index; ++j) {
1069+
bool last_was_v{false};
1070+
for (size_t j = 0; colon_index && j < *colon_index; ++j) {
1071+
if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
10561072
if (in[j] == 'a') {
1073+
script_size += 2;
10571074
to_parse.emplace_back(ParseContext::ALT, -1, -1);
10581075
} else if (in[j] == 's') {
1076+
script_size += 1;
10591077
to_parse.emplace_back(ParseContext::SWAP, -1, -1);
10601078
} else if (in[j] == 'c') {
1079+
script_size += 1;
10611080
to_parse.emplace_back(ParseContext::CHECK, -1, -1);
10621081
} else if (in[j] == 'd') {
1082+
script_size += 3;
10631083
to_parse.emplace_back(ParseContext::DUP_IF, -1, -1);
10641084
} else if (in[j] == 'j') {
1085+
script_size += 4;
10651086
to_parse.emplace_back(ParseContext::NON_ZERO, -1, -1);
10661087
} else if (in[j] == 'n') {
1088+
script_size += 1;
10671089
to_parse.emplace_back(ParseContext::ZERO_NOTEQUAL, -1, -1);
10681090
} else if (in[j] == 'v') {
1091+
// do not permit "...vv...:"; it's not valid, and also doesn't trigger early
1092+
// failure as script_size isn't incremented.
1093+
if (last_was_v) return {};
10691094
to_parse.emplace_back(ParseContext::VERIFY, -1, -1);
10701095
} else if (in[j] == 'u') {
1096+
script_size += 4;
10711097
to_parse.emplace_back(ParseContext::WRAP_U, -1, -1);
10721098
} else if (in[j] == 't') {
1099+
script_size += 1;
10731100
to_parse.emplace_back(ParseContext::WRAP_T, -1, -1);
10741101
} else if (in[j] == 'l') {
10751102
// The l: wrapper is equivalent to or_i(0,X)
1103+
script_size += 4;
10761104
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
10771105
to_parse.emplace_back(ParseContext::OR_I, -1, -1);
10781106
} else {
10791107
return {};
10801108
}
1109+
last_was_v = (in[j] == 'v');
10811110
}
10821111
to_parse.emplace_back(ParseContext::EXPR, -1, -1);
1083-
in = in.subspan(colon_index + 1);
1112+
if (colon_index) in = in.subspan(*colon_index + 1);
10841113
break;
10851114
}
10861115
case ParseContext::EXPR: {
@@ -1094,48 +1123,56 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
10941123
auto& [key, key_size] = *res;
10951124
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key))))));
10961125
in = in.subspan(key_size + 1);
1126+
script_size += 34;
10971127
} else if (Const("pkh(", in)) {
10981128
auto res = ParseKeyEnd<Key>(in, ctx);
10991129
if (!res) return {};
11001130
auto& [key, key_size] = *res;
11011131
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key))))));
11021132
in = in.subspan(key_size + 1);
1133+
script_size += 24;
11031134
} else if (Const("pk_k(", in)) {
11041135
auto res = ParseKeyEnd<Key>(in, ctx);
11051136
if (!res) return {};
11061137
auto& [key, key_size] = *res;
11071138
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key))));
11081139
in = in.subspan(key_size + 1);
1140+
script_size += 33;
11091141
} else if (Const("pk_h(", in)) {
11101142
auto res = ParseKeyEnd<Key>(in, ctx);
11111143
if (!res) return {};
11121144
auto& [key, key_size] = *res;
11131145
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key))));
11141146
in = in.subspan(key_size + 1);
1147+
script_size += 23;
11151148
} else if (Const("sha256(", in)) {
11161149
auto res = ParseHexStrEnd(in, 32, ctx);
11171150
if (!res) return {};
11181151
auto& [hash, hash_size] = *res;
11191152
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move(hash)));
11201153
in = in.subspan(hash_size + 1);
1154+
script_size += 38;
11211155
} else if (Const("ripemd160(", in)) {
11221156
auto res = ParseHexStrEnd(in, 20, ctx);
11231157
if (!res) return {};
11241158
auto& [hash, hash_size] = *res;
11251159
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move(hash)));
11261160
in = in.subspan(hash_size + 1);
1161+
script_size += 26;
11271162
} else if (Const("hash256(", in)) {
11281163
auto res = ParseHexStrEnd(in, 32, ctx);
11291164
if (!res) return {};
11301165
auto& [hash, hash_size] = *res;
11311166
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move(hash)));
11321167
in = in.subspan(hash_size + 1);
1168+
script_size += 38;
11331169
} else if (Const("hash160(", in)) {
11341170
auto res = ParseHexStrEnd(in, 20, ctx);
11351171
if (!res) return {};
11361172
auto& [hash, hash_size] = *res;
11371173
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move(hash)));
11381174
in = in.subspan(hash_size + 1);
1175+
script_size += 26;
11391176
} else if (Const("after(", in)) {
11401177
int arg_size = FindNextChar(in, ')');
11411178
if (arg_size < 1) return {};
@@ -1144,6 +1181,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
11441181
if (num < 1 || num >= 0x80000000L) return {};
11451182
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num));
11461183
in = in.subspan(arg_size + 1);
1184+
script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff);
11471185
} else if (Const("older(", in)) {
11481186
int arg_size = FindNextChar(in, ')');
11491187
if (arg_size < 1) return {};
@@ -1152,6 +1190,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
11521190
if (num < 1 || num >= 0x80000000L) return {};
11531191
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num));
11541192
in = in.subspan(arg_size + 1);
1193+
script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff);
11551194
} else if (Const("multi(", in)) {
11561195
// Get threshold
11571196
int next_comma = FindNextChar(in, ',');
@@ -1171,6 +1210,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
11711210
}
11721211
if (keys.size() < 1 || keys.size() > 20) return {};
11731212
if (k < 1 || k > (int64_t)keys.size()) return {};
1213+
script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size();
11741214
constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), k));
11751215
} else if (Const("thresh(", in)) {
11761216
int next_comma = FindNextChar(in, ',');
@@ -1181,6 +1221,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
11811221
// n = 1 here because we read the first WRAPPED_EXPR before reaching THRESH
11821222
to_parse.emplace_back(ParseContext::THRESH, 1, k);
11831223
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
1224+
script_size += 2 + (k > 16);
11841225
} else if (Const("andor(", in)) {
11851226
to_parse.emplace_back(ParseContext::ANDOR, -1, -1);
11861227
to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1);
@@ -1189,21 +1230,29 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
11891230
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
11901231
to_parse.emplace_back(ParseContext::COMMA, -1, -1);
11911232
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
1233+
script_size += 5;
11921234
} else {
11931235
if (Const("and_n(", in)) {
11941236
to_parse.emplace_back(ParseContext::AND_N, -1, -1);
1237+
script_size += 5;
11951238
} else if (Const("and_b(", in)) {
11961239
to_parse.emplace_back(ParseContext::AND_B, -1, -1);
1240+
script_size += 2;
11971241
} else if (Const("and_v(", in)) {
11981242
to_parse.emplace_back(ParseContext::AND_V, -1, -1);
1243+
script_size += 1;
11991244
} else if (Const("or_b(", in)) {
12001245
to_parse.emplace_back(ParseContext::OR_B, -1, -1);
1246+
script_size += 2;
12011247
} else if (Const("or_c(", in)) {
12021248
to_parse.emplace_back(ParseContext::OR_C, -1, -1);
1249+
script_size += 3;
12031250
} else if (Const("or_d(", in)) {
12041251
to_parse.emplace_back(ParseContext::OR_D, -1, -1);
1252+
script_size += 4;
12051253
} else if (Const("or_i(", in)) {
12061254
to_parse.emplace_back(ParseContext::OR_I, -1, -1);
1255+
script_size += 4;
12071256
} else {
12081257
return {};
12091258
}
@@ -1239,6 +1288,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
12391288
break;
12401289
}
12411290
case ParseContext::VERIFY: {
1291+
script_size += (constructed.back()->GetType() << "x"_mst);
12421292
constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back())));
12431293
break;
12441294
}
@@ -1294,6 +1344,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
12941344
in = in.subspan(1);
12951345
to_parse.emplace_back(ParseContext::THRESH, n+1, k);
12961346
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
1347+
script_size += 2;
12971348
} else if (in[0] == ')') {
12981349
if (k > n) return {};
12991350
in = in.subspan(1);
@@ -1325,6 +1376,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
13251376

13261377
// Sanity checks on the produced miniscript
13271378
assert(constructed.size() == 1);
1379+
assert(constructed[0]->ScriptSize() == script_size);
13281380
if (in.size() > 0) return {};
13291381
const NodeRef<Key> tl_node = std::move(constructed.front());
13301382
tl_node->DuplicateKeyCheck(ctx);
@@ -1779,6 +1831,8 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx&
17791831
template<typename Ctx>
17801832
inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) {
17811833
using namespace internal;
1834+
// A too large Script is necessarily invalid, don't bother parsing it.
1835+
if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
17821836
auto decomposed = DecomposeScript(script);
17831837
if (!decomposed) return {};
17841838
auto it = decomposed->begin();

0 commit comments

Comments
 (0)