BIP-341: Taproot
Taproot specification. Key-path and script-path spending with improved privacy and efficiency.
| Type | Bitcoin Improvement Proposal |
| Number | bip-341 |
| Status | Final |
| Authors | Pieter Wuille, Jonas Nick, Anthony Towns |
| Original | https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki |
| Requires |
BIP-341: Taproot
Taproot is Bitcoin’s most significant upgrade since SegWit. It combines Schnorr signatures with Merkelized Abstract Syntax Trees (MAST) for improved privacy and flexibility.
Overview
Taproot allows spending via:
- Key path: Single Schnorr signature (most efficient)
- Script path: Reveal and satisfy one script from a tree
┌─────────────┐
│ Tweaked Key │
│ Q = P + H │
└─────────────┘
↑
┌─────────────────┴─────────────────┐
│ │
Key Path Script Path
(signature) (reveal + satisfy)
Output Structure
scriptPubKey:
OP_1 <32-byte tweaked pubkey>
Just 34 bytes—one of the smallest output types.
Address Format
Bech32m with prefix bc1p:
bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297
Key Tweaking
The output key Q is derived from:
- Internal key P
- Merkle root of scripts (or nothing)
t = tagged_hash("TapTweak", P || merkle_root)
Q = P + t × G
Key-Only Tweak
No scripts, just key spending:
t = tagged_hash("TapTweak", P)
Q = P + t × G
Key Path Spending
When all parties agree, spend with single signature:
Witness:
<schnorr signature>
That’s it—64 bytes.
Signing
def sign_key_path(internal_key, merkle_root, message):
# Compute tweaked private key
t = tagged_hash("TapTweak", internal_key.pubkey || merkle_root)
tweaked_key = internal_key + t
# Sign with Schnorr
return schnorr_sign(message, tweaked_key)
Benefits
- Privacy: Looks identical to single-sig
- Efficiency: Smallest witness possible
- Flexibility: Can still use script path if needed
Script Path Spending
Reveal one script from merkle tree:
Witness:
<script arguments>
<script>
<control block>
Control Block
<leaf version + parity> || <internal key P> || <merkle proof>
| Field | Size | Description |
|---|---|---|
| Leaf version | 1 byte | 0xc0 for Tapscript |
| Internal key | 32 bytes | P (x-only) |
| Merkle proof | 32×n bytes | Path to script |
Merkle Tree
Scripts organized in binary tree:
Root
/ \
AB CD
/ \ / \
A B C D
↓ ↓ ↓ ↓
s1 s2 s3 s4
Reveal only the script you’re using + proof path.
Tapscript (BIP-342)
Modified script rules for Taproot:
Changes from SegWit v0
| Feature | SegWit v0 | Tapscript |
|---|---|---|
| Signature | ECDSA | Schnorr |
| Multisig | OP_CHECKMULTISIG | OP_CHECKSIGADD |
| Max script | 10,000 bytes | Unlimited |
| Max ops | 201 | Unlimited |
| Annexes | N/A | Supported |
OP_CHECKSIGADD
Replaces OP_CHECKMULTISIG for efficient k-of-n:
<sig1> <pk1> OP_CHECKSIG
<sig2> <pk2> OP_CHECKSIGADD
<sig3> <pk3> OP_CHECKSIGADD
<3> OP_EQUAL
Fails fast on invalid signatures.
Example: 2-of-3 Multisig
Old Way (P2WSH)
OP_2 <pk1> <pk2> <pk3> OP_3 OP_CHECKMULTISIG
- Always reveals all 3 pubkeys
- 71-byte signatures × 2
- ~250 bytes witness
Taproot Way
Key path: MuSig2 aggregate signature (64 bytes)
Script path fallback: Three 2-of-2 scripts
Script 1: <pk1> CHECKSIG <pk2> CHECKSIGADD 2 EQUAL
Script 2: <pk1> CHECKSIG <pk3> CHECKSIGADD 2 EQUAL
Script 3: <pk2> CHECKSIG <pk3> CHECKSIGADD 2 EQUAL
Benefits:
- Cooperative case: 64 bytes (like single-sig)
- Non-cooperative: Reveals only 2 keys
- Better privacy in common case
Implementation
Creating Taproot Output
const bitcoin = require('bitcoinjs-lib');
// Internal key
const internalKey = Buffer.from('...', 'hex');
// No scripts (key-only)
const { address, output } = bitcoin.payments.p2tr({
internalPubkey: internalKey,
network: bitcoin.networks.bitcoin
});
With Script Tree
const { Taptree } = require('bitcoinjs-lib');
// Define scripts
const script1 = bitcoin.script.compile([...]);
const script2 = bitcoin.script.compile([...]);
// Build tree
const tree = [
{ output: script1 },
{ output: script2 }
];
const { address } = bitcoin.payments.p2tr({
internalPubkey: internalKey,
scriptTree: tree
});
Key Path Spend
const psbt = new bitcoin.Psbt();
psbt.addInput({
hash: txid,
index: vout,
witnessUtxo: { script: output, value: amount },
tapInternalKey: internalKey
});
psbt.addOutput({ address: recipient, value: sendAmount });
// Sign with tweaked key
psbt.signTaprootInput(0, tweakedSigner);
psbt.finalizeAllInputs();
Script Path Spend
psbt.addInput({
hash: txid,
index: vout,
witnessUtxo: { script: output, value: amount },
tapInternalKey: internalKey,
tapLeafScript: [{
leafVersion: 0xc0,
script: script1,
controlBlock: controlBlock
}]
});
// Provide script arguments in witness
psbt.updateInput(0, {
tapScriptSig: [{
pubkey: signerPubkey,
signature: schnorrSignature,
leafHash: leafHash
}]
});
Privacy Benefits
All Spends Look Similar
| Spend Type | On-Chain Appearance |
|---|---|
| Single-sig | bc1p… + 64-byte witness |
| 2-of-2 MuSig | bc1p… + 64-byte witness |
| Complex contract (key path) | bc1p… + 64-byte witness |
Only script-path reveals contract details.
MAST Hides Unused Paths
Complex contract with 10 conditions:
- Only reveal the one you use
- Other 9 remain private
- Observer can’t tell they exist
Fee Comparison
| Transaction Type | Witness Size |
|---|---|
| P2PKH | ~108 bytes |
| P2WPKH | ~108 bytes |
| P2TR key path | 64 bytes |
| P2TR script (simple) | ~100+ bytes |
Recommendation: Use key path when possible.
For Agents
When to Use Taproot
- ✅ New wallets (default choice)
- ✅ Multi-party setups (MuSig2)
- ✅ Complex contracts with likely key-path
- ✅ Privacy-sensitive applications
When P2WPKH May Be Better
- Legacy system compatibility
- Simple single-sig only needed
- Existing infrastructure constraints
Libraries
| Language | Library |
|---|---|
| JavaScript | bitcoinjs-lib v6+, @noble/secp256k1 |
| Rust | rust-bitcoin, bdk |
| Python | python-bitcoinlib (limited) |
Machine-Readable Summary
{
"bip": 341,
"title": "Taproot: SegWit version 1 spending rules",
"status": "final",
"activated": "2021-11-14",
"block_height": 709632,
"witness_version": 1,
"address_prefix": "bc1p",
"spending_paths": ["key-path", "script-path"],
"features": ["schnorr", "mast", "tapscript"],
"related_bips": [340, 342]
}