Skip to content

Commit f4f978d

Browse files
committed
miniscript: adapt resources checks depending on context
Under Tapscript, there is: - No limit on the number of OPs - No limit on the script size, it's implicitly limited by the maximum (standard) transaction size. - No standardness limit on the number of stack items, it's limited by the consensus MAX_STACK_SIZE. This requires tracking the maximum stack size at all times during script execution, which will be tackled in its own commit. In order to avoid any Miniscript that would not be spendable by a standard transaction because of the size of the witness, we limit the script size under Tapscript to the maximum standard transaction size minus the maximum possible witness and Taproot control block sizes. Note this is a conservative limit but it still allows for scripts more than a hundred times larger than under P2WSH.
1 parent 9cb4c68 commit f4f978d

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

src/script/miniscript.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
#include <vector>
77
#include <script/script.h>
88
#include <script/miniscript.h>
9+
#include <serialize.h>
910

1011
#include <assert.h>
1112

1213
namespace miniscript {
13-
1414
namespace internal {
1515

1616
Type SanitizeType(Type e) {

src/script/miniscript.h

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,34 @@ constexpr bool IsTapscript(MiniscriptContext ms_ctx)
248248

249249
namespace internal {
250250

251+
//! The maximum size of a witness item for a Miniscript under Tapscript context. (A BIP340 signature with a sighash type byte.)
252+
static constexpr uint32_t MAX_TAPMINISCRIPT_STACK_ELEM_SIZE{65};
253+
254+
//! nVersion + nLockTime
255+
constexpr uint32_t TX_OVERHEAD{4 + 4};
256+
//! prevout + nSequence + scriptSig
257+
constexpr uint32_t TXIN_BYTES_NO_WITNESS{36 + 4 + 1};
258+
//! nValue + script len + OP_0 + pushdata 32.
259+
constexpr uint32_t P2WSH_TXOUT_BYTES{8 + 1 + 1 + 33};
260+
//! Data other than the witness in a transaction. Overhead + vin count + one vin + vout count + one vout + segwit marker
261+
constexpr uint32_t TX_BODY_LEEWAY_WEIGHT{(TX_OVERHEAD + GetSizeOfCompactSize(1) + TXIN_BYTES_NO_WITNESS + GetSizeOfCompactSize(1) + P2WSH_TXOUT_BYTES) * WITNESS_SCALE_FACTOR + 2};
262+
//! Maximum possible stack size to spend a Taproot output (excluding the script itself).
263+
constexpr uint32_t MAX_TAPSCRIPT_SAT_SIZE{GetSizeOfCompactSize(MAX_STACK_SIZE) + (GetSizeOfCompactSize(MAX_TAPMINISCRIPT_STACK_ELEM_SIZE) + MAX_TAPMINISCRIPT_STACK_ELEM_SIZE) * MAX_STACK_SIZE + GetSizeOfCompactSize(TAPROOT_CONTROL_MAX_SIZE) + TAPROOT_CONTROL_MAX_SIZE};
264+
/** The maximum size of a script depending on the context. */
265+
constexpr uint32_t MaxScriptSize(MiniscriptContext ms_ctx)
266+
{
267+
if (IsTapscript(ms_ctx)) {
268+
// Leaf scripts under Tapscript are not explicitly limited in size. They are only implicitly
269+
// bounded by the maximum standard size of a spending transaction. Let the maximum script
270+
// size conservatively be small enough such that even a maximum sized witness and a reasonably
271+
// sized spending transaction can spend an output paying to this script without running into
272+
// the maximum standard tx size limit.
273+
constexpr auto max_size{MAX_STANDARD_TX_WEIGHT - TX_BODY_LEEWAY_WEIGHT - MAX_TAPSCRIPT_SAT_SIZE};
274+
return max_size - GetSizeOfCompactSize(max_size);
275+
}
276+
return MAX_STANDARD_P2WSH_SCRIPT_SIZE;
277+
}
278+
251279
//! Helper function for Node::CalcType.
252280
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx);
253281

@@ -1277,6 +1305,7 @@ struct Node {
12771305

12781306
//! Check the ops limit of this script against the consensus limit.
12791307
bool CheckOpsLimit() const {
1308+
if (IsTapscript(m_script_ctx)) return true;
12801309
if (const auto ops = GetOps()) return *ops <= MAX_OPS_PER_SCRIPT;
12811310
return true;
12821311
}
@@ -1290,6 +1319,8 @@ struct Node {
12901319

12911320
//! Check the maximum stack size for this script against the policy limit.
12921321
bool CheckStackSize() const {
1322+
// TODO: MAX_STACK_SIZE during script execution under Tapscript.
1323+
if (IsTapscript(m_script_ctx)) return true;
12931324
if (const auto ss = GetStackSize()) return *ss <= MAX_STANDARD_P2WSH_STACK_ITEMS;
12941325
return true;
12951326
}
@@ -1359,7 +1390,10 @@ struct Node {
13591390
}
13601391

13611392
//! Check whether this node is valid at all.
1362-
bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; }
1393+
bool IsValid() const {
1394+
if (GetType() == ""_mst) return false;
1395+
return ScriptSize() <= internal::MaxScriptSize(m_script_ctx);
1396+
}
13631397

13641398
//! Check whether this node is valid as a script on its own.
13651399
bool IsValidTopLevel() const { return IsValid() && GetType() << "B"_mst; }
@@ -1546,6 +1580,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
15461580
// (instead transforming another opcode into its VERIFY form). However, the v: wrapper has
15471581
// to be interleaved with other fragments to be valid, so this is not a concern.
15481582
size_t script_size{1};
1583+
size_t max_size{internal::MaxScriptSize(ctx.MsContext())};
15491584

15501585
// The two integers are used to hold state for thresh()
15511586
std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse;
@@ -1589,7 +1624,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
15891624
};
15901625

15911626
while (!to_parse.empty()) {
1592-
if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
1627+
if (script_size > max_size) return {};
15931628

15941629
// Get the current context we are decoding within
15951630
auto [cur_context, n, k] = to_parse.back();
@@ -1608,7 +1643,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
16081643
// If there is no colon, this loop won't execute
16091644
bool last_was_v{false};
16101645
for (size_t j = 0; colon_index && j < *colon_index; ++j) {
1611-
if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
1646+
if (script_size > max_size) return {};
16121647
if (in[j] == 'a') {
16131648
script_size += 2;
16141649
to_parse.emplace_back(ParseContext::ALT, -1, -1);
@@ -2386,7 +2421,7 @@ template<typename Ctx>
23862421
inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) {
23872422
using namespace internal;
23882423
// A too large Script is necessarily invalid, don't bother parsing it.
2389-
if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
2424+
if (script.size() > MaxScriptSize(ctx.MsContext())) return {};
23902425
auto decomposed = DecomposeScript(script);
23912426
if (!decomposed) return {};
23922427
auto it = decomposed->begin();

0 commit comments

Comments
 (0)