跳至主要內容
高級

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:可證明不可花費的輸出
已複製連結
已複製到剪貼簿