aboutsummaryrefslogtreecommitdiff
path: root/src/script
diff options
context:
space:
mode:
authorAntoine Poinsot <darosior@protonmail.com>2023-01-25 14:31:05 +0100
committerAntoine Poinsot <darosior@protonmail.com>2023-10-08 02:43:17 +0200
commitf4f978d38ee4920c5cd0de5d93b407ec37bfd9c0 (patch)
tree0d0aa0457688ff9bef670a9d785a8a52e5e2b21e /src/script
parent9cb4c68b89a5715f82026f4aa446b876addd8472 (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.cpp2
-rw-r--r--src/script/miniscript.h43
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();