跳至主要內容
進階

SPV

深入了解簡化支付驗證(SPV)的原理、實現和安全性考慮。

12 分鐘

什麼是 SPV?

簡化支付驗證(Simplified Payment Verification)是中本聰在比特幣白皮書中描述的輕量級驗證方法。 SPV 客戶端只需下載區塊頭(約 60 MB)而非完整區塊鏈(600+ GB),即可驗證交易是否被確認。

全節點 vs SPV

全節點

  • • 下載所有區塊(600+ GB)
  • • 驗證所有交易和區塊
  • • 維護完整 UTXO 集
  • • 最高安全性

SPV 客戶端

  • • 只下載區塊頭(~60 MB)
  • • 驗證區塊頭和 Merkle 證明
  • • 依賴全節點提供交易數據
  • • 適合移動設備

工作原理

SPV 驗證流程:

1. 同步區塊頭
   ┌─────────────────────────────────────────────────────────────┐
   │ 區塊頭(80 bytes)                                          │
   │ ├── version (4 bytes)                                       │
   │ ├── prev_block_hash (32 bytes)                              │
   │ ├── merkle_root (32 bytes)    ← 用於驗證交易                 │
   │ ├── timestamp (4 bytes)                                     │
   │ ├── bits (4 bytes)                                          │
   │ └── nonce (4 bytes)                                         │
   └─────────────────────────────────────────────────────────────┘

2. 驗證區塊頭鏈
   ├── 檢查 PoW(hash < target)
   ├── 驗證區塊連接(prev_hash)
   └── 選擇最長有效鏈

3. 請求交易的 Merkle 證明
   ├── 從全節點獲取
   └── 包含必要的兄弟節點哈希

4. 驗證 Merkle 證明
   ├── 計算路徑上的哈希
   └── 比對區塊頭中的 merkle_root

Merkle 證明

Merkle 證明示例:

假設區塊包含 8 筆交易,驗證 Tx2:

              Root(在區塊頭中)
             /              \
          H01                H23
         /    \            /    \
       H0      H1        H2      H3
       |       |         |       |
      Tx0    Tx1       Tx2*    Tx3

驗證 Tx2 需要的證明:
├── Tx2 本身
├── H3 = Hash(Tx3)        ← 兄弟節點
├── H01 = Hash(H0 || H1)  ← 兄弟節點
└── 計算:
    H2 = Hash(Tx2)
    H23 = Hash(H2 || H3)
    Root = Hash(H01 || H23)
    比對區塊頭的 merkle_root

證明大小:O(log n)
├── 8 筆交易 → 3 個哈希
├── 1024 筆交易 → 10 個哈希
└── 每個哈希 32 bytes
import hashlib

def double_sha256(data: bytes) -> bytes:
    """Bitcoin 使用的雙重 SHA256"""
    return hashlib.sha256(hashlib.sha256(data).digest()).digest()

def verify_merkle_proof(tx_hash: bytes, proof: list,
                        merkle_root: bytes, index: int) -> bool:
    """
    驗證 Merkle 證明

    tx_hash: 交易哈希
    proof: 兄弟節點哈希列表
    merkle_root: 區塊頭中的 merkle root
    index: 交易在區塊中的位置
    """
    current = tx_hash

    for sibling in proof:
        if index % 2 == 0:
            # 當前節點在左邊
            current = double_sha256(current + sibling)
        else:
            # 當前節點在右邊
            current = double_sha256(sibling + current)
        index //= 2

    return current == merkle_root

# 使用示例
is_valid = verify_merkle_proof(
    tx_hash=bytes.fromhex("abc123..."),
    proof=[bytes.fromhex("..."), bytes.fromhex("...")],
    merkle_root=bytes.fromhex("merkle_root_from_header"),
    index=2
)

Bloom Filters(BIP-37)

BIP-37 Bloom Filter SPV(已棄用):

工作流程:
1. SPV 客戶端創建 Bloom Filter
   ├── 包含關注的地址/交易
   └── 有一定的誤報率

2. 發送 filterload 消息給節點
   └── 節點存儲 filter

3. 節點發送匹配的交易
   ├── 通過 merkleblock 消息
   └── 包含交易和 Merkle 證明

問題:
├── 隱私洩露(節點知道你關注什麼)
├── DoS 風險(節點需要為每個客戶端過濾)
└── 誤報可能被利用來追蹤

狀態:
├── Bitcoin Core 已禁用(-peerbloomfilters=0)
└── 被 BIP-157/158 取代

警告: BIP-37 Bloom Filters 存在嚴重隱私問題,已被棄用。請使用 BIP-157/158 Compact Block Filters。

Neutrino(BIP-157/158)

BIP-157/158 Compact Block Filters:

改進的 SPV 方法:

1. 服務器生成區塊過濾器
   ├── 每個區塊一個固定過濾器
   ├── 包含區塊中所有 scriptPubKeys
   └── 使用 Golomb-Rice 編碼壓縮

2. 客戶端下載過濾器
   ├── 過濾器承諾在區塊頭中(可驗證)
   └── 按需下載相關區塊

3. 客戶端本地匹配
   ├── 不向服務器透露興趣
   └── 保護隱私

優勢:
├── 隱私:服務器不知道客戶端關注什麼
├── 效率:服務器只需一次計算
├── 可驗證:過濾器承諾可驗證
└── 輕量:每個過濾器約 20 KB
# Bitcoin Core 啟用區塊過濾器服務
# bitcoin.conf
blockfilterindex=basic
peerblockfilters=1

# 獲取過濾器頭
bitcoin-cli getblockfilter "blockhash" "basic"

# 輸出
{
  "filter": "0187a5...",
  "header": "7a3dfc..."
}

# 使用 Neutrino 客戶端(如 btcd 的 neutrino)
# 客戶端會:
# 1. 下載所有過濾器頭
# 2. 下載需要的過濾器
# 3. 本地匹配
# 4. 下載匹配的完整區塊

安全性考慮

SPV 安全模型:

假設:
├── 最長鏈代表最多工作量
├── 多數礦工是誠實的
└── 至少連接到一個誠實節點

可驗證:
├── 區塊頭的 PoW
├── 區塊連接正確
├── 交易包含在區塊中(Merkle 證明)
└── 交易有足夠確認數

無法驗證:
├── 交易本身是否有效
├── 輸入是否未被花費
├── 區塊中的其他交易
└── 共識規則

攻擊向量:
1. 隱藏攻擊
   └── 礦工不向 SPV 客戶端發送交易

2. 雙花攻擊
   └── 礦工創建沖突區塊

3. 無效區塊攻擊
   └── 區塊 PoW 有效但內容無效

4. Eclipse 攻擊
   └── 隔離客戶端只連接惡意節點

SPV 優勢

  • • 極低資源需求
  • • 快速同步
  • • 適合移動設備
  • • 基本交易驗證

SPV 風險

  • • 信任假設更多
  • • 無法驗證交易有效性
  • • 可能被欺騙
  • • 隱私較差(取決於實現)

SPV 實現

錢包 方法 隱私
Electrum Electrum Server 服務器知道地址
Bitcoin Wallet (Android) BIP-37 Bloom 較差
Breez Neutrino 較好
Phoenix Electrum 服務器知道地址
Wasabi BIP-157/158 較好

Electrum 協議

Electrum 架構:

┌─────────────────────────────────────────────────────────────┐
│  Electrum Client  ←→  Electrum Server  ←→  Bitcoin Core    │
└─────────────────────────────────────────────────────────────┘

特點:
├── 服務器維護地址索引
├── 客戶端通過 JSON-RPC 查詢
├── 支持訂閱地址更新
└── 快速交易廣播

隱私考慮:
├── 服務器知道你的所有地址
├── 可以關聯地址到 IP
└── 建議運行自己的服務器

運行自己的 Electrum Server:
├── electrs(Rust)
├── ElectrumX(Python)
└── Fulcrum(C++)
# Electrum 協議示例

import socket
import json

def electrum_request(method, params=[]):
    """發送 Electrum 請求"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("electrum.blockstream.info", 50002))

    # 使用 SSL 包裝
    import ssl
    sock = ssl.wrap_socket(sock)

    request = {
        "id": 1,
        "method": method,
        "params": params
    }

    sock.send((json.dumps(request) + "\n").encode())
    response = sock.recv(4096).decode()
    sock.close()

    return json.loads(response)

# 獲取地址餘額
result = electrum_request(
    "blockchain.scripthash.get_balance",
    ["scripthash_of_address"]
)

# 獲取交易歷史
history = electrum_request(
    "blockchain.scripthash.get_history",
    ["scripthash_of_address"]
)

建議

SPV 使用建議:

1. 小額交易
   ├── SPV 足夠安全
   └── 等待更多確認

2. 大額交易
   ├── 使用全節點驗證
   └── 或等待 6+ 確認

3. 隱私敏感
   ├── 使用 BIP-157/158(Neutrino)
   ├── 運行自己的後端服務器
   └── 通過 Tor 連接

4. 最佳實踐
   ├── 連接多個節點
   ├── 驗證區塊頭鏈
   ├── 等待足夠確認
   └── 考慮運行全節點

總結

  • 輕量級:只需區塊頭和 Merkle 證明
  • Neutrino:BIP-157/158 改進隱私
  • 適用:移動設備和資源受限環境
  • 權衡:安全性低於全節點,大額交易建議全節點
已複製連結
已複製到剪貼簿