高級
Witness Commitment
了解 SegWit 見證承諾如何嵌入 Coinbase 交易,確保見證數據的完整性。
10 分鐘
見證承諾(Witness Commitment)是 SegWit 升級的關鍵組件,將區塊中所有交易的 見證數據摘要嵌入 Coinbase 交易的輸出中,確保見證數據的完整性和可驗證性。
為什麼需要見證承諾?
SegWit 的向後兼容設計:
舊節點視角:
- 區塊頭的 Merkle Root 只包含交易(不含見證)
- 舊節點可以驗證區塊頭
- 不需要下載見證數據
問題:
- 如果見證數據不被承諾,可能被篡改
- 完整節點需要驗證見證數據完整性
解決方案:
- 在 Coinbase 輸出中嵌入見證承諾
- 全節點可以驗證見證數據
- 舊節點忽略此承諾(軟分叉兼容) 承諾結構
見證承諾位於 Coinbase 交易的一個 OP_RETURN 輸出:
輸出腳本格式:
OP_RETURN <commitment>
commitment (38 字節):
┌──────────────────────────────────────────────────────┐
│ 0x6a (OP_RETURN) │
│ 0x24 (36 字節推送) │
│ 0xaa21a9ed (見證承諾標識符,4 字節) │
│ commitment_hash (32 字節) │
└──────────────────────────────────────────────────────┘
標識符 0xaa21a9ed:
- 固定前綴,用於識別見證承諾
- 與普通 OP_RETURN 數據區分 承諾計算
見證承諾計算過程:
1. 計算見證 Merkle Root
- 使用 WTXID 而非 TXID
- Coinbase 的 WTXID 設為全零
- 計算 Merkle Tree
2. 連接見證保留值
witness_reserved_value = Coinbase witness 的第一項
通常為 32 字節的 0x00
3. 計算承諾
commitment = SHA256(SHA256(
witness_merkle_root || witness_reserved_value
))
// 偽代碼
def calculate_witness_commitment(block):
wtxids = []
for tx in block.transactions:
if tx.is_coinbase:
wtxids.append(bytes(32)) # 全零
else:
wtxids.append(tx.wtxid)
witness_root = merkle_root(wtxids)
reserved = block.coinbase.witness[0] # 通常 32 字節 0
return sha256d(witness_root + reserved) 雙 Merkle 樹結構
SegWit 區塊有兩棵 Merkle 樹:
1. 交易 Merkle 樹 (區塊頭中):
- 使用 TXID
- 不包含見證數據
- 舊節點可驗證
merkle_root = merkle(txid_1, txid_2, ...)
↓
區塊頭
2. 見證 Merkle 樹 (Coinbase 中):
- 使用 WTXID
- 包含見證數據
- 只有新節點驗證
witness_root = merkle(wtxid_0=0, wtxid_1, wtxid_2, ...)
↓
commitment = hash(witness_root || reserved)
↓
Coinbase OP_RETURN
這種設計實現了軟分叉兼容性 Coinbase 見證
Coinbase 交易的特殊見證:
SegWit 前:
- Coinbase 沒有真正的輸入
- 沒有見證數據
SegWit 後:
- Coinbase 有一個見證項
- 見證保留值 (witness reserved value)
- 通常是 32 字節的零
- 用於未來擴展
結構:
{
"txid": "...",
"vin": [{
"coinbase": "...",
"sequence": 0xffffffff,
"txinwitness": [
"0000000000000000000000000000000000000000000000000000000000000000"
]
}],
"vout": [
{"value": 6.25, "scriptPubKey": "..."}, // 獎勵
{"value": 0, "scriptPubKey": "6a24aa21a9ed..."} // 見證承諾
]
} 驗證過程
完整節點驗證見證承諾:
1. 找到 Coinbase 的見證承諾輸出
- 搜索 OP_RETURN 輸出
- 檢查 0xaa21a9ed 前綴
- 如果有多個,使用最後一個
2. 提取見證保留值
- 從 Coinbase witness[0] 獲取
- 必須是 32 字節
3. 計算預期承諾
- 構建見證 Merkle 樹
- 計算承諾雜湊
4. 比較
- 預期承諾 == 實際承諾
- 不匹配則區塊無效
// 驗證代碼
def verify_witness_commitment(block):
# 找承諾
commitment = find_witness_commitment(block.coinbase)
if not commitment:
# 如果區塊有 SegWit 交易但無承諾,無效
if has_witness_tx(block):
return False
return True
# 計算預期值
expected = calculate_witness_commitment(block)
return commitment == expected 查看實際區塊
# 獲取區塊的 Coinbase 交易
bitcoin-cli getblock <blockhash> 2 | jq '.tx[0]'
# 查看見證承諾輸出
{
"vout": [
{
"value": 0.00000000,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN aa21a9ed...",
"hex": "6a24aa21a9ed...",
"type": "nulldata"
}
}
],
"vin": [
{
"txinwitness": [
"0000000000000000000000000000000000000000000000000000000000000000"
]
}
]
}
# 解析承諾
6a = OP_RETURN
24 = 36 字節數據
aa21a9ed = 見證承諾標識符
後面 32 字節 = 實際承諾雜湊 見證保留值的用途
見證保留值 (witness reserved value):
當前用途:
- 通常設為 32 字節的零
- 不影響承諾計算的有效性
未來擴展:
- 可以包含額外的承諾
- 例如: 擴展區塊承諾
- 軟分叉升級的擴展點
設計考量:
- 允許礦工包含任意數據
- 不需要共識改變即可使用
- 類似 Coinbase scriptSig 的彈性
潛在應用:
- 側鏈承諾
- 額外協議數據
- 礦工消息 與 SPV 的關係
SPV 節點和見證承諾:
傳統 SPV:
- 只下載區塊頭
- 使用 Merkle 證明驗證交易
- 不需要見證數據
SegWit SPV:
- 可以選擇下載見證數據
- 使用見證承諾驗證完整性
- 需要 Coinbase 交易來獲取承諾
驗證 SegWit 交易:
1. 獲取區塊頭
2. 獲取 Coinbase 交易(包含見證承諾)
3. 獲取目標交易的 Merkle 證明
4. 如果需要見證,獲取見證 Merkle 證明
// Neutrino (BIP-157/158) 簡化了這個過程 程式實現
import hashlib
def sha256d(data):
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
def merkle_root(hashes):
if len(hashes) == 0:
return bytes(32)
while len(hashes) > 1:
if len(hashes) % 2 == 1:
hashes.append(hashes[-1])
hashes = [sha256d(hashes[i] + hashes[i+1])
for i in range(0, len(hashes), 2)]
return hashes[0]
def calc_witness_commitment(wtxids, reserved_value):
"""計算見證承諾"""
# Coinbase WTXID 設為零
wtxids[0] = bytes(32)
witness_root = merkle_root(wtxids)
return sha256d(witness_root + reserved_value)
def find_witness_commitment(coinbase_tx):
"""從 Coinbase 找見證承諾"""
MARKER = bytes.fromhex('aa21a9ed')
commitment = None
for out in coinbase_tx.vout:
script = out.scriptPubKey
# OP_RETURN + 36 字節數據
if (len(script) >= 38 and
script[0] == 0x6a and
script[1] == 0x24 and
script[2:6] == MARKER):
commitment = script[6:38]
return commitment # 返回最後一個匹配的
def verify_witness_commitment(block):
"""驗證區塊的見證承諾"""
coinbase = block.transactions[0]
commitment = find_witness_commitment(coinbase)
if not commitment:
# 檢查是否有 SegWit 交易
for tx in block.transactions[1:]:
if tx.has_witness:
return False # 無承諾但有見證交易
return True
# 計算預期承諾
wtxids = [tx.wtxid for tx in block.transactions]
reserved = coinbase.witness[0] if coinbase.witness else bytes(32)
expected = calc_witness_commitment(wtxids, reserved)
return commitment == expected 歷史演進
見證承諾的發展:
BIP-141 (SegWit):
- 定義了見證承諾格式
- 2017 年 8 月啟用
設計選擇:
1. 為什麼用 OP_RETURN?
- 可證明不可花費
- 不污染 UTXO 集
- 舊節點直接忽略
2. 為什麼在 Coinbase?
- 每個區塊必有 Coinbase
- 確保承諾被打包
- 礦工有控制權
3. 為什麼要見證保留值?
- 未來擴展性
- 允許額外承諾
- 不需要硬分叉 相關概念
- SegWit:隔離見證升級
- WTXID:包含見證的交易識別碼
- Merkle Tree:交易的樹狀承諾結構
- Coinbase:區塊的第一筆交易
- OP_RETURN:可證明不可花費的輸出
已複製連結