進階
WTxIdRelay
了解 BIP-339 WTxIdRelay 協議,使用見證交易 ID 進行交易中繼以解決延展性問題。
10 分鐘
WTxIdRelay(BIP-339)是比特幣 P2P 協議的擴展,允許節點使用見證交易 ID(wtxid) 而非傳統交易 ID(txid)來中繼交易,解決了 SegWit 交易的延展性相關問題。
WTxIdRelay 概述
WTxIdRelay 解決的問題:
問題背景:
SegWit 交易有兩個 ID:
- txid: 不包含見證數據的雜湊
- wtxid: 包含見證數據的雜湊
傳統中繼使用 txid:
1. 節點 A 廣播 inv(txid)
2. 節點 B 回覆 getdata(txid)
3. A 發送交易
問題:
- 相同 txid 可能有不同的 wtxid
- 惡意節點可以修改見證數據
- 導致下載無效交易
WTxIdRelay 解決方案:
1. 使用 wtxid 識別交易
2. 無法修改見證後仍匹配
3. 確保下載的是期望的交易
BIP-339 定義:
- 2020 年引入
- Bitcoin Core 0.21.0 實現 協議機制
WTxIdRelay 協商:
版本握手後:
┌─────────┐ ┌─────────┐
│ Node A │ wtxidrelay │ Node B │
│ │ ──────────────→ │ │
│ │ wtxidrelay │ │
│ │ ←────────────── │ │
└─────────┘ └─────────┘
wtxidrelay 消息:
- 空的 payload(0 字節)
- 必須在 verack 之前發送
- 雙方都發送後才啟用
啟用後的交易中繼:
┌─────────┐ inv(wtxid) ┌─────────┐
│ Node A │ ──────────────→ │ Node B │
│ │ getdata(wtxid) │ │
│ │ ←────────────── │ │
│ │ tx │ │
│ │ ──────────────→ │ │
└─────────┘ └─────────┘
注意: inv 和 getdata 使用新的消息類型
MSG_WTX (0x00000005) 而非 MSG_TX (0x00000001) 消息格式
wtxidrelay 消息:
┌─────────────────────────────────────────┐
│ (empty payload - 0 bytes) │
└─────────────────────────────────────────┘
完整 P2P 消息:
┌──────────────────────────────────────────────┐
│ magic (4 bytes) │
│ command: "wtxidrelay" (12 bytes, padded) │
│ length: 0 (4 bytes) │
│ checksum: 0x5df6e0e2 (4 bytes) │
│ payload: (empty) │
└──────────────────────────────────────────────┘
使用 wtxid 的 inv:
┌─────────────────────────────────────────┐
│ count: 1 │
│ type: MSG_WTX (0x00000005) │
│ hash: wtxid (32 bytes) │
└─────────────────────────────────────────┘
使用 wtxid 的 getdata:
┌─────────────────────────────────────────┐
│ count: 1 │
│ type: MSG_WTX (0x00000005) │
│ hash: wtxid (32 bytes) │
└─────────────────────────────────────────┘ TXID vs WTXID
兩種交易 ID 的區別:
TXID (傳統):
- 雜湊範圍: 版本 + 輸入 + 輸出 + locktime
- 不包含: marker, flag, 見證數據
- SegWit 前後計算方式相同
WTXID (見證):
- 雜湊範圍: 整個序列化交易
- 包含: 所有數據包括見證
- 只對 SegWit 交易有意義
計算:
txid = SHA256(SHA256(
version || inputs || outputs || locktime
))
wtxid = SHA256(SHA256(
version || marker || flag ||
inputs || outputs || witness || locktime
))
特殊情況:
- 非 SegWit 交易: txid == wtxid
- Coinbase: wtxid 定義為全 0
範例:
交易原始數據不同部分:
- 基本數據相同 → txid 相同
- 見證數據不同 → wtxid 不同 延展性問題
WTxIdRelay 如何防止延展性攻擊:
攻擊場景(使用 txid):
1. 節點 A 廣播 inv(txid1)
2. 節點 B 請求 getdata(txid1)
3. 惡意節點 M 攔截請求
4. M 修改見證數據,創建 tx1'
- txid(tx1') == txid1 (相同)
- wtxid(tx1') != wtxid(tx1) (不同)
5. M 發送 tx1' 給 B
6. B 驗證失敗(無效簽名)
7. B 誤以為 txid1 是無效交易
8. 真正的 tx1 被阻止傳播
使用 WTxIdRelay:
1. 節點 A 廣播 inv(wtxid1)
2. 節點 B 請求 getdata(wtxid1)
3. 惡意節點 M 無法匹配
- 修改後的交易有不同 wtxid
- 無法響應 getdata(wtxid1)
4. B 繼續等待正確的交易
5. 攻擊被防止 向後兼容
WTxIdRelay 的兼容性處理:
協商結果:
雙方都支持:
- 使用 wtxid 中繼
- MSG_WTX 消息類型
只有一方支持:
- 回退到 txid 中繼
- MSG_TX 消息類型
實現邏輯:
class Peer {
bool m_wtxid_relay = false;
void OnWtxidRelay() {
if (state == BEFORE_VERACK) {
m_wants_wtxid_relay = true;
}
}
void OnVerack() {
if (m_wants_wtxid_relay && m_we_support_wtxid) {
m_wtxid_relay = true;
}
}
void RelayTransaction(const CTransaction& tx) {
if (m_wtxid_relay) {
SendInv(MSG_WTX, tx.GetWitnessHash());
} else {
SendInv(MSG_TX, tx.GetHash());
}
}
};
// 現代節點都支持 wtxidrelay Orphan 交易處理
WTxIdRelay 對孤立交易的影響:
孤立交易:
- 父交易尚未確認
- 暫時無法驗證
- 需要追蹤和重試
使用 txid(舊方式):
orphan_map[txid] = tx
// 問題: 相同 txid 可能是不同的交易
使用 wtxid(新方式):
orphan_map[wtxid] = tx
// 優勢: 精確識別特定交易
孤立解析:
// 父交易到達後
for (auto& orphan : orphans_by_prev) {
if (orphan.parent_txid == new_tx.txid) {
// 嘗試驗證
ValidateOrphan(orphan);
}
}
優化:
- 按父交易的 txid 索引孤立交易
- 收到父交易後可以快速找到孤立交易
- wtxid 用於精確追蹤 調試與監控
# 監控 WTxIdRelay 狀態
# 查看節點連接信息
bitcoin-cli getpeerinfo
[
{
"id": 1,
"addr": "...",
"services": "...",
"relaytxes": true,
"wtxidrelay": true, // 是否啟用 wtxidrelay
...
}
]
# 檢查所有節點的 wtxidrelay 狀態
bitcoin-cli getpeerinfo | jq '.[] | {id: .id, wtxid: .wtxidrelay}'
# 日誌設置
# bitcoin.conf
debug=net
# 日誌輸出:
# "Negotiated wtxidrelay with peer=5"
# "Sending wtxidrelay to peer=3"
# 計算交易的 txid 和 wtxid
bitcoin-cli decoderawtransaction <hex>
{
"txid": "abc123...",
"hash": "def456...", // 這是 wtxid
...
} 實現細節
Bitcoin Core 中的實現:
// 發送 wtxidrelay
void PeerManager::SendWtxidRelay(CNode& node) {
// 必須在 verack 之前
if (node.GetCommonVersion() >= WTXID_RELAY_VERSION) {
connman.PushMessage(&node,
CNetMsgMaker(node.GetCommonVersion())
.Make(NetMsgType::WTXIDRELAY));
}
}
// 處理 wtxidrelay
void PeerManager::ProcessWtxidRelay(CNode& node) {
if (node.fSuccessfullyConnected) {
// 太晚了,必須在 verack 之前
Misbehaving(node.GetId(), 1);
return;
}
node.m_wtxid_relay = true;
}
// 中繼交易
void PeerManager::RelayTransaction(const uint256& txid, const uint256& wtxid) {
for (auto& node : connman.GetNodes()) {
if (!node.fRelayTxes) continue;
if (node.m_wtxid_relay) {
// 使用 wtxid
node.PushInventory(CInv(MSG_WTX, wtxid));
} else {
// 回退到 txid
node.PushInventory(CInv(MSG_TX, txid));
}
}
}
const int WTXID_RELAY_VERSION = 70016; 相關概念
- WTXID:見證交易識別碼
- TX Malleability:交易延展性
- Transaction Broadcast:交易廣播機制
- SegWit:隔離見證
- Orphan Transactions:孤立交易處理
已複製連結