P2TR Script Path - Full Taproot Execution
10. P2TR Script Path — Full Taproot Execution
Overview
The script path spend in P2TR allows spending via one of potentially many alternative scripts embedded in the Taproot tree — without revealing the other scripts or the internal public key. This is achieved through a Merkle proof system called the Tapscript tree.
Taproot Tree Structure
Scripts are organized in a binary Merkle tree of "tapleaves," each containing a script:
Root
/ \
Branch Leaf C
/ \
Leaf A Leaf B
Each Leaf = tagged_hash("TapLeaf", version || compact_size(script) || script)
Each Branch = tagged_hash("TapBranch", left || right) (sorted lexicographically)
Root = top-level hash
The Merkle root is incorporated into the output key tweak, committing to all scripts.
Script Path Witness Structure
Witness:
<script inputs satisfying the chosen script>
<chosen tapscript>
<control block>
Control block = leaf_version || internal_pubkey || merkle_path
leaf_version: 1 byte (0xc0 for Tapscript, with parity bit)
internal_pubkey: 32 bytes (x-only)
merkle_path: 0–128 × 32-byte sibling hashes
Script Path Verification
Step 1: Verify the merkle path
- Recompute tapleaf hash from tapscript
- Traverse up the tree using the merkle_path hashes
- Verify the resulting root matches the one used to compute Q
Step 2: Verify the output key commitment
t = tagged_hash("TapTweak", internal_pubkey || merkle_root)
Q == lift_x(internal_pubkey) + t·G
(check x-coordinate parity via control block's parity bit)
Step 3: Execute the tapscript
Uses Tapscript rules (BIP 342), which differ slightly from legacy script:
- OP_CHECKSIG uses Schnorr, not ECDSA
- OP_CHECKMULTISIG is DISABLED (use OP_CHECKSIGADD instead)
- Script resource limits are different (no 201-opcode limit)
- OP_SUCCESS opcodes for soft-fork extensibility
Tapscript-Specific Rules (BIP 342)
Disabled opcodes in Tapscript:
OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY → cause script failure if encountered
New opcode: OP_CHECKSIGADD
Usage: <pubkey> OP_CHECKSIGADD
Pops: pubkey, n (accumulator), sig
Pushes: n+1 if sig valid, n if sig empty, fails if sig invalid-but-non-empty
Enables: threshold signatures via OP_NUMEQUAL at the end
OP_SUCCESS opcodes:
Any opcode in range [0x50, 0x60, 0x62, 0x66–0x67, 0x68–0x6a, 0x6d–0x6f,
0x71, 0x73, 0x75, 0x77–0x7a, 0x7c–0x7e, 0x80, 0x82–0x98, 0xa6–0xaf]
If encountered: script succeeds unconditionally (for future soft-fork use)
Example: 3-of-5 Tapscript Multisig
Tapscript using OP_CHECKSIGADD:
<pk1> OP_CHECKSIG
<pk2> OP_CHECKSIGADD
<pk3> OP_CHECKSIGADD
<pk4> OP_CHECKSIGADD
<pk5> OP_CHECKSIGADD
OP_3 OP_NUMEQUAL
Spending witness inputs (3 valid signatures, 2 empty):
<sig1> <sig2> OP_0 <sig4> OP_0
This is more efficient than OP_CHECKMULTISIG for large M-of-N and allows exact threshold enforcement with fail-on-invalid-signature semantics.
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: