diff options
author | Antoine Poinsot <darosior@protonmail.com> | 2023-01-25 14:31:05 +0100 |
---|---|---|
committer | Antoine Poinsot <darosior@protonmail.com> | 2023-10-08 02:43:17 +0200 |
commit | f4f978d38ee4920c5cd0de5d93b407ec37bfd9c0 (patch) | |
tree | 0d0aa0457688ff9bef670a9d785a8a52e5e2b21e /src/script | |
parent | 9cb4c68b89a5715f82026f4aa446b876addd8472 (diff) |
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.
Diffstat (limited to 'src/script')
-rw-r--r-- | src/script/miniscript.cpp | 2 | ||||
-rw-r--r-- | src/script/miniscript.h | 43 |
2 files changed, 40 insertions, 5 deletions
diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index c133f88709..03158c5d8a 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -6,11 +6,11 @@ #include <vector> #include <script/script.h> #include <script/miniscript.h> +#include <serialize.h> #include <assert.h> namespace miniscript { - namespace internal { Type SanitizeType(Type e) { diff --git a/src/script/miniscript.h b/src/script/miniscript.h index cd656444b6..54d86bf5b0 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -248,6 +248,34 @@ constexpr bool IsTapscript(MiniscriptContext ms_ctx) namespace internal { +//! The maximum size of a witness item for a Miniscript under Tapscript context. (A BIP340 signature with a sighash type byte.) +static constexpr uint32_t MAX_TAPMINISCRIPT_STACK_ELEM_SIZE{65}; + +//! nVersion + nLockTime +constexpr uint32_t TX_OVERHEAD{4 + 4}; +//! prevout + nSequence + scriptSig +constexpr uint32_t TXIN_BYTES_NO_WITNESS{36 + 4 + 1}; +//! nValue + script len + OP_0 + pushdata 32. +constexpr uint32_t P2WSH_TXOUT_BYTES{8 + 1 + 1 + 33}; +//! Data other than the witness in a transaction. Overhead + vin count + one vin + vout count + one vout + segwit marker +constexpr uint32_t TX_BODY_LEEWAY_WEIGHT{(TX_OVERHEAD + GetSizeOfCompactSize(1) + TXIN_BYTES_NO_WITNESS + GetSizeOfCompactSize(1) + P2WSH_TXOUT_BYTES) * WITNESS_SCALE_FACTOR + 2}; +//! Maximum possible stack size to spend a Taproot output (excluding the script itself). +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}; +/** The maximum size of a script depending on the context. */ +constexpr uint32_t MaxScriptSize(MiniscriptContext ms_ctx) +{ + if (IsTapscript(ms_ctx)) { + // Leaf scripts under Tapscript are not explicitly limited in size. They are only implicitly + // bounded by the maximum standard size of a spending transaction. Let the maximum script + // size conservatively be small enough such that even a maximum sized witness and a reasonably + // sized spending transaction can spend an output paying to this script without running into + // the maximum standard tx size limit. + constexpr auto max_size{MAX_STANDARD_TX_WEIGHT - TX_BODY_LEEWAY_WEIGHT - MAX_TAPSCRIPT_SAT_SIZE}; + return max_size - GetSizeOfCompactSize(max_size); + } + return MAX_STANDARD_P2WSH_SCRIPT_SIZE; +} + //! Helper function for Node::CalcType. 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); @@ -1277,6 +1305,7 @@ public: //! Check the ops limit of this script against the consensus limit. bool CheckOpsLimit() const { + if (IsTapscript(m_script_ctx)) return true; if (const auto ops = GetOps()) return *ops <= MAX_OPS_PER_SCRIPT; return true; } @@ -1290,6 +1319,8 @@ public: //! Check the maximum stack size for this script against the policy limit. bool CheckStackSize() const { + // TODO: MAX_STACK_SIZE during script execution under Tapscript. + if (IsTapscript(m_script_ctx)) return true; if (const auto ss = GetStackSize()) return *ss <= MAX_STANDARD_P2WSH_STACK_ITEMS; return true; } @@ -1359,7 +1390,10 @@ public: } //! Check whether this node is valid at all. - bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; } + bool IsValid() const { + if (GetType() == ""_mst) return false; + return ScriptSize() <= internal::MaxScriptSize(m_script_ctx); + } //! Check whether this node is valid as a script on its own. bool IsValidTopLevel() const { return IsValid() && GetType() << "B"_mst; } @@ -1546,6 +1580,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // (instead transforming another opcode into its VERIFY form). However, the v: wrapper has // to be interleaved with other fragments to be valid, so this is not a concern. size_t script_size{1}; + size_t max_size{internal::MaxScriptSize(ctx.MsContext())}; // The two integers are used to hold state for thresh() 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) }; while (!to_parse.empty()) { - if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (script_size > max_size) return {}; // Get the current context we are decoding within auto [cur_context, n, k] = to_parse.back(); @@ -1608,7 +1643,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // If there is no colon, this loop won't execute bool last_was_v{false}; for (size_t j = 0; colon_index && j < *colon_index; ++j) { - if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (script_size > max_size) return {}; if (in[j] == 'a') { script_size += 2; to_parse.emplace_back(ParseContext::ALT, -1, -1); @@ -2386,7 +2421,7 @@ template<typename Ctx> inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { using namespace internal; // A too large Script is necessarily invalid, don't bother parsing it. - if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (script.size() > MaxScriptSize(ctx.MsContext())) return {}; auto decomposed = DecomposeScript(script); if (!decomposed) return {}; auto it = decomposed->begin(); |