第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)

技术实现关键点

  1. TapLeaf 哈希计算:每个脚本分别计算其 TapLeaf 哈希

  2. TapBranch 哈希计算:在按字典序排序两个 TapLeaf 哈希后计算 TapBranch 哈希

  3. 控制块构建:每个脚本需要包含其兄弟节点哈希作为 Merkle 证明

让我们通过实际链上交易数据深入理解这一切如何工作。

我们将基于两个真实测试网交易分析双叶脚本树的完整实现:

交易 1:哈希脚本路径支出#

  • 交易 IDb61857a05852482c9d5ffbb8159fc2ba1efa3dd16fe4595f121fc35878a2e430

  • Taproot 地址tb1p93c4wxsr87p88jau7vru83zpk6xl0shf5ynmutd9x0gxwau3tngq9a4w3z

  • 支出方法:Script Path(使用原像”helloworld”)

交易 2:Bob 脚本路径支出#

  • 交易 ID185024daff64cea4c82f129aa9a8e97b4622899961452d1d144604e65a70cfe0

  • Taproot 地址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=Truetapleaf_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 的私钥对应此公钥,签名对交易数据有效)

验证过程详情

  1. 从栈弹出公钥:84b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5

  2. 从栈弹出签名:26a0eadca0bba3d1bb6f82b8e1f76e2d84038c97a92fa95cc0b9f6a6a59bac5f...

  3. 使用 BIP340 Schnorr 签名验证算法验证签名有效性

  4. 验证成功,推送 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] 会验证失败。✅ 索引须一致。

调试 siblingcb = 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)

关键洞察

  1. Key Path 始终是最优选择:无论脚本树复杂度如何,Key Path 具有最高效率和隐私

  2. Script Path 成本可控:与传统复杂脚本相比,Taproot 的额外开销在可接受范围内

  3. 选择性揭示的价值:只有实际使用的路径被暴露,未使用的路径永远保持私有

通过双叶脚本树的完整实现,我们掌握了 Taproot 多路径支出的关键技术:真正的 Merkle 树构建、包含兄弟节点证明的控制块,以及同一地址内不同脚本的协调机制。更重要的是,我们理解了 Taproot 的核心哲学——选择性揭示,只暴露使用的路径,在复杂功能和高隐私之间实现完美平衡。

在下一章中,我们将探索多层嵌套脚本树高级 Taproot 应用模式,学习如何构建支持更多花费条件的企业级区块链应用,以及如何结合时间锁、多重签名和其他高级功能创建更复杂和实用的智能合约系统。

双叶脚本树是 Taproot 应用开发的重要里程碑——它们展示了如何在保持简单性的同时实现真正的功能复杂性。这就是 Bitcoin Taproot 技术的本质:外观简单,内在强大