Tapscript Deep Dive: Programmable Spending Conditions Beyond Simple Key Paths
How tapscript enables complex spending conditions on Bitcoin, from timelocked vaults to conditional payments, without revealing unused paths.
Every Bitcoin transaction enforces a spending condition: a script that determines who can move funds and under what circumstances. For most of Bitcoin's history, that script language was limited by strict size caps, inefficient multisig opcodes, and the requirement to reveal every possible spending path on-chain. Tapscript, defined in BIP 342, rewrites these rules. It replaces ECDSA with Schnorr signature verification, introduces OP_CHECKSIGADD for composable threshold schemes, removes legacy size limits, and reserves a family of OP_SUCCESS opcodes for future soft-fork upgrades.
Combined with the taptree structure from BIP 341, tapscript enables Bitcoin spending conditions that were previously impractical or impossible: timelocked vaults with emergency recovery keys, multi-party escrows with automatic timeouts, and conditional payments gated on hash preimage revelation. This article walks through how tapscript differs from legacy Bitcoin Script, examines concrete script examples with annotations, and explores what future opcodes like OP_CAT and CTV could unlock.
From Legacy Script to Tapscript
Bitcoin Script has always been intentionally constrained. Satoshi disabled several opcodes early on, and the remaining language operated under tight resource limits: a 10,000-byte maximum script size, a cap of 201 non-push opcodes per script, and a block-wide limit of 80,000 weighted signature operations. These constraints prevented denial-of-service attacks but also prevented expressive spending conditions.
SegWit (BIP 141) moved signature data into the witness and applied a 75% discount to witness bytes. This reduced costs for standard transactions but did not change the scripting language itself. The fundamental limitations on script complexity and multisig efficiency remained.
Tapscript, activated alongside Taproot in November 2021, is a different scripting regime that applies only when spending via the script path of a P2TR output. It shares most opcodes with legacy script but makes several changes that fundamentally alter what is practical to build on Bitcoin.
Key Changes in BIP 342
The differences between legacy script (including SegWit v0 script) and tapscript fall into four categories: signature validation, multisig handling, resource limits, and upgrade mechanisms.
| Feature | Legacy / SegWit v0 | Tapscript (BIP 342) |
|---|---|---|
| Signature scheme | ECDSA (DER-encoded) | Schnorr (BIP 340, 64-byte fixed) |
| Multisig opcode | OP_CHECKMULTISIG (requires dummy element) | OP_CHECKSIGADD (no dummy, batch-verifiable) |
| Script size limit | 10,000 bytes | No limit (bounded by block weight) |
| Non-push opcode limit | 201 per script | Removed |
| Sigop accounting | Block-wide limit of 80,000 (weighted) | Per-input budget: 50 + witness size bytes |
| Upgrade path | OP_NOP redefinition (limited) | OP_SUCCESS (unconditional pass, clean soft-fork path) |
| Public key format | Compressed (33 bytes) or uncompressed (65 bytes) | x-only (32 bytes) |
| Signature hash algorithm | SIGHASH over serialized tx | BIP 341 common signature message (commits to more context) |
Why remove the 201-opcode limit? In legacy script, the opcode cap existed alongside the sigop limit to prevent resource exhaustion. Tapscript replaces both with a single mechanism: the signature budget scales with witness size. Since the spender pays for witness weight, the economic cost of execution is directly proportional to the computational cost of validation. No separate cap is needed.
OP_CHECKSIGADD: Composable Threshold Verification
Legacy multisig used OP_CHECKMULTISIG, which accepted a list of public keys and signatures and tried to match them in order. This design had a well-known off-by-one bug that required pushing a dummy zero element onto the stack. Worse, because the opcode consumed all keys and signatures in a single operation, validators could not batch-verify the individual signatures.
Tapscript replaces this with OP_CHECKSIGADD. The opcode pops three elements: a public key, an integer counter, and a signature. If the signature is valid (verified using BIP 340 Schnorr rules), the counter is incremented by one and pushed back. If the signature is the empty byte string, the counter is pushed unchanged. If the signature is present but invalid, the script fails immediately.
A 2-of-3 Tapscript Multisig
Here is a 2-of-3 multisig in tapscript, annotated step by step. Each signer provides either a valid 64-byte Schnorr signature or an empty byte string:
# Witness stack (from top): <sig_C> <sig_B> <sig_A>
<pubkey_A> # Push first public key
OP_CHECKSIG # Verify sig_A; pushes 1 (valid) or 0 (empty sig)
<pubkey_B> # Push second public key
OP_CHECKSIGADD # Verify sig_B; add result to running count
<pubkey_C> # Push third public key
OP_CHECKSIGADD # Verify sig_C; add result to running count
OP_2 # Push threshold value
OP_NUMEQUAL # Check: does the count equal 2?If Alice and Carol sign (and Bob provides an empty signature), the counter increments to 1 on Alice's check, stays at 1 on Bob's empty signature, and increments to 2 on Carol's check. The final OP_NUMEQUAL succeeds. Each signature check is independent and can be batch-verified with other Schnorr signatures in the same block.
Concrete Script Examples
Tapscript becomes powerful when combined with taptree structure. Instead of encoding every spending condition in a single script (as legacy transactions must), a P2TR output commits to a Merkle tree of scripts. Each leaf represents a distinct spending path. Only the executed path is revealed on-chain.
Timelocked Vault with Emergency Recovery
Consider a cold-storage vault where the owner can spend after a delay, but a recovery key can sweep funds immediately in case of compromise. This requires two taptree leaves:
# Leaf 1: Normal spend (owner, after 1008 blocks / ~1 week)
<owner_pubkey>
OP_CHECKSIGVERIFY
1008
OP_CHECKSEQUENCEVERIFY
# Leaf 2: Emergency recovery (immediate, requires recovery key)
<recovery_pubkey>
OP_CHECKSIGUnder normal operation, the owner waits 1008 blocks (roughly one week) after the UTXO is confirmed, then spends via Leaf 1 using OP_CHECKSEQUENCEVERIFY. If the owner's key is compromised, the recovery key (stored in a hardware security module or hardware wallet) can sweep funds immediately via Leaf 2. The attacker with the owner's key cannot spend for a week, giving the recovery process time to act.
The taptree also has an internal key. In a vault design, this internal key can be set to a MuSig2 aggregate of the owner and recovery keys, enabling a cooperative key-path spend that reveals nothing about the vault's script conditions.
Multi-Party Escrow with Timeout
A three-party escrow where any two parties can release funds, or the sender recovers after a timeout. This uses three taptree leaves:
# Leaf 1: Buyer + Seller agree (2-of-2)
<buyer_pubkey>
OP_CHECKSIGVERIFY
<seller_pubkey>
OP_CHECKSIG
# Leaf 2: Arbiter + either party (2-of-3)
<buyer_pubkey>
OP_CHECKSIG
<seller_pubkey>
OP_CHECKSIGADD
<arbiter_pubkey>
OP_CHECKSIGADD
OP_2
OP_NUMEQUAL
# Leaf 3: Timeout refund (buyer only, after 4320 blocks / ~30 days)
<buyer_pubkey>
OP_CHECKSIGVERIFY
4320
OP_CHECKSEQUENCEVERIFYThe happy path (buyer and seller agree) uses Leaf 1. If there is a dispute, the arbiter can resolve it with either party via Leaf 2. If all parties become unresponsive, the buyer recovers funds after 30 days via Leaf 3. On-chain, only the exercised leaf is ever visible. An observer cannot determine whether an escrow, a vault, or a simple payment occurred.
Hash Preimage Conditional Payment
Conditional payments based on hash preimage revelation are the foundation of HTLCs used in the Lightning Network and atomic swaps. In tapscript, this can be encoded as two taptree leaves:
# Leaf 1: Recipient claims with preimage
OP_SHA256
<hash_of_preimage>
OP_EQUALVERIFY
<recipient_pubkey>
OP_CHECKSIG
# Leaf 2: Sender refund after timeout
<sender_pubkey>
OP_CHECKSIGVERIFY
144
OP_CHECKSEQUENCEVERIFYThe recipient reveals the preimage to claim funds via Leaf 1. If the preimage is never revealed, the sender recovers after 144 blocks (roughly one day) via Leaf 2. This is functionally identical to an HTLC, but because it uses tapscript and taptree structure, the on-chain footprint is smaller: only one leaf script and its Merkle proof appear in the witness, not the entire contract.
How the Taptree Hides Unused Paths
The privacy advantage of tapscript spending comes from the MAST (Merkelized Abstract Syntax Tree) structure embedded in every P2TR output. When a P2TR address is created, the spending conditions are organized into a binary Merkle tree. The tree's root hash is tweaked into the output's public key using a specific formula defined in BIP 341.
When spending via the script path, the witness must include the script being executed, a control block containing the internal public key and the leaf version, and the Merkle proof (the sibling hashes along the path from the leaf to the root). The verifier reconstructs the root hash from these elements and checks that it matches the commitment in the output key.
What stays hidden: Only the executed script leaf and its Merkle path are revealed. All other leaves in the tree remain completely private. A taptree with 128 leaves requires only 7 hashes in the Merkle proof (log2(128) = 7), regardless of how complex the other 127 spending conditions are. An observer cannot even determine how many total leaves exist.
This is a fundamental improvement over P2WSH, where the entire redeem script is revealed at spend time. A complex P2WSH contract with ten spending conditions exposes all ten to chain analysis, even if only one is used. With tapscript, the nine unused conditions remain private.
Key Path: The Ultimate Privacy Shortcut
If all parties cooperate, a key-path spend can be used instead. This produces a single Schnorr signature against the output key, which is indistinguishable from any other single-signature P2TR transaction. No script is revealed. No Merkle proof appears. The entire taptree remains hidden, and the transaction looks identical to a simple payment. This is described in detail in our P2TR spend path deep dive.
Weight Cost: Tapscript vs Legacy
Transaction weight directly determines the fee a user pays. Tapscript spending can be more or less efficient than legacy equivalents depending on the complexity of the spending condition and whether the key path is used.
| Spending Condition | P2WSH (SegWit v0) | P2TR Key Path | P2TR Script Path |
|---|---|---|---|
| Single signature | ~112 vB input | ~57 vB input | ~83 vB input |
| 2-of-3 multisig | ~104 vB input (witness) | ~57 vB (via MuSig2) | ~107 vB input |
| Complex contract (5 paths) | ~full script revealed | ~57 vB (cooperative) | ~script + Merkle proof |
| Output size | 34 bytes | 34 bytes | 34 bytes |
| Unused paths revealed | All paths revealed | No paths revealed | Only executed path |
The key-path spend is the clear winner for efficiency: a single 64-byte Schnorr signature in the witness, no script, no Merkle proof. For cooperative cases, key aggregation (MuSig2 or FROST) can collapse any n-of-n or threshold policy into a single public key, making even complex multi-party setups indistinguishable from simple payments.
Script-path spends add the cost of the script itself, the control block (33 bytes for the internal key plus leaf version), and the Merkle proof (32 bytes per tree depth level). For a tree of depth 4 (up to 16 leaves), the Merkle proof costs 128 bytes of witness data, which at the witness discount translates to 32 weight units.
The Signature Budget
Tapscript replaces legacy sigop counting with a per-input signature budget. The budget for each input is calculated as 50 plus the total serialized size (in bytes) of the input's witness. Each executed signature opcode (OP_CHECKSIG, OP_CHECKSIGVERIFY, or OP_CHECKSIGADD) with a non-empty signature deducts 50 from the budget. If the budget drops below zero, the script fails.
This design means every transaction gets one "free" signature operation (from the base 50), and additional signatures are economically gated by the witness size the spender is already paying for. A 2-of-3 multisig needs 2 signature operations costing 100 budget units, which requires at least 50 bytes of witness data beyond the base allowance. In practice, the signatures themselves (64 bytes each) more than cover this requirement.
The OP_SUCCESS Upgrade Path
One of tapscript's most forward-looking changes is the introduction of OP_SUCCESS opcodes. In legacy script, unused opcodes were either disabled (causing script failure if encountered) or defined as OP_NOP variants that could be redefined via soft fork but with severe constraints: new OP_NOP behavior could only add validation rules, never remove them, and could not modify the stack.
Tapscript takes a different approach. Opcodes 80, 98, 126-129, 131-134, 137-138, 141-142, 149-153, and 187-254 are designated as OP_SUCCESSx. If any of these opcodes appears anywhere in a tapscript (even in an unexecuted branch of an OP_IF), the entire script unconditionally succeeds. This means a future soft fork can redefine an OP_SUCCESS opcode to impose new validation rules without any compatibility issues: old nodes see the opcode and accept the transaction; new nodes enforce the additional checks.
Why this matters for Bitcoin's evolution: The OP_SUCCESS mechanism provides a clean, well-defined path for introducing new opcodes via soft fork. Every proposed covenant opcode (CTV, APO, CAT, VAULT) could be deployed by redefining one of these reserved slots. The upgrade path is built into the consensus rules, reducing the technical risk of future scripting improvements.What OP_CAT and CTV Would Unlock
While tapscript significantly expands what is possible in Bitcoin scripting, it still cannot inspect transaction outputs or enforce constraints on where funds go next. These capabilities require covenants: opcodes that let a script constrain future spending conditions.
OP_CHECKTEMPLATEVERIFY (CTV)
CTV (BIP 119) commits to a hash of the spending transaction's outputs, locktime, and other fields. A script can use CTV to enforce that funds can only be spent to a specific set of outputs. This enables non-interactive vaults (no watchtower required), congestion control via pre-committed transaction trees, and payment pools where multiple users share a single UTXO with pre-defined exit paths. An activation client proposes a Speedy Trial structure with a 90% miner threshold and a minimum activation height around May 2027.
OP_CAT
OP_CAT (BIP 347) concatenates two stack elements into one. While simple in isolation, concatenation combined with hash operations enables scripts to reconstruct and verify parts of the spending transaction on the stack, effectively creating general-purpose introspection. OP_CAT reached "Complete" specification status in March 2026 and has been tested extensively on signet, though no mainnet activation parameters have been proposed.
What Becomes Possible
With covenants, tapscript spending conditions could enforce rules like:
- Funds must pass through a time-delayed intermediate address before reaching the final destination (vault with clawback)
- A UTXO can only be spent to one of a pre-approved set of addresses (whitelisted withdrawal)
- Multiple users can exit a shared UTXO independently without coordinating with others (payment pools)
- A transaction tree can batch hundreds of payments into a single on-chain commitment (congestion control)
These patterns would be especially impactful for Bitcoin Layer 2 protocols, where covenant-enforced exit conditions could reduce trust assumptions and enable more expressive off-chain contract designs.
Limitations of Tapscript Today
Even with the improvements in BIP 342, tapscript has meaningful constraints that developers should understand:
- No output introspection: scripts cannot examine the outputs of the spending transaction, limiting the ability to enforce forwarding rules
- No recursion or loops: Bitcoin Script remains a stack-based language with no looping constructs, bounding execution time but limiting expressiveness
- Stack element size limit: individual stack elements are still capped at 520 bytes, constraining the size of data that can be manipulated in a single operation
- Taptree depth: while there is no explicit depth limit, deeper trees increase the Merkle proof size (32 bytes per level), making very large trees expensive to spend from
- No arithmetic on large numbers: Bitcoin Script integers are limited to 4-byte values (up to roughly 2.1 billion), making precise calculations with satoshi amounts above that range impossible
These limitations are deliberate: they keep validation predictable and bound the worst-case cost of verifying a transaction. But they also mean that advanced constructions like trustless bridges, on-chain DEX settlement, and recursive covenants remain out of reach without further consensus changes.
Tapscript in the Wild: Spark and Beyond
Spark provides a practical example of how taproot's capabilities underpin Layer 2 design. Spark's statechain construction uses a 2-of-2 key structure where the user holds one key and a set of independent operators (the Spark Entity) collectively hold the other via FROST threshold signatures. On-chain, this appears as a standard P2TR key-path output: a single public key that reveals nothing about the multi-party signing setup behind it. The entire FROST committee of operators and the user's key share are invisible to chain analysis.
This is taproot's privacy property in action. A Spark UTXO is indistinguishable from any other single-signature taproot output. The taptree can encode fallback script-path conditions (such as timelocked exit transactions) that are only revealed if the cooperative key path fails. In the normal case, transfers happen entirely off-chain through key rotation, and the on-chain footprint is minimal.
Future covenant opcodes could further enhance this model. CTV-enforced exit paths would allow Spark to pre-commit withdrawal destinations without revealing them until needed. OP_CAT-based introspection could enable more expressive off-chain contract conditions that are verifiable on-chain during dispute resolution. The combination of tapscript's current capabilities with potential covenant opcodes points toward a future where Bitcoin Layer 2s can offer programmable spending conditions with minimal trust assumptions.
Getting Started with Tapscript Development
For developers looking to experiment with tapscript spending conditions, the btcdeb debugger provides a step-through environment for tapscript execution. The Miniscript policy language can compile human-readable spending policies into optimized tapscript, and tools like output descriptors (BIP 386) provide a standardized way to describe P2TR outputs with taptrees.
To explore how Spark builds on these primitives, the Spark SDK documentation covers the protocol's use of taproot key-path spends, FROST signing, and off-chain state transitions. For a deeper comparison of how different Bitcoin scripting approaches compare, see our Bitcoin Script programmability overview and the covenant proposals comparison tool.
This article is for educational purposes only. It does not constitute financial or investment advice. Bitcoin and Layer 2 protocols involve technical and financial risk. Always do your own research and understand the tradeoffs before using any protocol.

