TeachMeBitcoin

Example control block for a leaf at depth 2:

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

8. Script Path Spending in Taproot

Script path spending is one of the two ways to spend a P2TR (Taproot) output. It involves revealing one of the scripts committed to in the Taproot output's Merkle tree and providing witness data to satisfy that script.

The Full Script Path Spending Flow

Step 1: Sender creates a P2TR output

The sender constructs a script tree (MAST):
       Root
      /    \
  Leaf A   Branch
           /    \
       Leaf B   Leaf C

Each leaf contains a Tapscript:

- Leaf A: 2-of-2 multisig between Alice and Bob

- Leaf B: Alice alone after 6 months (timelock)

- Leaf C: 3-of-5 federation recovery

The sender tweaks an internal key with the Merkle root:
tweaked_pubkey = internal_key + tagged_hash("TapTweak", internal_key + merkle_root) * G

Output: OP_1 <tweaked_pubkey_x_coordinate>

Step 2: Spender chooses a script path

To spend via Leaf B (Alice's timelock path):

1. Assemble the Tapscript for Leaf B

2. Build the control block proving Leaf B is in the tree

3. Provide the script inputs (Alice's signature)

Step 3: Construct the witness stack

Witness stack (script path):
[
  <alice_schnorr_signature>,    ← script inputs (satisfying Leaf B)
  <leaf_B_tapscript>,           ← the script being executed
  <control_block>               ← proof of inclusion in the tree
]

If annex present:
[
  <alice_schnorr_signature>,
  <leaf_B_tapscript>,
  <control_block>,
  <annex>
]

The Control Block in Detail

The control block is the cryptographic proof that the revealed script is actually part of the committed Taproot tree. It contains the internal public key and the Merkle path from the leaf to the root.

def build_control_block(internal_pubkey, leaf_version, merkle_path, tweaked_pubkey_parity):
    """
    internal_pubkey:       32-byte x-only pubkey
    leaf_version:          e.g., 0xc0
    merkle_path:           list of 32-byte branch hashes
    tweaked_pubkey_parity: 0 or 1 (parity of tweaked key y-coordinate)
    """
    version_and_parity = (leaf_version | tweaked_pubkey_parity).to_bytes(1, 'big')
    path_bytes = b''.join(merkle_path)

    return version_and_parity + internal_pubkey + path_bytes

# Example control block for a leaf at depth 2:
# 1 byte (version+parity) + 32 bytes (internal key) + 2×32 bytes (path) = 97 bytes

Validation of Script Path Spending

When a node sees a Taproot input spending via the script path, it validates in this order:

def validate_taproot_script_path(witness, scriptPubKey):
    # 1. Extract components
    if has_annex(witness):
        annex = witness[-1]
        control_block = witness[-2]
        tapscript = witness[-3]
        script_inputs = witness[:-3]
    else:
        control_block = witness[-1]
        tapscript = witness[-2]
        script_inputs = witness[:-2]

    # 2. Parse control block
    leaf_version = control_block[0] & 0xfe
    parity = control_block[0] & 0x01
    internal_key = control_block[1:33]
    merkle_path = [control_block[33+i*32 : 65+i*32] for i in range((len(control_block)-33)//32)]

    # 3. Compute leaf hash
    leaf_hash = tagged_hash("TapLeaf", 
        bytes([leaf_version]) + compact_size(len(tapscript)) + tapscript)

    # 4. Compute Merkle root by walking up the path
    current = leaf_hash
    for branch_hash in merkle_path:
        if current <= branch_hash:
            current = tagged_hash("TapBranch", current + branch_hash)
        else:
            current = tagged_hash("TapBranch", branch_hash + current)
    merkle_root = current

    # 5. Verify the tweaked pubkey matches
    tweak = tagged_hash("TapTweak", internal_key + merkle_root)
    expected_tweaked_key = point_add(point(internal_key), point_mul(G, tweak))
    output_key = scriptPubKey[2:]  # The 32-byte x-only key from OP_1 <key>

    if x_only(expected_tweaked_key) != output_key:
        return False  # Wrong key
    if has_even_y(expected_tweaked_key) != (parity == 0):
        return False  # Wrong parity

    # 6. Execute the tapscript
    return execute_tapscript(tapscript, script_inputs)

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.

☕ Help support TeachMeBitcoin

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:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!