高級
Taproot Script Path
了解 Taproot script path 花費方式,使用 MAST 結構執行腳本條件。
12 分鐘
Taproot script path 允許通過執行腳本來花費輸出,使用 MAST(Merklized Alternative Script Trees) 結構,只需揭露實際使用的腳本分支,其他條件保持隱藏。
Script Path 概述
Taproot 腳本樹結構:
[Root]
/ \
[Branch] [Branch]
/ \ \
[Leaf A] [Leaf B] [Leaf C]
每個葉子是一個 TapScript:
- Leaf A: 2-of-3 多簽
- Leaf B: 時間鎖 + 單簽
- Leaf C: 哈希鎖
花費時只需揭露:
1. 使用的腳本
2. 該腳本的 Merkle 證明
3. 滿足腳本的數據(簽名等)
其他腳本保持隱藏 創建腳本樹
步驟 1: 定義葉子腳本
# TapScript(類似 SegWit v0 腳本)
script_a = [
pubkey_1, OP_CHECKSIG,
pubkey_2, OP_CHECKSIGADD,
pubkey_3, OP_CHECKSIGADD,
OP_2, OP_NUMEQUAL
] # 2-of-3 多簽
script_b = [
delay, OP_CHECKSEQUENCEVERIFY, OP_DROP,
pubkey_backup, OP_CHECKSIG
] # 時間鎖
步驟 2: 計算葉子雜湊
# 葉子雜湊 = tagged_hash("TapLeaf", version || script)
leaf_a = tagged_hash("TapLeaf", 0xc0 || compact_size(script_a) || script_a)
leaf_b = tagged_hash("TapLeaf", 0xc0 || compact_size(script_b) || script_b)
# 版本 0xc0 = leaf version,表示 TapScript
步驟 3: 構建 Merkle 樹
# 分支雜湊(排序後連接)
if leaf_a < leaf_b:
branch = tagged_hash("TapBranch", leaf_a || leaf_b)
else:
branch = tagged_hash("TapBranch", leaf_b || leaf_a)
# 這就是 Merkle root 計算輸出公鑰
將腳本樹嵌入公鑰:
P = 內部公鑰
m = Merkle root
# 計算調整
t = tagged_hash("TapTweak", P || m)
# 輸出公鑰
Q = P + t × G
# 創建 P2TR 輸出
scriptPubKey = OP_1 <Q.x>
# 注意:
# - 如果只有 key path(無腳本),m 為空
# - 有腳本時,m 是腳本樹的 Merkle root
# - Q 包含了對腳本樹的承諾 花費 Script Path
花費時需要提供:
1. 滿足腳本的數據
2. 腳本本身
3. 控制塊(Control Block)
見證結構:
witness = [
<script args>, # 滿足腳本的數據
<script>, # TapScript
<control_block> # 控制塊
]
控制塊結構:
┌───────────────────────────────────────────┐
│ leaf_version | parity (1 byte) │
│ internal_pubkey (32 bytes) │
│ merkle_path (32 bytes × n) │
└───────────────────────────────────────────┘
leaf_version: 腳本版本(0xc0 或 0xc1)
parity: 輸出公鑰 y 座標的奇偶性
internal_pubkey: 內部公鑰 P
merkle_path: 證明腳本在樹中的路徑 驗證流程
節點驗證 Script Path:
def verify_script_path(witness, output_pubkey):
# 解析見證
control_block = witness[-1]
script = witness[-2]
args = witness[:-2]
# 解析控制塊
leaf_version = control_block[0] & 0xfe
parity = control_block[0] & 0x01
internal_pubkey = control_block[1:33]
merkle_path = control_block[33:]
# 計算葉子雜湊
leaf_hash = tagged_hash("TapLeaf",
bytes([leaf_version]) + compact_size(script) + script)
# 驗證 Merkle 路徑
current = leaf_hash
for i in range(0, len(merkle_path), 32):
sibling = merkle_path[i:i+32]
if current < sibling:
current = tagged_hash("TapBranch", current + sibling)
else:
current = tagged_hash("TapBranch", sibling + current)
merkle_root = current
# 計算預期的輸出公鑰
t = tagged_hash("TapTweak", internal_pubkey + merkle_root)
expected_Q = internal_pubkey + t × G
# 驗證公鑰
if expected_Q.x != output_pubkey:
return False
# 執行腳本
return execute_tapscript(script, args) TapScript 操作碼
TapScript vs SegWit v0 腳本:
相同:
- 大多數操作碼相同
- 基本腳本邏輯相同
改進:
1. OP_CHECKSIGADD
- 取代 OP_CHECKMULTISIG
- 更高效的多簽
- 批量驗證友好
2. 簽名驗證
- 使用 Schnorr 簽名
- 64 字節(無 sighash 類型時)
3. 無 OP_CHECKMULTISIG
- 被 OP_CHECKSIGADD 取代
- 更靈活的多簽設計
4. 資源限制不同
- 無 201 操作碼限制
- 有 sigops 預算限制
範例 - 2-of-3 多簽:
# 舊方式
OP_2 <pk1> <pk2> <pk3> OP_3 OP_CHECKMULTISIG
# TapScript 方式
<pk1> OP_CHECKSIG
<pk2> OP_CHECKSIGADD
<pk3> OP_CHECKSIGADD
OP_2 OP_NUMEQUAL 樹結構優化
最優樹結構設計:
原則: 常用腳本放在較淺的位置
範例: 4 個腳本,使用概率不同
- Script A: 70% 概率使用
- Script B: 20%
- Script C: 8%
- Script D: 2%
不平衡樹(優化後):
[Root]
/ \
[A] [Branch]
/ \
[B] [Branch]
/ \
[C] [D]
深度:
- A: 1 (最常用,最淺)
- B: 2
- C: 3
- D: 3
見證大小(控制塊):
- A: 33 + 32 = 65 bytes
- B: 33 + 64 = 97 bytes
- C, D: 33 + 96 = 129 bytes
// 平衡樹所有深度都是 2,不夠優化 實際應用
場景: 閃電網路承諾交易
腳本樹設計:
[Root]
/ \
[合作關閉] [Branch]
/ \
[撤銷] [超時]
腳本:
1. 合作關閉 (Key Path)
- MuSig2 聚合簽名
- 最常用,最高效
2. 撤銷路徑
- <revocation_key> OP_CHECKSIG
- 對方作弊時使用
3. 超時路徑
- <delay> OP_CSV OP_DROP <local_key> OP_CHECKSIG
- 正常單方關閉
正常情況:
- 使用 Key Path(合作關閉)
- 看起來像普通交易
爭議情況:
- 使用 Script Path
- 揭露具體條件 查看 Script Path 交易
# 識別 Script Path 花費
bitcoin-cli getrawtransaction <txid> true
{
"vin": [{
"txinwitness": [
"3045...簽名", # 腳本參數
"20ab12...腳本", # TapScript
"c1a1b2...控制塊" # 控制塊
]
}]
}
# 識別方式:
# - witness 最後一個元素是控制塊
# - 控制塊以 0xc0 或 0xc1 開頭
# - 長度 = 33 + 32n (n = 樹深度)
# 解析控制塊
control_block = "c1" + "a1b2..." + "merkle_path..."
# c1 = leaf version 0xc0 + parity 1
# 接下來 32 字節是內部公鑰
# 剩餘是 Merkle 路徑 與 Key Path 的比較
| 特性 | Key Path | Script Path |
|---|---|---|
| 見證大小 | 64 bytes | 變化,較大 |
| 隱私 | 最佳 | 揭露使用的腳本 |
| 互動 | 需要(MuSig2) | 可以非互動 |
| 適用場景 | 合作/正常 | 爭議/備用 |
相關概念
- Taproot:Taproot 升級概述
- Key Path:密鑰路徑花費
- Miniscript:結構化腳本
- MAST:Merklized Abstract Syntax Trees
- Schnorr Signatures:Schnorr 簽名
已複製連結