@@ -1030,57 +1030,86 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1030
1030
{
1031
1031
using namespace spanparsing ;
1032
1032
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
+
1033
1045
// The two integers are used to hold state for thresh()
1034
1046
std::vector<std::tuple<ParseContext, int64_t , int64_t >> to_parse;
1035
1047
std::vector<NodeRef<Key>> constructed;
1036
1048
1037
1049
to_parse.emplace_back (ParseContext::WRAPPED_EXPR, -1 , -1 );
1038
1050
1039
1051
while (!to_parse.empty ()) {
1052
+ if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
1053
+
1040
1054
// Get the current context we are decoding within
1041
1055
auto [cur_context, n, k] = to_parse.back ();
1042
1056
to_parse.pop_back ();
1043
1057
1044
1058
switch (cur_context) {
1045
1059
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) {
1048
1062
if (in[i] == ' :' ) {
1049
1063
colon_index = i;
1050
1064
break ;
1051
1065
}
1052
1066
if (in[i] < ' a' || in[i] > ' z' ) break ;
1053
1067
}
1054
1068
// 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 {};
1056
1072
if (in[j] == ' a' ) {
1073
+ script_size += 2 ;
1057
1074
to_parse.emplace_back (ParseContext::ALT, -1 , -1 );
1058
1075
} else if (in[j] == ' s' ) {
1076
+ script_size += 1 ;
1059
1077
to_parse.emplace_back (ParseContext::SWAP, -1 , -1 );
1060
1078
} else if (in[j] == ' c' ) {
1079
+ script_size += 1 ;
1061
1080
to_parse.emplace_back (ParseContext::CHECK, -1 , -1 );
1062
1081
} else if (in[j] == ' d' ) {
1082
+ script_size += 3 ;
1063
1083
to_parse.emplace_back (ParseContext::DUP_IF, -1 , -1 );
1064
1084
} else if (in[j] == ' j' ) {
1085
+ script_size += 4 ;
1065
1086
to_parse.emplace_back (ParseContext::NON_ZERO, -1 , -1 );
1066
1087
} else if (in[j] == ' n' ) {
1088
+ script_size += 1 ;
1067
1089
to_parse.emplace_back (ParseContext::ZERO_NOTEQUAL, -1 , -1 );
1068
1090
} 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 {};
1069
1094
to_parse.emplace_back (ParseContext::VERIFY, -1 , -1 );
1070
1095
} else if (in[j] == ' u' ) {
1096
+ script_size += 4 ;
1071
1097
to_parse.emplace_back (ParseContext::WRAP_U, -1 , -1 );
1072
1098
} else if (in[j] == ' t' ) {
1099
+ script_size += 1 ;
1073
1100
to_parse.emplace_back (ParseContext::WRAP_T, -1 , -1 );
1074
1101
} else if (in[j] == ' l' ) {
1075
1102
// The l: wrapper is equivalent to or_i(0,X)
1103
+ script_size += 4 ;
1076
1104
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
1077
1105
to_parse.emplace_back (ParseContext::OR_I, -1 , -1 );
1078
1106
} else {
1079
1107
return {};
1080
1108
}
1109
+ last_was_v = (in[j] == ' v' );
1081
1110
}
1082
1111
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 );
1084
1113
break ;
1085
1114
}
1086
1115
case ParseContext::EXPR: {
@@ -1094,48 +1123,56 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1094
1123
auto & [key, key_size] = *res;
1095
1124
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector (std::move (key))))));
1096
1125
in = in.subspan (key_size + 1 );
1126
+ script_size += 34 ;
1097
1127
} else if (Const (" pkh(" , in)) {
1098
1128
auto res = ParseKeyEnd<Key>(in, ctx);
1099
1129
if (!res) return {};
1100
1130
auto & [key, key_size] = *res;
1101
1131
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector (std::move (key))))));
1102
1132
in = in.subspan (key_size + 1 );
1133
+ script_size += 24 ;
1103
1134
} else if (Const (" pk_k(" , in)) {
1104
1135
auto res = ParseKeyEnd<Key>(in, ctx);
1105
1136
if (!res) return {};
1106
1137
auto & [key, key_size] = *res;
1107
1138
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector (std::move (key))));
1108
1139
in = in.subspan (key_size + 1 );
1140
+ script_size += 33 ;
1109
1141
} else if (Const (" pk_h(" , in)) {
1110
1142
auto res = ParseKeyEnd<Key>(in, ctx);
1111
1143
if (!res) return {};
1112
1144
auto & [key, key_size] = *res;
1113
1145
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector (std::move (key))));
1114
1146
in = in.subspan (key_size + 1 );
1147
+ script_size += 23 ;
1115
1148
} else if (Const (" sha256(" , in)) {
1116
1149
auto res = ParseHexStrEnd (in, 32 , ctx);
1117
1150
if (!res) return {};
1118
1151
auto & [hash, hash_size] = *res;
1119
1152
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move (hash)));
1120
1153
in = in.subspan (hash_size + 1 );
1154
+ script_size += 38 ;
1121
1155
} else if (Const (" ripemd160(" , in)) {
1122
1156
auto res = ParseHexStrEnd (in, 20 , ctx);
1123
1157
if (!res) return {};
1124
1158
auto & [hash, hash_size] = *res;
1125
1159
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move (hash)));
1126
1160
in = in.subspan (hash_size + 1 );
1161
+ script_size += 26 ;
1127
1162
} else if (Const (" hash256(" , in)) {
1128
1163
auto res = ParseHexStrEnd (in, 32 , ctx);
1129
1164
if (!res) return {};
1130
1165
auto & [hash, hash_size] = *res;
1131
1166
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move (hash)));
1132
1167
in = in.subspan (hash_size + 1 );
1168
+ script_size += 38 ;
1133
1169
} else if (Const (" hash160(" , in)) {
1134
1170
auto res = ParseHexStrEnd (in, 20 , ctx);
1135
1171
if (!res) return {};
1136
1172
auto & [hash, hash_size] = *res;
1137
1173
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move (hash)));
1138
1174
in = in.subspan (hash_size + 1 );
1175
+ script_size += 26 ;
1139
1176
} else if (Const (" after(" , in)) {
1140
1177
int arg_size = FindNextChar (in, ' )' );
1141
1178
if (arg_size < 1 ) return {};
@@ -1144,6 +1181,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1144
1181
if (num < 1 || num >= 0x80000000L ) return {};
1145
1182
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num));
1146
1183
in = in.subspan (arg_size + 1 );
1184
+ script_size += 1 + (num > 16 ) + (num > 0x7f ) + (num > 0x7fff ) + (num > 0x7fffff );
1147
1185
} else if (Const (" older(" , in)) {
1148
1186
int arg_size = FindNextChar (in, ' )' );
1149
1187
if (arg_size < 1 ) return {};
@@ -1152,6 +1190,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1152
1190
if (num < 1 || num >= 0x80000000L ) return {};
1153
1191
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num));
1154
1192
in = in.subspan (arg_size + 1 );
1193
+ script_size += 1 + (num > 16 ) + (num > 0x7f ) + (num > 0x7fff ) + (num > 0x7fffff );
1155
1194
} else if (Const (" multi(" , in)) {
1156
1195
// Get threshold
1157
1196
int next_comma = FindNextChar (in, ' ,' );
@@ -1171,6 +1210,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1171
1210
}
1172
1211
if (keys.size () < 1 || keys.size () > 20 ) return {};
1173
1212
if (k < 1 || k > (int64_t )keys.size ()) return {};
1213
+ script_size += 2 + (keys.size () > 16 ) + (k > 16 ) + 34 * keys.size ();
1174
1214
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move (keys), k));
1175
1215
} else if (Const (" thresh(" , in)) {
1176
1216
int next_comma = FindNextChar (in, ' ,' );
@@ -1181,6 +1221,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1181
1221
// n = 1 here because we read the first WRAPPED_EXPR before reaching THRESH
1182
1222
to_parse.emplace_back (ParseContext::THRESH, 1 , k);
1183
1223
to_parse.emplace_back (ParseContext::WRAPPED_EXPR, -1 , -1 );
1224
+ script_size += 2 + (k > 16 );
1184
1225
} else if (Const (" andor(" , in)) {
1185
1226
to_parse.emplace_back (ParseContext::ANDOR, -1 , -1 );
1186
1227
to_parse.emplace_back (ParseContext::CLOSE_BRACKET, -1 , -1 );
@@ -1189,21 +1230,29 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1189
1230
to_parse.emplace_back (ParseContext::WRAPPED_EXPR, -1 , -1 );
1190
1231
to_parse.emplace_back (ParseContext::COMMA, -1 , -1 );
1191
1232
to_parse.emplace_back (ParseContext::WRAPPED_EXPR, -1 , -1 );
1233
+ script_size += 5 ;
1192
1234
} else {
1193
1235
if (Const (" and_n(" , in)) {
1194
1236
to_parse.emplace_back (ParseContext::AND_N, -1 , -1 );
1237
+ script_size += 5 ;
1195
1238
} else if (Const (" and_b(" , in)) {
1196
1239
to_parse.emplace_back (ParseContext::AND_B, -1 , -1 );
1240
+ script_size += 2 ;
1197
1241
} else if (Const (" and_v(" , in)) {
1198
1242
to_parse.emplace_back (ParseContext::AND_V, -1 , -1 );
1243
+ script_size += 1 ;
1199
1244
} else if (Const (" or_b(" , in)) {
1200
1245
to_parse.emplace_back (ParseContext::OR_B, -1 , -1 );
1246
+ script_size += 2 ;
1201
1247
} else if (Const (" or_c(" , in)) {
1202
1248
to_parse.emplace_back (ParseContext::OR_C, -1 , -1 );
1249
+ script_size += 3 ;
1203
1250
} else if (Const (" or_d(" , in)) {
1204
1251
to_parse.emplace_back (ParseContext::OR_D, -1 , -1 );
1252
+ script_size += 4 ;
1205
1253
} else if (Const (" or_i(" , in)) {
1206
1254
to_parse.emplace_back (ParseContext::OR_I, -1 , -1 );
1255
+ script_size += 4 ;
1207
1256
} else {
1208
1257
return {};
1209
1258
}
@@ -1239,6 +1288,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1239
1288
break ;
1240
1289
}
1241
1290
case ParseContext::VERIFY: {
1291
+ script_size += (constructed.back ()->GetType () << " x" _mst);
1242
1292
constructed.back () = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector (std::move (constructed.back ())));
1243
1293
break ;
1244
1294
}
@@ -1294,6 +1344,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1294
1344
in = in.subspan (1 );
1295
1345
to_parse.emplace_back (ParseContext::THRESH, n+1 , k);
1296
1346
to_parse.emplace_back (ParseContext::WRAPPED_EXPR, -1 , -1 );
1347
+ script_size += 2 ;
1297
1348
} else if (in[0 ] == ' )' ) {
1298
1349
if (k > n) return {};
1299
1350
in = in.subspan (1 );
@@ -1325,6 +1376,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1325
1376
1326
1377
// Sanity checks on the produced miniscript
1327
1378
assert (constructed.size () == 1 );
1379
+ assert (constructed[0 ]->ScriptSize () == script_size);
1328
1380
if (in.size () > 0 ) return {};
1329
1381
const NodeRef<Key> tl_node = std::move (constructed.front ());
1330
1382
tl_node->DuplicateKeyCheck (ctx);
@@ -1779,6 +1831,8 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx&
1779
1831
template <typename Ctx>
1780
1832
inline NodeRef<typename Ctx::Key> FromScript (const CScript& script, const Ctx& ctx) {
1781
1833
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 {};
1782
1836
auto decomposed = DecomposeScript (script);
1783
1837
if (!decomposed) return {};
1784
1838
auto it = decomposed->begin ();
0 commit comments