第8章:四叶 Taproot 脚本树#

参考: book/Chapter 08.md
代码示例: code/chapter08/
最后更新: 2025-12-06


引言:从理论到实践的飞跃#

在前几章中,我们掌握了 Taproot 的基本原理和双叶脚本树的实现。然而,真正的企业级应用需要更复杂的逻辑——四叶脚本树代表了当前 Taproot 技术在实际应用中的主流复杂度。

为什么四叶脚本树如此重要?#

大多数 Taproot 应用停留在简单的 key path 支出,这最大化隐私但使 Taproot 的大部分智能合约潜力未开发。四叶脚本树展示了简单实现中缺失的几个关键能力:

真实世界应用场景

  • 钱包恢复:具有时间锁 + 多重签名 + 紧急路径的渐进式访问控制

  • Lightning Network 通道:不同参与者集合的多个协作关闭场景

  • 原子交换:具有各种回退条件的哈希时间锁定合约

  • 继承规划:具有多受益人选项的基于时间的访问

技术优势

  • 选择性披露:只暴露执行的脚本,其他脚本保持隐藏

  • 费用效率:比等价的传统多条件脚本更小

  • 灵活逻辑:单个承诺内的多个执行路径

真实案例研究:测试网上的完整验证#

让我们通过真实案例研究分析在测试网上实现和验证的四叶脚本树的实际结构:

共享 Taproot 地址#

  • 地址tb1pjfdm902y2adr08qnn4tahxjvp6x5selgmvzx63yfqk2hdey02yvqjcr29q

  • 特性:使用相同地址的五种不同支出方法

脚本树设计#

                 Merkle Root
                /            \
        Branch0              Branch1
        /      \             /      \
   Script0   Script1    Script2   Script3
  (Hashlock) (Multisig)  (CSV)    (Sig)

四个脚本路径详情

  1. Script 0(SHA256 哈希锁):任何知道原像”helloworld”的人都可以花费

    • 实现原子交换中的哈希锁模式

    • 见证数据:[preimage, script, control_block]

  2. Script 1(2-of-2 多重签名):需要 Alice 和 Bob 协作

    • 使用 Tapscript 高效的 OP_CHECKSIGADD 而非传统 OP_CHECKMULTISIG

    • 见证数据:[bob_sig, alice_sig, script, control_block]

  3. Script 2(CSV 时间锁):Bob 在等待 2 个区块后可以花费

    • 实现相对时间锁

    • 见证数据:[bob_sig, script, control_block]

    • 关键:交易输入必须设置自定义序列值

  4. Script 3(简单签名):Bob 可以立即使用签名花费

    • 最简单的脚本路径

    • 见证数据:[bob_sig, script, control_block]

  5. Key Path:Alice 使用调整后的私钥进行最大隐私支出

    • 看起来像普通单签名交易

    • 见证数据:[alice_sig]

深入技术实现分析#

使用 bitcoinutils 构建四叶脚本树:先初始化密钥,再定义四个脚本,最后用嵌套列表指定树结构生成地址。

Python 实现框架#

alice_priv = PrivateKey("cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT")
bob_priv = PrivateKey.from_wif("cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG")
alice_pub = alice_priv.get_public_key()
bob_pub = bob_priv.get_public_key()

构建四个脚本#

# Script 0: Hashlock(需 import hashlib)
hash0 = hashlib.sha256(b"helloworld").hexdigest()
script0 = Script(['OP_SHA256', hash0, 'OP_EQUALVERIFY', 'OP_TRUE'])

# Script 1: 2-of-2 Multisig
script1 = Script(["OP_0", alice_pub.to_x_only_hex(), "OP_CHECKSIGADD",
                  bob_pub.to_x_only_hex(), "OP_CHECKSIGADD", "OP_2", "OP_EQUAL"])

# Script 2: CSV 时间锁
seq = Sequence(TYPE_RELATIVE_TIMELOCK, 2)
script2 = Script([seq.for_script(), "OP_CHECKSEQUENCEVERIFY", "OP_DROP",
                  bob_pub.to_x_only_hex(), "OP_CHECKSIG"])

# Script 3: 简单签名
script3 = Script([bob_pub.to_x_only_hex(), "OP_CHECKSIG"])

创建 Taproot 地址#

四叶树用 [[s0,s1],[s2,s3]] 指定 Branch0 与 Branch1:

tree = [[script0, script1], [script2, script3]]
taproot_address = alice_pub.get_taproot_address(tree)
# 四叶 Taproot 脚本树(btcaaron)
# 参考: examples/ch08_four_leaf_tree.py

from btcaaron import Key, TapTree

alice = Key.from_wif("cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT")
bob   = Key.from_wif("cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG")

# 四叶: hashlock | 2of2 multisig | CSV timelock | bob checksig
program = (TapTree(internal_key=alice)
    .hashlock("helloworld", label="hash")
    .multisig(2, [alice, bob], label="2of2")
    .timelock(blocks=2, then=bob, label="csv")
    .checksig(bob, label="bob")
).build()

print("=== 四叶 Taproot 树 ===")
print(f"地址:  {program.address}")
print(f"叶子:  {program.leaves}")
print(program.visualize())

# 五种支出路径示例
# 1. Hashlock
tx_h = program.spend("hash").from_utxo("245563c5aa4c6d32fc34eed2f182b5ed76892d13370f067dc56f34616b66c468", 0, sats=1200).to("tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne", 666).unlock(preimage="helloworld").build()
print(f"Hashlock TXID: {tx_h.txid}")
# 2. 2of2
tx_m = program.spend("2of2").from_utxo("1ed5a3e97a6d3bc0493acc2aac15011cd99000b52e932724766c3d277d76daac", 0, sats=1400).to("tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne", 668).sign(alice, bob).build()
print(f"2of2 Multisig TXID: {tx_m.txid}")
# 3. Key Path (Alice)
tx_k = program.keypath().from_utxo("42a9796a91cf971093b35685db9cb1a164fb5402aa7e2541ea7693acc1923059", 0, sats=2000).to("tb1p060z97qusuxe7w6h8z0l9kam5kn76jur22ecel75wjlmnkpxtnls6vdgne", 888).sign(alice).build()
print(f"Key Path TXID: {tx_k.txid}")
=== 四叶 Taproot 树 ===
地址:  tb1pjfdm902y2adr08qnn4tahxjvp6x5selgmvzx63yfqk2hdey02yvqjcr29q
叶子:  ['hash', '2of2', 'csv', 'bob']

        Merkle Root
       /            \
   Branch0        Branch1
   /      \       /      \
[hash]  [2of2] [csv]  [bob]

Hashlock TXID: 1ba4835fca1c94e7eb0016ce37c6de2545d07d84a97436f8db999f33a6fd6845
2of2 Multisig TXID: 1951a3be0f05df377b1789223f6da66ed39c781aaf39ace0bf98c3beb7e604a1
Key Path TXID: 1e518aa540bc770df549ec9836d89783ca19fc79b84e7407a882cbe9e95600da

Script Path 支出的核心实现#

五种支出路径的 bitcoinutils 关键逻辑(完整可运行代码见 examples/ch08_* 或上方 btcaaron cell)。

1. 哈希锁#

见证:[preimage, script, control_block]。TXID: 1ba4835f...

cb = ControlBlock(alice_pub, tree, 0, is_odd=taproot_address.is_odd())
tx.witnesses.append(TxWitnessInput(["helloworld".encode().hex(), script0.to_hex(), cb.to_hex()]))

2. 多重签名(2-of-2)#

见证顺序:Bob 签名在前(栈底,后消耗),Alice 在后。script_path=True。TXID: 1951a3be...

cb = ControlBlock(alice_pub, tree, 1, is_odd=taproot_address.is_odd())
# 签名时:script_path=True, tapleaf_script=script1
tx.witnesses.append(TxWitnessInput([sig_bob, sig_alice, script1.to_hex(), cb.to_hex()]))

3. CSV 时间锁#

关键TxInput(..., sequence=seq.for_input_sequence())。见证:[sig_bob, script, cb]。TXID: 98361ab2...

txin = TxInput(commit_txid, vout, sequence=seq.for_input_sequence())
cb = ControlBlock(alice_pub, tree, 2, is_odd=taproot_address.is_odd())
tx.witnesses.append(TxWitnessInput([sig_bob, script2.to_hex(), cb.to_hex()]))

4. 简单签名#

见证:[sig_bob, script, cb]。TXID: 1af46d4c...

cb = ControlBlock(alice_pub, tree, 3, is_odd=taproot_address.is_odd())
tx.witnesses.append(TxWitnessInput([sig_bob, script3.to_hex(), cb.to_hex()]))

5. Key Path(最大隐私)#

script_path=False,只提供 tapleaf_scripts=tree 供 tweak 计算。见证仅 [sig_alice]。TXID: 1e518aa5...

sig_alice = alice_priv.sign_taproot_input(..., script_path=False, tapleaf_scripts=tree)
tx.witnesses.append(TxWitnessInput([sig_alice]))

多重签名栈执行可视化:OP_CHECKSIGADD 创新#

在前几章中,我们熟悉了单签名脚本的栈执行过程。四叶脚本树引入了新挑战:2-of-2 多重签名脚本。这次我们使用 Tapscript 高效的 OP_CHECKSIGADD opcode。让我们详细分析其栈执行过程。

多重签名脚本结构#

script1 = Script(["OP_0", alice_pub.to_x_only_hex(), "OP_CHECKSIGADD",
                  bob_pub.to_x_only_hex(), "OP_CHECKSIGADD", "OP_2", "OP_EQUAL"])

见证顺序#

关键:Bob 签名在前(栈底、后消耗),Alice 在后(栈顶、先消耗)。

tx.witnesses.append(TxWitnessInput([sig_bob, sig_alice, script1.to_hex(), cb.to_hex()]))

栈执行动画:OP_CHECKSIGADD 如何工作#

执行脚本OP_0 [Alice_PubKey] OP_CHECKSIGADD [Bob_PubKey] OP_CHECKSIGADD OP_2 OP_EQUAL

初始状态:栈上的见证数据#

Stack State (bottom to top):
│ sig_alice     │ ← Stack top, consumed first
│ sig_bob       │ ← Consumed second by OP_CHECKSIGADD
└─────────────--┘

1. OP_0:初始化签名计数器#

Stack State:
│ 0           │ ← Counter initial value
│ sig_alice   │
│ sig_bob     │
└─────────────┘

2. [Alice_PubKey]:推送 Alice 公钥#

Stack State:
│ alice_pubkey│ ← Alice's 32-byte x-only public key
│ 0           │ ← Counter
│ sig_alice   │
│ sig_bob     │
└─────────────┘

3. OP_CHECKSIGADD:验证 Alice 签名并递增计数器#

Execution Process:
- Pop alice_pubkey
- Pop sig_alice (note: pop from lower layer)
- Verify signature: schnorr_verify(sig_alice, alice_pubkey, sighash)
- Pop counter 0
- Verification successful: push (0+1=1)

Stack State:
│ 1           │ ← Counter incremented to 1 ✅
│ sig_bob     │
└─────────────┘

4. [Bob_PubKey]:推送 Bob 公钥#

Stack State:
│ bob_pubkey  │ ← Bob's 32-byte x-only public key
│ 1           │ ← Current counter value
│ sig_bob     │
└─────────────┘

5. OP_CHECKSIGADD:再次验证 Bob 签名并递增计数器#

Execution Process:
- Pop bob_pubkey
- Pop sig_bob
- Verify signature: schnorr_verify(sig_bob, bob_pubkey, sighash)
- Pop counter 1
- Verification successful: push (1+1=2)

Stack State:
│ 2           │ ← Counter incremented to 2 ✅
└─────────────┘

6. OP_2:推送所需签名数量#

Stack State:
│ 2           │ ← Required signature count
│ 2           │ ← Actual verified signature count
└─────────────┘

7. OP_EQUAL:检查两个值是否相等#

Execution Process:
- Pop both 2s
- Compare: 2 == 2 is true
- Push 1 (indicating script execution success)
Final Stack State:
│ 1           │ ← Script execution success flag ✅
└─────────────┘

OP_CHECKSIGADD vs 传统 OP_CHECKMULTISIG#

技术优势对比

  1. 效率提升

    • OP_CHECKSIGADD:逐个验证,失败时立即停止

    • OP_CHECKMULTISIG:必须检查所有可能的签名组合

  2. 简化的栈操作

    • OP_CHECKSIGADD:清晰的计数器机制

    • OP_CHECKMULTISIG:复杂的栈操作和 off-by-one 问题

  3. 原生 x-only 公钥支持

    • OP_CHECKSIGADD:直接支持 32 字节 x-only 公钥

    • OP_CHECKMULTISIG:需要 33 字节压缩公钥

见证栈顺序#

OP_CHECKSIGADD 先消耗栈顶(alice_sig),再消耗栈底(bob_sig),故见证须 [sig_bob, sig_alice, ...]。❌ [sig_alice, sig_bob, ...] 会验证失败。

四叶控制块扩展#

四叶脚本树的控制块为 97 字节,包含两级 Merkle 证明。以交易 1951a3be0f05df377b1789223f6da66ed39c781aaf39ace0bf98c3beb7e604a1(Script 1 多重签名)为例:

见证栈结构:Bob 签名 → Alice 签名 → 多签脚本 → 97 字节控制块。

控制块字节布局

  • Byte 0:version (0xc0) + parity

  • Bytes 1–32:internal pubkey(Alice x-only)

  • Bytes 33–64:sibling 1(Script 0 的 TapLeaf 哈希)

  • Bytes 65–96:sibling 2(Branch 1 的 TapBranch 哈希)

Merkle 层次:Script1_TapLeaf + sibling1 → Branch0;Branch0 + sibling2 → Root;TapTweak(internal_pubkey ‖ root) → 输出公钥。

paths#

paths = {
    0: "[Script1_TapLeaf, Branch1_TapBranch]",  # Hashlock
    1: "[Script0_TapLeaf, Branch1_TapBranch]",  # Multisig
    2: "[Script3_TapLeaf, Branch0_TapBranch]",  # CSV
    3: "[Script2_TapLeaf, Branch0_TapBranch]"   # Simple Sig
}

字节解析#

cb_bytes = bytes.fromhex(cb_hex)
internal_pubkey = cb_bytes[1:33].hex()
sibling_1 = cb_bytes[33:65].hex()
sibling_2 = cb_bytes[65:97].hex()

关键技术洞察#

  1. 控制块结构:内部公钥 50be5fc4...,兄弟节点 1 fe78d852...(Script0),兄弟节点 2 da551975...(Branch1)。

  2. Merkle 层次:Script1 → Branch0(Script0, Script1) → Root(Branch0, Branch1)。

  3. 字典序:TapBranch 计算须按字典序排序。

  4. 验证链:控制块可完整证明脚本归属原始 Taproot 承诺。

# 可运行:解析交易 1951a3be... 的 97 字节控制块(仅用标准库)
cb_hex = "c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659eda55197526f26fa309563b7a3551ca945c046e5b7ada957e59160d4d27f299e3"
cb = bytes.fromhex(cb_hex)
print(f"控制块长度: {len(cb)} 字节")
print(f"Internal pubkey: {cb[1:33].hex()[:16]}...")
print(f"Sibling 1:       {cb[33:65].hex()[:16]}...")
print(f"Sibling 2:       {cb[65:97].hex()[:16]}...")

常见编程陷阱和解决方案#

1. 见证栈顺序问题#

多重签名的见证顺序至关重要:

# ❌ Wrong: Alice signature first
witness = [sig_alice, sig_bob, script, control_block]

# ✅ Correct: Bob signature first (consumed second)
witness = [sig_bob, sig_alice, script, control_block]

2. CSV 脚本序列值#

CSV 脚本需要特定的交易序列值:

# ❌ Wrong: Default sequence
txin = TxInput(txid, vout)

# ✅ Correct: CSV-compatible sequence
txin = TxInput(txid, vout, sequence=seq.for_input_sequence())

3. Script Path vs Key Path 签名#

两种路径的签名过程不同:

# Key path: script_path=False, provide tree for tweak
sig = priv.sign_taproot_input(..., script_path=False, tapleaf_scripts=tree)

# Script path: script_path=True, provide specific script
sig = priv.sign_taproot_input(..., script_path=True, tapleaf_script=script)

结论:本章技术总结#

本章通过四叶 Taproot 脚本树的完整实现,将前几章的基础知识扩展到了更接近实际应用的复杂度。

本章核心收获#

  1. 四叶 Merkle 树的两级证明结构:控制块从 65 字节(双叶)扩展到 97 字节,包含两个兄弟节点哈希,形成完整的两级 Merkle 证明链。

  2. OP_CHECKSIGADD 多签机制:Tapscript 引入的计数器式多签替代了传统 OP_CHECKMULTISIG,栈操作更清晰,效率更高,原生支持 x-only 公钥。

  3. CSV 时间锁的 sequence 处理TxInputsequence 参数必须与脚本中的 OP_CHECKSEQUENCEVERIFY 值匹配,这是一个容易遗漏的实现细节。

  4. 五种支出路径共享同一地址:五笔真实测试网交易验证了四叶脚本树的正确性,Key Path 支出在链上与普通单签名交易不可区分。

局限性说明#

  • 本章的四叶树使用均衡结构(两个分支各两片叶子)。实际应用中,应将高概率使用的脚本放在树的浅层,以减少 Merkle 证明大小和手续费。

  • 哈希锁脚本仍使用 OP_TRUE 结尾(参见第六章安全提示),生产环境应绑定签名验证。

  • 验证代码中的椭圆曲线点运算由库内部封装,未展示底层实现。

下一步#

掌握了四叶脚本树后,我们已经具备了理解更复杂 Bitcoin 协议的基础。后续章节将探讨这些技术在闪电网络、Ordinals 等真实协议中的应用。