Pseudocode for validation weight accounting:
10. Tapscript Resource Limits
Tapscript defines specific resource limits that govern how complex a script can be and how much computation a node must perform to validate it. These limits differ from legacy Script in important ways.
Transaction Weight as the Primary Constraint
In legacy Script, individual scripts had byte size limits and opcode count limits. In Tapscript, the primary resource constraint is transaction weight, which affects all script data including the witness.
Transaction weight = base_size × 4 + total_witness_size
Witness discount: witness data counts at 1/4 weight compared to base data
This means Tapscript data (in the witness) is "cheap" relative to non-witness data
The maximum transaction weight is 400,000 weight units. This indirectly limits how large a Tapscript can be, but there is no separate byte limit on individual scripts.
Removed Limits in Tapscript
Removed from Tapscript (vs legacy Script):
- MAX_OPS_PER_SCRIPT (201 non-push opcodes): REMOVED
- MAX_SCRIPT_SIZE (10,000 bytes per script): REMOVED
(replaced by weight-based limits)
The opcode count limit in legacy Script was a rough proxy for execution cost. Tapscript removes it because:
-
Schnorr signatures allow batch validation — validating many signatures at once is cheaper than validating each individually, so counting signature check opcodes as "expensive" is inaccurate
-
Transaction weight provides a more accurate overall constraint
-
The 201-opcode limit was preventing useful smart contract patterns
Remaining Limits in Tapscript
Unchanged from legacy Script:
- Maximum stack item size: 520 bytes
- Maximum stack depth: 1,000 items (main + alt stack combined)
- Maximum script numeric value: 4 bytes (CScriptNum range)
- Maximum witness item count: 2 + stack depth per input
Validation Weight for Signature Opcodes
BIP 342 introduces a concept called validation weight for signature-checking opcodes in Tapscript. Each input starts with a budget of 50 + witness_size validation weight units. Each executed OP_CHECKSIG, OP_CHECKSIGVERIFY, or OP_CHECKSIGADD costs 50 weight units.
Initial budget per Tapscript input:
budget = 50 + len(witness_stack_serialized)
Cost per signature check opcode:
- OP_CHECKSIG (successful): 50 weight units
- OP_CHECKSIGADD (any): 50 weight units
- OP_CHECKSIG with empty signature: 0 weight units (no verification needed)
If budget runs out:
→ Script fails with SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT
This mechanism prevents a Tapscript from requiring an unbounded number of signature verifications while still allowing complex multi-signature constructions.
# Pseudocode for validation weight accounting:
def execute_tapscript_with_weight_limit(script, witness_stack):
budget = 50 + sum(len(item) for item in witness_stack)
for opcode in script:
if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD):
if stack[-1] != b'': # Non-empty signature
budget -= 50
if budget < 0:
raise ScriptError("TAPSCRIPT_VALIDATION_WEIGHT")
Control Block Size Limits
The control block (Merkle path proof) is also constrained:
Control block size:
Minimum: 33 bytes (1 byte version + 32 bytes internal key, depth 0)
Maximum: 33 + 128 × 32 = 4,129 bytes (depth 128, the maximum tree depth)
Maximum Taproot tree depth: 128 levels
Maximum leaves in a tree: 2^128 (effectively unlimited)
Technical Insight
This topic covers essential mechanics for Chapter 11. Understanding these details is key to mastering advanced Bitcoin script constructions like Taproot and specialized covenants.
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: