跳至主要內容
進階

WTXID

了解 Witness Transaction ID 的概念,以及它如何解決 SegWit 交易的唯一識別問題。

10 分鐘

WTXID(Witness Transaction ID)是 SegWit 引入的交易識別碼,它對整個交易 (包括見證數據)進行雜湊計算,提供了交易的完整標識。

TXID vs WTXID

在 SegWit 之前,交易只有一個識別碼 TXID。SegWit 引入了新的序列化格式, 需要兩種不同的識別方式:

特性 TXID WTXID
計算範圍 不含見證數據 含見證數據
用於 交易引用、outpoint 區塊 Merkle 樹
可延展性 不可變 見證可變則變
Legacy TX TXID = WTXID 相同

計算方式

// TXID 計算(不含見證數據)
TXID = SHA256(SHA256(
    version ||
    inputs ||
    outputs ||
    locktime
))

// WTXID 計算(含見證數據)
WTXID = SHA256(SHA256(
    version ||
    marker ||
    flag ||
    inputs ||
    outputs ||
    witness ||
    locktime
))

交易序列化格式

SegWit 交易有兩種序列化格式:

// 傳統格式(用於 TXID 計算)
[version][input count][inputs][output count][outputs][locktime]

// 見證格式(用於 WTXID 計算和網路傳輸)
[version][marker=0x00][flag=0x01][input count][inputs]
[output count][outputs][witness][locktime]

marker 和 flag:marker 為 0x00,flag 為 0x01。這個組合不會與 傳統交易混淆,因為傳統交易的 input count 不可能為 0。

Witness Merkle Tree

區塊中有兩棵 Merkle 樹:傳統的 TXID 樹和 WTXID 樹。

區塊頭
├── merkle_root (TXID 樹的根)
│   ├── coinbase_txid
│   ├── tx1_txid
│   └── tx2_txid
│
Coinbase 的 OP_RETURN
└── witness_commitment
    └── witness_merkle_root (WTXID 樹的根)
        ├── 0x00...00 (coinbase 的 WTXID 固定為 0)
        ├── tx1_wtxid
        └── tx2_wtxid

Witness Commitment

WTXID Merkle 根被嵌入在 coinbase 交易的輸出中:

// Witness commitment 結構
OP_RETURN
  0xaa21a9ed  // commitment 標識符
  SHA256(SHA256(witness_root || witness_reserved_value))

// 驗證邏輯
uint256 ComputeWitnessMerkleRoot(const std::vector& vtx)
{
    std::vector leaves;
    leaves.push_back(uint256());  // coinbase WTXID = 0
    for (size_t i = 1; i < vtx.size(); i++) {
        leaves.push_back(vtx[i]->GetWitnessHash());
    }
    return ComputeMerkleRoot(leaves);
}

RPC 查詢

# 獲取交易的 TXID 和 WTXID
bitcoin-cli getrawtransaction <txid> true

# 返回結果包含
{
  "txid": "abc123...",       // TXID
  "hash": "def456...",       // WTXID(在有見證數據時不同)
  "wtxid": "def456...",      // 同上,顯式字段
  ...
}

# 解碼原始交易
bitcoin-cli decoderawtransaction <hex>

# 獲取區塊的 witness commitment
bitcoin-cli getblock <blockhash> 2 | jq '.tx[0].vout[] | select(.scriptPubKey.asm | contains("OP_RETURN"))'

編程示例

import hashlib

def double_sha256(data):
    return hashlib.sha256(hashlib.sha256(data).digest()).digest()

def compute_txid(tx_bytes):
    """計算 TXID(不含見證數據)"""
    # 解析並移除見證數據
    stripped = strip_witness(tx_bytes)
    return double_sha256(stripped)[::-1].hex()

def compute_wtxid(tx_bytes):
    """計算 WTXID(含見證數據)"""
    return double_sha256(tx_bytes)[::-1].hex()

# 對於非 SegWit 交易
# TXID == WTXID

# 對於 SegWit 交易
# TXID != WTXID(因為見證數據)

使用場景

  • 交易引用:使用 TXID(保持向後兼容)
  • 區塊驗證:驗證兩棵 Merkle 樹
  • 交易中繼:INV 訊息可使用 WTXID
  • BIP-339 wtxidrelay:節點可以宣告使用 WTXID 進行交易中繼

BIP-339: WTXID Relay

BIP-339 引入了基於 WTXID 的交易中繼,提高了效率並減少了頻寬浪費:

// 握手時宣告支持
version message
  -> services: NODE_WITNESS

wtxidrelay message (空訊息,在 verack 之前發送)
  -> 表示將使用 WTXID 進行 INV/GETDATA

// 交易中繼
INV: MSG_WTX (0x05) + wtxid
GETDATA: MSG_WTX (0x05) + wtxid

為什麼需要兩種 ID?

保留 TXID 是為了向後兼容:

  • 現有錢包和交易所使用 TXID 追蹤交易
  • outpoint(輸入引用)使用 TXID
  • 鏈上合約可能依賴 TXID
  • WTXID 用於新的見證相關功能
已複製連結
已複製到剪貼簿