diff options
| author | Bitflash <btf01@proton.me> | 2026-07-30 18:22:48 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-30 18:22:48 -0300 |
| commit | 773440fcc7752b85d99ee5b8500b64dbb5869603 (patch) | |
| tree | b0559564577797486bd44e043f2c0691f1610105 | |
| parent | 50eeb9c9d620895c0d63664d610ea4cfe7b81a34 (diff) | |
script: bound how many operations and stack entries a script may use (#57)
v1.2.1 disabled the unbounded opcodes and capped a single pushed element at
520 bytes. What it did not add was a ceiling on how many items there can be,
so a script still pays for itself once, in bytes, and then multiplies: 520
bytes per stack entry means a script of N bytes made almost entirely of
OP_DUP occupies roughly 520N in memory, on every node that validates it.
Two limits, both from Bitcoin:
- at most 201 operations per script, counted outside the fExec test so
the cost of a script cannot depend on which branch a signature happens
to take. Pushes are exempt, being already bounded by the script length
and by MAX_SCRIPT_ELEMENT_SIZE.
- at most 1000 entries across both stacks together, counted together
because OP_TOALTSTACK would otherwise move the problem rather than
solve it.
This tightens validation, so a node that does not upgrade accepts a
superset of what this one accepts and is not stranded.
Verified against the live chain: a node with these rules synced all 3910
blocks from genesis and accepted every one, so nothing in use relies on
scripts these limits would reject.
| -rw-r--r-- | src/script.cpp | 13 | ||||
| -rw-r--r-- | src/script.h | 9 |
2 files changed, 22 insertions, 0 deletions
diff --git a/src/script.cpp b/src/script.cpp index 44eed93..6d4fb34 100644 --- a/src/script.cpp +++ b/src/script.cpp | |||
| @@ -51,6 +51,7 @@ bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nI | |||
| 51 | vector<bool> vfExec; | 51 | vector<bool> vfExec; |
| 52 | vector<valtype> stack; | 52 | vector<valtype> stack; |
| 53 | vector<valtype> altstack; | 53 | vector<valtype> altstack; |
| 54 | unsigned int nOpCount = 0; | ||
| 54 | if (pvStackRet) | 55 | if (pvStackRet) |
| 55 | pvStackRet->clear(); | 56 | pvStackRet->clear(); |
| 56 | 57 | ||
| @@ -85,6 +86,13 @@ bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nI | |||
| 85 | opcode == OP_MOD || opcode == OP_LSHIFT || opcode == OP_RSHIFT) | 86 | opcode == OP_MOD || opcode == OP_LSHIFT || opcode == OP_RSHIFT) |
| 86 | return false; | 87 | return false; |
| 87 | 88 | ||
| 89 | // Counted outside fExec for the same reason: the cost of a script must | ||
| 90 | // not depend on which branch a signature happens to take. Pushes are | ||
| 91 | // exempt because their cost is already bounded by the script's own | ||
| 92 | // length and by MAX_SCRIPT_ELEMENT_SIZE. | ||
| 93 | if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT) | ||
| 94 | return false; | ||
| 95 | |||
| 88 | if (fExec && opcode <= OP_PUSHDATA4) | 96 | if (fExec && opcode <= OP_PUSHDATA4) |
| 89 | stack.push_back(vchPushValue); | 97 | stack.push_back(vchPushValue); |
| 90 | else if (fExec || (OP_IF <= opcode && opcode <= OP_ENDIF)) | 98 | else if (fExec || (OP_IF <= opcode && opcode <= OP_ENDIF)) |
| @@ -824,6 +832,11 @@ bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nI | |||
| 824 | default: | 832 | default: |
| 825 | return false; | 833 | return false; |
| 826 | } | 834 | } |
| 835 | |||
| 836 | // Both stacks together, because OP_TOALTSTACK would otherwise move the | ||
| 837 | // problem rather than solve it. | ||
| 838 | if (stack.size() + altstack.size() > MAX_STACK_SIZE) | ||
| 839 | return false; | ||
| 827 | } | 840 | } |
| 828 | 841 | ||
| 829 | 842 | ||
diff --git a/src/script.h b/src/script.h index 26bb65a..d36a317 100644 --- a/src/script.h +++ b/src/script.h | |||
| @@ -8,6 +8,15 @@ class CTransaction; | |||
| 8 | // script evaluation allocates whatever the transaction asks it to. | 8 | // script evaluation allocates whatever the transaction asks it to. |
| 9 | static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; | 9 | static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; |
| 10 | 10 | ||
| 11 | // Bounds on what one script may do while being evaluated. The element cap | ||
| 12 | // above limits how big a single item is; these limit how many there can be and | ||
| 13 | // how much work producing them costs. Without them, a script pays for itself | ||
| 14 | // once, in bytes, and then multiplies: 520 bytes per stack entry means a | ||
| 15 | // script of N bytes made almost entirely of OP_DUP occupies roughly 520N in | ||
| 16 | // memory, and every node that validates it pays. | ||
| 17 | static const unsigned int MAX_OPS_PER_SCRIPT = 201; | ||
| 18 | static const unsigned int MAX_STACK_SIZE = 1000; | ||
| 19 | |||
| 11 | enum | 20 | enum |
| 12 | { | 21 | { |
| 13 | SIGHASH_ALL = 1, | 22 | SIGHASH_ALL = 1, |