The Rule Nobody Thinks About Until Fees Bite Them
You've assembled a Taproot spend. The script validates. The transaction broadcasts. Then the fee estimator returns a number that doesn't match your mental model, and you start picking at the weight calculation wondering where the gap crept in.
Nine times out of ten, it's the clean stack rule and the witness discount doing something together that most explanations treat as separate topics. They aren't. They interact, and the interaction has real fee consequences that compound fast once you're batching inputs.
What the Clean Stack Rule Actually Demands
Bitcoin Script has one mandate at the end of execution: exactly one item must remain on the stack, and it must be truthy. Not "at least one item." Not "a truthy value somewhere in there." One. Clean.
This rule, enforced under BIP-62 standardness and carried forward into Tapscript via BIP-342, means your script must consume its own inputs tidily. Leave two items on the stack after execution and the transaction is non-standard, rejected by the mempool before it ever troubles a miner. Leave a zero-length byte array as the sole remaining item and it fails outright.
Every element pushed onto the stack during execution either gets consumed by an opcode or it becomes your single final witness to validity. No littering.
For legacy Script this was already true. What Taproot changes is where those stack items live and what they cost.
The Witness Discount: A Quick Mechanical Recap
SegWit split transaction data into two weight classes. Non-witness bytes cost 4 weight units each. Witness bytes cost 1 weight unit each. The block limit sits at 4,000,000 weight units total.
Taproot (P2TR) spends are SegWit version 1. Every byte of the witness stack, the script itself in a script-path spend, the control block, all witness data items, gets that 1-weight-unit rate. The scriptPubKey and all non-witness fields still pay 4 weight units per byte.
The discount is substantial. A 100-byte witness item costs 100 weight units; the same 100 bytes in a non-witness field costs 400. That four-to-one ratio is the entire motivation for moving complex logic into Taproot script paths, and it's as close to a free lunch as Bitcoin protocol design offers.
The discount does not care whether your witness data is necessary. It discounts everything in the witness, including any excess items the clean stack rule eventually forces you to address.
Where the Two Rules Collide
Consider a script-path spend using a custom locking script: a 2-of-3 multisig built in Tapscript with OP_CHECKSIGADD, which replaced the legacy OP_CHECKMULTISIG in Tapscript. Three public keys compiled into the script leaf. To satisfy it, you push two valid Schnorr signatures onto the witness stack before execution.
Now imagine a slightly careless script author adds an OP_SWAP and a superfluous OP_DROP to rearrange intermediate results but forgets to consume one intermediate hash preimage pushed early in execution. The script evaluates to true. Two items remain on the stack. Non-standard. Rejected.
The fix is another OP_DROP. One opcode, one byte in the script leaf. Tiny, right?
Not quite invisible. That byte lives in the witness at 1 weight unit. More importantly, the reason you needed the OP_DROP is that your witness stack had an extra 32-byte preimage sitting in it: 32 weight units you're paying on every single spend of this output. Across thousands of UTXOs, that is not rounding error.
Here's the version I find clarifying. Say you're building a covenant-style script that requires a 64-byte hash preimage as part of its unlock condition. The preimage gets pushed, used in OP_SHA256, compared with OP_EQUALVERIFY, consumed. Clean. A developer then modifies the script to check the preimage against a second branch condition, and the refactored version pushes it twice. One copy gets consumed by the verify. One doesn't. The clean stack rule catches it at broadcast. The developer adds OP_DROP. The extra 64 bytes of preimage remain in the witness on every spend: 64 weight units, plus 1 for the OP_DROP in the script leaf.
At a block weight limit of 4,000,000 units, with a typical simple P2TR key-path spend weighing roughly 57.5 vbytes (230 weight units), that extra 65 weight units is a 28% weight increase on that one input. Multiply by a batched transaction with 50 such inputs and you've added over 3,000 weight units to a transaction that didn't need them. Do you want to explain that to the wallet team?
The Annex Wrinkle (Speculation Flagged)
Taproot witness structures include an optional annex field, identified by a leading 0x50 byte. The annex is currently undefined in consensus; nodes strip it before passing data to script execution. It sits in the witness and pays the 1-weight-unit rate.
From here, I'm speculating and I'll own that explicitly. Proposals for future soft forks, including some covenant designs and introspection opcodes, might use the annex to carry data that interacts with script execution. If annex data eventually influences what lands on the execution stack, the clean stack rule would govern how scripts handle it, and every byte of annex would carry the discounted witness cost while potentially creating new clean-stack obligations. None of this is deployed, none of it is finalized, and it could change shape entirely. Worth watching. Not worth coding against today.
Counting Weight Before You Broadcast
The practical discipline is straightforward: before finalizing any custom Tapscript, trace the stack manually through every execution path and confirm that every non-terminal path ends with exactly one truthy item. The happy path and every branch reachable through OP_IF trees.
Then count witness bytes deliberately:
- Control block: 33 bytes for a key-path-only tree, growing with Merkle proof depth (each additional level adds 32 bytes)
- Script: every opcode and every pushed constant, at 1 weight unit per byte
- Witness stack items: every signature (64 bytes for Schnorr), every preimage, every pubkey pushed explicitly
Add compact-size integer overhead for the number of witness items and the length prefix of each item, typically 1 to 3 bytes each, also at 1 weight unit.
Find a path that pushes more stack items than the script consumes. That's not just a clean-stack violation, it's a fee leak. The witness discount is real, but discounted waste is still waste, and it scales with every input you add.
The witness discount makes Taproot script-path spends remarkably efficient for complex logic. Think of the clean stack rule as the auditor standing at the door: it's what ensures only necessary complexity survives into the weight calculation. The two mechanisms aren't in tension. The clean stack rule is, in a precise sense, what keeps the discount honest, and scripts that ignore it are paying a tax they never had to.