P2TR MAST - Merklized Alternative Script Trees
Overview
MAST (Merklized Alternative Script Trees) is a technique that allows multiple spending conditions to be committed to a single output while only revealing the condition that is actually used at spending time. In Taproot (P2TR), MAST is implemented through a Merkle tree of scripts that is tweaked into the output public key.
The key insight is that complex Bitcoin contracts often have multiple spending paths, but typically only one path is ever executed. With MAST, the unused paths remain completely hidden, providing both privacy (observers cannot see what other conditions existed) and efficiency (only the executed script and its merkle proof are published on-chain).
A P2TR output commits to both an internal public key (for key-path spending) and an optional script tree (for script-path spending). The script tree uses Merkle tree construction so that only the executed branch needs to be revealed.
Script Tree Construction
The script tree is a binary Merkle tree where each leaf contains a spending script. The tree is constructed bottom-up, starting from the leaf scripts and combining them into branch nodes until a single root hash is produced.
Leaf Nodes (TapLeaf)
Each script is converted into a leaf hash using the TapLeaf tagged hash. The leaf hash commits to both the leaf version and the script content:
The leaf_version is currently 0xc0 for Tapscript (the default). This version byte allows for future script upgrades while maintaining backward compatibility. The compact size prefix indicates the length of the script that follows.
Branch Nodes (TapBranch)
Internal branch nodes are computed by hashing the concatenation of their two child hashes. Critically, the child hashes are lexicographically sorted before concatenation:
Sorting ensures that the same set of scripts always produces the same merkle root, regardless of the order in which they were inserted into the tree. This is essential for deterministic address generation and prevents subtle bugs where different implementations might produce different addresses for the same scripts.
Tagged Hashing
Taproot uses "tagged hashes" throughout its construction. A tagged hash prefixes the data with a tag-specific value to create domain separation:
The tag is hashed twice and prepended to the data. This technique prevents cross-protocol attacks where a valid hash in one context could be reinterpreted in another context. Each operation in Taproot uses a unique tag:
| Tag | Usage |
|---|---|
| TapLeaf | Hashing individual scripts into leaf nodes |
| TapBranch | Combining two child hashes into a branch node |
| TapTweak | Computing the tweak from internal key and merkle root |
| TapSighash | Computing the signature hash for Taproot spends |
Output Key Derivation
The output key Q that appears on-chain is derived from two components:
- The internal public key P.
- The merkle root m of the script tree.
This derivation uses elliptic curve point addition:
Step 1: Compute the Tweak
The tweak t is a 32-byte scalar derived from the concatenation of the internal key's x-coordinate and the merkle root.
Step 2: Apply the Tweak
The output key is computed by adding the internal key point P to the tweak scalar multiplied by the generator point G. This is standard elliptic curve point addition on secp256k1.
The output key Q commits to both the internal key and the entire script tree. Anyone who knows P and m can verify that Q was correctly derived, but without this knowledge, Q looks like any other public key.
Key-Path Only Outputs
If no script tree is needed (key-path only), the merkle root is omitted and the tweak is computed as:
This still applies a tweak to prevent certain attacks, but commits only to the internal key.
Spending Paths
A P2TR output can be spent in two ways:
- The key path (using the internal key).
- A script path (using one of the scripts in the tree).
Key Path Spend
The spender provides a Schnorr signature for the output key Q.
- Most efficient (single 64-byte signature).
- Indistinguishable from script path spends on-chain.
- Requires knowledge of the internal key's private key.
- Script tree remains completely hidden.
Witness Stack:
Script Path Spend
The spender reveals and executes one script from the tree.
- Requires merkle proof to the executed script.
- Only the used script is revealed.
- Other scripts remain hidden.
- Proof size is O(log n) for n scripts.
Witness Stack:
Control Block Structure
When spending via script path, the witness includes a "control block" that provides the merkle proof. The control block contains:
| Field | Size | Description |
|---|---|---|
| leaf_version + parity | 1 byte | Leaf version (0xc0 or 0xc1) with output key parity in the lowest bit |
| internal_key | 32 bytes | The x-only internal public key P |
| merkle_path | 32 × d bytes | Sibling hashes from leaf to root (d = tree depth) |
The control block size depends on the tree depth. For a balanced tree with n leaves, the merkle path contains log₂(n) hashes, each 32 bytes.
Verification Process
When a node receives a script path spend, it must verify that the revealed script is committed to by the output key. The key insight is that the control block contains only the sibling hashes needed for the merkle proof, not the hash of the script being spent.
Why the Spent Script Hash is Not in the Control Block
When spending Script B, the script itself is revealed separately in the witness as raw bytes. The verifier computes the hash of this revealed script using TapLeaf. The control block only needs to provide the sibling hashes along the merkle path, because everything else can be derived:
- Hash(Script A): Script B's sibling at the leaf level (from control block).
- Hash(Branch CD): Branch AB's sibling at the branch level (from control block).
This is standard merkle proof logic: you reveal the data you're proving (Script B), and provide only the sibling hashes. The verifier reconstructs the path from leaf to root, then verifies it matches the committed output key.
Verification Path (Script B Example)
Step-by-Step Verification
- Extract components: Parse the witness to get script inputs, the raw script, and the control block.
- Compute leaf hash: Hash the revealed script using TapLeaf(leaf_version, script). This produces Hash(Script B).
- Reconstruct Branch AB: Combine Hash(Script B) with Hash(Script A) from the control block using TapBranch (sorting lexicographically).
- Reconstruct Merkle Root: Combine Hash(Branch AB) with Hash(Branch CD) from the control block using TapBranch.
- Compute expected output key: Using the internal key P from the control block and the computed merkle root m, derive Q' = P + H(P||m)·G.
- Verify match: Check that Q' equals the actual on-chain output key Q (including y-coordinate parity from the control block's first byte).
- Execute script: If verification passes, execute the revealed script with the provided inputs.
If verification succeeds, the script was committed to at output creation time. No script can be "injected" after the fact because the merkle root is cryptographically bound to the output key.
Practical Examples
Example 1: 2-of-2 Multisig with Timeout Recovery
A common pattern for shared custody where two parties (Alice and Bob) normally spend together, but Alice can recover funds alone after a timeout period. This is useful for escrow, joint accounts, or any situation where cooperative spending is preferred but a fallback is needed.
How It Works
The internal key is a MuSig2 aggregated public key combining Alice and Bob's keys. When both parties cooperate, they produce a single Schnorr signature for the key path spend. This is the most efficient option (64-byte witness) and reveals nothing about the timeout fallback.
If Bob becomes unresponsive or uncooperative, Alice waits for the timelock (e.g., 52560 blocks ≈ 1 year) and then spends via Script B. The witness includes her signature, the script, and a control block with Hash(Script A) as the sibling proof. Script A's existence is revealed only as a hash, not its contents.
Script A provides a non-timelocked 2-of-2 multisig as backup in case MuSig2 signing fails (e.g., nonce generation issues). In practice, key path is always preferred, so Script A is rarely used. Placing the timeout script (more likely fallback) at the same tree level keeps proof sizes equal.
Example 2: Inheritance with Multiple Heirs
A sophisticated inheritance setup where different beneficiaries can claim funds after progressively longer timeouts. The owner retains full control via key path, and heirs only gain access if the owner becomes incapacitated or passes away.
Spending Priority and Privacy
The owner normally spends via key path, which is indistinguishable from any other Taproot transaction. The entire inheritance structure remains hidden. Heirs don't even know they're beneficiaries, providing protection against social engineering or coercion.
If the owner becomes incapacitated, heirs can claim in priority order based on timelock expiration:
- Spouse (1 year): First to gain access. If they claim, only Script A is revealed. The existence of Scripts B, C, D remains hidden (only their branch hashes appear in the control block).
- Child (2 years): Can claim if spouse doesn't act within 2 years. Reveals Script B, hides C and D.
- Lawyer (3 years): Estate executor fallback if family doesn't claim.
- Charity (5 years): Ultimate fallback preventing permanent loss of funds.
What Each Heir Sees
When the spouse claims via Script A, the on-chain witness reveals:
- Script A contents (spouse's key + 1-year timelock).
- Hash(Script B): proves Script B exists but not its contents.
- Hash(Branch CD): proves more scripts exist but reveals nothing about them.
The child, lawyer, and charity learn that two other spending conditions existed (from Hash(Script B) and Hash(Branch CD)), but cannot determine who or what they were. This preserves family privacy even after a claim.
In this example, the spouse script (most likely to be used) is placed at tree depth 2, same as all other scripts. For larger trees, you could place higher-priority heirs closer to the root to reduce their proof size. However, this would reveal information about priority ordering. The balanced tree approach treats all heirs equally in terms of on-chain footprint.
The owner can periodically move funds to a new P2TR output with updated timelocks, effectively resetting the countdown. This proves continued control and prevents heirs from counting down to access.