第7章:Taproot 双叶脚本树#
参考:
book/Chapter 07.md
代码示例:code/chapter07/
最后更新: 2025-12-06
从单叶到双叶:Taproot 脚本树的真正力量#
在上一章中,我们通过 Alice 的哈希锁合约掌握了单叶 Taproot Script Path 的完整实现。然而,Taproot 的真正力量在于其多分支脚本树架构——能够在一个地址内优雅地组织多个不同的花费条件,实现复杂的有条件逻辑。
想象这个业务场景:Alice 想创建一个数字托管合约,既支持基于秘密信息的自动解锁(哈希锁),又为 Bob 提供直接私钥控制权限。在传统 Bitcoin 中,这需要复杂的多重签名脚本或多个独立地址。Taproot 的双叶脚本树可以优雅地将这两个条件整合到一个地址中:
Script Path 1:哈希锁脚本,任何知道”helloworld”的人都可以花费
Script Path 2:Bob 脚本,只有 Bob 的私钥持有者可以花费
Key Path:Alice 作为内部密钥持有者可以直接花费(最大隐私)
这种设计的优雅之处在于,外部观察者无法区分这是简单支付还是复杂的三路径有条件合约。只有在实际花费时,使用的路径才会被选择性揭示。
与直接使用 TapLeaf 哈希作为 Merkle 根的单叶树不同,双叶脚本树需要构建真正的 Merkle 树:
Merkle Root
/ \
TapLeaf A TapLeaf B
(Hash Script) (Bob Script)
技术实现关键点:
TapLeaf 哈希计算:每个脚本分别计算其 TapLeaf 哈希
TapBranch 哈希计算:在按字典序排序两个 TapLeaf 哈希后计算 TapBranch 哈希
控制块构建:每个脚本需要包含其兄弟节点哈希作为 Merkle 证明
让我们通过实际链上交易数据深入理解这一切如何工作。
我们将基于两个真实测试网交易分析双叶脚本树的完整实现:
交易 1:哈希脚本路径支出#
交易 ID:
b61857a05852482c9d5ffbb8159fc2ba1efa3dd16fe4595f121fc35878a2e430Taproot 地址:
tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z支出方法:Script Path(使用原像”helloworld”)
交易 2:Bob 脚本路径支出#
交易 ID:
185024daff64cea4c82f129aa9a8e97b4622899961452d1d144604e65a70cfe0Taproot 地址:
tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z支出方法:Script Path(使用 Bob 的私钥签名)
注意,这两笔交易使用完全相同的 Taproot 地址,证明它们确实来自同一个双叶脚本树!
Commit 阶段:双叶脚本树构建#
扁平结构 [hash_script, bob_script]:hash_script 索引 0,bob_script 索引 1。(完整可运行代码见上方 btcaaron cell。)
脚本构建#
# 需 import hashlib,及 bitcoinutils 的 Script、PrivateKey、get_taproot_address
preimage_hash = hashlib.sha256(b"helloworld").hexdigest()
hash_script = Script(['OP_SHA256', preimage_hash, 'OP_EQUALVERIFY', 'OP_TRUE'])
bob_script = Script([bob_pub.to_x_only_hex(), 'OP_CHECKSIG'])
地址生成#
all_leafs = [hash_script, bob_script]
taproot_address = alice_pub.get_taproot_address(all_leafs)
# 双叶 Taproot 脚本树(btcaaron)
# 参考: examples/ch07_dual_leaf_tree.py
from btcaaron import Key, TapTree
alice = Key.from_wif("cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT")
bob = Key.from_wif("cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG")
# 双叶树: [hashlock] | [bob checksig]
program = (TapTree(internal_key=alice)
.hashlock("helloworld", label="hash")
.checksig(bob, label="bob")
).build()
print("=== 双叶 Taproot 树 ===")
print(f"地址: {program.address}")
print(f"叶子: {program.leaves}")
print(program.visualize())
# 哈希锁路径支出
tx_hash = (program.spend("hash")
.from_utxo("f02c055369812944390ca6a232190ec0db83e4b1b623c452a269408bf8282d66", 0, sats=1234)
.to("tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne", 1034)
.unlock(preimage="helloworld")
.build())
print(f"\n哈希锁 TXID: {tx_hash.txid}")
# Bob 签名路径支出
tx_bob = (program.spend("bob")
.from_utxo("8caddfad76a5b3a8595a522e24305dc20580ca868ef733493e308ada084a050c", 1, sats=1111)
.to("tb1pshzcvake3a3d76jmue3jz4hyh35yvk0gjj752pd53ys9txy5c3aswe5cn7", 900)
.sign(bob)
.build())
print(f"Bob 签名 TXID: {tx_bob.txid}")
=== 双叶 Taproot 树 ===
地址: tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z
叶子: ['hash', 'bob']
Merkle Root
/ \
[hash] [bob]
哈希锁 TXID: 51fbb47cd777798afe1a0d0d55b5f128e6396013a6c3099eb5ad10f3624c7dbf
Bob 签名 TXID: a7dd4e294374bdece6eb6412c58974fe1907b90d064ff792a749303cf54c6d2c
Reveal 阶段:两种 Script Path 支出#
1. 哈希脚本路径(索引 0)#
见证:[preimage_hex, script, control_block]。TXID: b61857a0...
cb = ControlBlock(alice_pub, all_leafs, 0, is_odd=taproot_address.is_odd())
tx.witnesses.append(TxWitnessInput(["helloworld".encode().hex(), hash_script.to_hex(), cb.to_hex()]))
2. Bob 脚本路径(索引 1)#
见证:[sig, script, control_block]。关键:script_path=True,tapleaf_script=bob_script(单数),tweak=False。TXID: 185024da...
cb = ControlBlock(alice_pub, all_leafs, 1, is_odd=taproot_address.is_odd())
sig = bob_priv.sign_taproot_input(..., script_path=True, tapleaf_script=bob_script, tweak=False)
tx.witnesses.append(TxWitnessInput([sig, bob_script.to_hex(), cb.to_hex()]))
对比:Hash 路径用原像验证;Bob 路径用 Schnorr 签名,控制块分别包含兄弟节点 TapLeaf 哈希。
在双叶脚本树中,每个脚本的控制块包含其兄弟节点哈希作为 Merkle 证明。让我们分析实际链上数据:
哈希脚本路径控制块#
从交易 b61857a0… 提取的数据:
Control Block: c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d32faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9df
Structure breakdown:
├─ c0: Leaf version (0xc0)
├─ 50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3: Alice internal pubkey
└─ 2faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9df: Bob Script's TapLeaf hash
Bob 脚本路径控制块#
从交易 185024da… 提取的数据:
Control Block: c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e
Structure breakdown:
├─ c0: Leaf version (0xc0)
├─ 50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3: Alice internal pubkey (same!)
└─ fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e: Hash Script's TapLeaf hash
重要观察:两控制块共用同一内部公钥;Merkle 路径为兄弟 TapLeaf 哈希。
双叶控制块结构(65 字节)#
Byte 0:version+parity;1–32:internal pubkey;33–64:sibling TapLeaf hash。
cb = bytes.fromhex(control_block_hex)
internal_pubkey = cb[1:33].hex()
sibling = cb[33:65].hex()
# 可运行:解析双叶控制块(65 字节,交易 b61857a0... 哈希路径)
cb_hex = "c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d32faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9df"
cb = bytes.fromhex(cb_hex)
print(f"控制块长度: {len(cb)} 字节")
print(f"Internal pubkey: {cb[1:33].hex()[:16]}...")
print(f"Sibling (Bob TapLeaf): {cb[33:65].hex()[:16]}...")
控制块长度: 65 字节
Internal pubkey: 50be5fc44ec580c3...
Sibling (Bob TapLeaf): 2faaa677cb6ad6a7...
现在让我们详细分析哈希脚本路径的完整执行过程。基于交易 b61857a0... 的实际数据:
见证数据结构#
Witness Stack:
[0] 68656c6c6f776f726c64 (preimage_hex)
[1] a820936a185c...8851 (script_hex)
[2] c050be5fc4...cf9df (control_block)
脚本字节码解析#
哈希脚本:a820936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af8851
Bytecode breakdown:
a8 = OP_SHA256
20 = OP_PUSHBYTES_32
936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af = SHA256("helloworld")
88 = OP_EQUALVERIFY
51 = OP_PUSHNUM_1 (OP_TRUE)
栈执行动画——哈希脚本路径#
执行脚本:OP_SHA256 OP_PUSHBYTES_32 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af OP_EQUALVERIFY OP_PUSHNUM_1
0. 初始状态:加载脚本输入#
│ 68656c6c6f776f726c64 (preimage_hex) │
└──────────────────────────────────────┘
(原像”helloworld”的十六进制表示已在栈上)
1. OP_SHA256:计算栈顶元素的 SHA256 哈希#
│ 936a185c...07af (computed_hash) │
└─────────────────────────────────┘
(SHA256(“helloworld”) = 936a185c…07af)
2. OP_PUSHBYTES_32:推送预期哈希值#
│ 936a185c...07af (expected_hash) │
│ 936a185c...07af (computed_hash) │
└─────────────────────────────────┘
(栈顶现在有两个相同的哈希值)
3. OP_EQUALVERIFY:验证哈希相等#
│ (empty_stack) │
└───────────────┘
(验证成功:expected_hash == computed_hash,两个元素都被移除)
4. OP_PUSHNUM_1:推送成功标志#
│ 01 (true_value) │
└─────────────────┘
(脚本执行成功:栈顶是非零值)
接下来,让我们分析 Bob 脚本路径的执行过程。基于交易 185024da... 的实际数据:
见证数据结构#
Witness Stack:
[0] 26a0eadc...f9f1c5c (bob_signature)
[1] 2084b59516...63af5ac (script_hex)
[2] c050be5fc4...0f659e (control_block)
脚本字节码解析#
Bob 脚本:2084b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5ac
Bytecode breakdown:
20 = OP_PUSHBYTES_32
84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5 = Bob's x-only pubkey
ac = OP_CHECKSIG
栈执行动画——Bob 脚本路径#
执行脚本:OP_PUSHBYTES_32 84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5 OP_CHECKSIG
0. 初始状态:加载脚本输入#
│ 26a0eadc...f9f1c5c (bob_signature) │
└────────────────────────────────────┘
(Bob 的 64 字节 Schnorr 签名已在栈上)
1. OP_PUSHBYTES_32:推送 Bob 的 x-only 公钥#
│ 84b59516...eef63af5 (bob_pubkey) │
│ 26a0eadc...f9f1c5c (bob_signature) │
└────────────────────────────────────┘
(Bob 的 32 字节 x-only 公钥被推送到栈顶)
2. OP_CHECKSIG:验证 Schnorr 签名#
│ 01 (signature_valid) │
└──────────────────────┘
(签名验证成功:Bob 的私钥对应此公钥,签名对交易数据有效)
验证过程详情:
从栈弹出公钥:
84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5从栈弹出签名:
26a0eadca0bba3d1bb6f82b8e1f76e2d84038c97a92fa95cc0b9f6a6a59bac5f...使用 BIP340 Schnorr 签名验证算法验证签名有效性
验证成功,推送 1 表示 TRUE
通过比较单叶和双叶实现,我们可以清楚地看到 Merkle 树计算的差异:
单叶脚本树#
Merkle Root = TapLeaf Hash
= Tagged_Hash("TapLeaf", 0xc0 + len(script) + script)
特征:
简单直接,TapLeaf 哈希作为 Merkle 根
控制块仅包含内部公钥,无 Merkle 路径
适用于简单的单条件验证场景
双叶脚本树#
Merkle Root = TapBranch Hash
= Tagged_Hash("TapBranch", sorted(TapLeaf_A, TapLeaf_B))
TapLeaf_A = Tagged_Hash("TapLeaf", 0xc0 + len(script_A) + script_A)
TapLeaf_B = Tagged_Hash("TapLeaf", 0xc0 + len(script_B) + script_B)
特征:
真正的 Merkle 树结构,需要 TapBranch 计算
字典序排序确保确定性结果
控制块包含兄弟节点哈希作为 Merkle 证明
支持复杂的多条件验证场景
控制块大小对比#
Script Tree Type |
Control Block Size |
Structure |
|---|---|---|
Single-leaf |
33 bytes |
[version+parity] + [internal_pubkey] |
Dual-leaf |
65 bytes |
[version+parity] + [internal_pubkey] + [sibling_hash] |
Four-leaf |
97 bytes |
[version+parity] + [internal_pubkey] + [sibling_hash] + [parent_sibling_hash] |
随着脚本树深度增加,控制块线性增长,但仍比传统多重签名脚本高效得多。
编程最佳实践#
1. Commit 阶段#
leafs = [hash_script, bob_script] # 索引顺序固定
taproot_address = alice_key.get_taproot_address(leafs)
2. Script Path 支出模板#
control_block = ControlBlock(internal_key, leafs, script_index, is_odd=taproot_addr.is_odd())
witness = TxWitnessInput([*input_data, leafs[script_index].to_hex(), control_block.to_hex()])
3. 常见错误#
脚本索引不一致:❌ ControlBlock(..., 1, ...) 搭配 leafs[0] 会验证失败。✅ 索引须一致。
调试 sibling:cb = bytes.fromhex(cb_hex); actual = cb[33:65],与预期 TapLeaf 哈希对比。
通过实际链上数据,我们可以定量分析不同支出方法的性能和隐私特征:
Spending Method |
Transaction Size |
Witness Data |
Computational Complexity |
Privacy Level |
Relative Fee Cost |
|---|---|---|---|---|---|
Key Path |
~110 bytes |
64-byte signature |
1 signature verification |
Complete privacy |
Baseline (1.0x) |
Hash Script |
~180 bytes |
preimage+script+cb |
Hash calculation+Merkle verification |
Exposes Hash Lock |
Medium (1.6x) |
Bob Script |
~185 bytes |
signature+script+cb |
Signature verification+Merkle verification |
Exposes P2PK structure |
Medium (1.7x) |
关键洞察:
Key Path 始终是最优选择:无论脚本树复杂度如何,Key Path 具有最高效率和隐私
Script Path 成本可控:与传统复杂脚本相比,Taproot 的额外开销在可接受范围内
选择性揭示的价值:只有实际使用的路径被暴露,未使用的路径永远保持私有
通过双叶脚本树的完整实现,我们掌握了 Taproot 多路径支出的关键技术:真正的 Merkle 树构建、包含兄弟节点证明的控制块,以及同一地址内不同脚本的协调机制。更重要的是,我们理解了 Taproot 的核心哲学——选择性揭示,只暴露使用的路径,在复杂功能和高隐私之间实现完美平衡。
在下一章中,我们将探索多层嵌套脚本树和高级 Taproot 应用模式,学习如何构建支持更多花费条件的企业级区块链应用,以及如何结合时间锁、多重签名和其他高级功能创建更复杂和实用的智能合约系统。
双叶脚本树是 Taproot 应用开发的重要里程碑——它们展示了如何在保持简单性的同时实现真正的功能复杂性。这就是 Bitcoin Taproot 技术的本质:外观简单,内在强大。