@@ -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&
17791831template <typename Ctx>
17801832inline 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