跳至主要內容
進階

Merkle Tree

Merkle 樹:高效驗證交易包含性的密碼學資料結構

14 分鐘

概述

Merkle 樹(也稱為雜湊樹)是一種二元樹結構,其中每個葉節點是數據的雜湊, 每個非葉節點是其子節點雜湊的雜湊。在比特幣中,Merkle 樹用於高效地 摘要區塊中的所有交易,並允許 SPV 客戶端驗證交易是否包含在區塊中。

核心價值: Merkle 樹將任意數量交易壓縮為 32 字節的根雜湊, 同時允許 O(log n) 複雜度的包含性證明。

樹結構

基本構造

Merkle 樹結構:

假設有 4 筆交易: TxA, TxB, TxC, TxD

步驟 1: 計算葉節點雜湊
HA = SHA256(SHA256(TxA))
HB = SHA256(SHA256(TxB))
HC = SHA256(SHA256(TxC))
HD = SHA256(SHA256(TxD))

步驟 2: 計算父節點
HAB = SHA256(SHA256(HA || HB))
HCD = SHA256(SHA256(HC || HD))

步驟 3: 計算根節點
Root = SHA256(SHA256(HAB || HCD))

視覺化:
                    Root
                   /    \
                HAB      HCD
               /   \    /   \
             HA    HB  HC    HD
              |     |   |     |
            TxA   TxB TxC   TxD

最終: 區塊頭只需包含 32 byte 的 Merkle Root

奇數交易處理

當交易數量為奇數時:

5 筆交易的情況:
             ___Root___
            /          \
        HABCD          HE'E'
        /    \         /    \
      HAB    HCD      HE     HE  ← 複製最後一個
     /  \   /  \       |      |
    HA  HB HC  HD     HE     HE
     |   |  |   |      |
   TxA TxB TxC TxD   TxE

規則:
- 如果某一層的節點數為奇數
- 複製最後一個節點
- 然後繼續向上計算

這確保了樹始終是完整的二元樹

Merkle 證明

SPV 證明概念

Merkle 證明 (Merkle Proof):

目標: 證明 TxB 在區塊中

需要的數據:
1. TxB 本身
2. HA (TxB 的兄弟節點)
3. HCD (HAB 的兄弟節點)
4. Merkle Root (來自區塊頭)

驗證過程:
1. 計算 HB = SHA256(SHA256(TxB))
2. 計算 HAB = SHA256(SHA256(HA || HB))
3. 計算 Root' = SHA256(SHA256(HAB || HCD))
4. 驗證 Root' == 區塊頭中的 Merkle Root

                    Root ← 驗證這個
                   /    \
           計算→ HAB      HCD ← 提供
               /   \
      提供→  HA    HB ← 計算
                    |
                  TxB ← 我的交易

空間複雜度: O(log n) 個雜湊
vs 下載整個區塊: O(n)

證明路徑

證明路徑 (Merkle Path):

包含:
1. 兄弟雜湊列表
2. 每個節點的位置標記 (左/右)

結構:
┌─────────────────────────────────────┐
│ Merkle Path for TxB                 │
├─────────────────────────────────────┤
│ Level 0: HA (left sibling)          │
│ Level 1: HCD (right sibling)        │
└─────────────────────────────────────┘

位置標記:
- 如果我們的節點在右邊,兄弟在左邊
- 這決定了連接順序

驗證時:
- Level 0: H = SHA256(SHA256(HA || HB))
- Level 1: H = SHA256(SHA256(H || HCD))
  (因為 HCD 在右邊)

證明大小計算:
- 樹深度 = ⌈log₂(n)⌉
- 每層一個 32 byte 雜湊
- 總大小 = 32 × ⌈log₂(n)⌉ bytes

TypeScript 實作

構建 Merkle 樹

import * as crypto from 'crypto';

function sha256d(data: Uint8Array): Uint8Array {
  const hash1 = crypto.createHash('sha256').update(data).digest();
  return new Uint8Array(crypto.createHash('sha256').update(hash1).digest());
}

function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
  const result = new Uint8Array(a.length + b.length);
  result.set(a);
  result.set(b, a.length);
  return result;
}

// 計算 Merkle Root
function computeMerkleRoot(txids: Uint8Array[]): Uint8Array {
  if (txids.length === 0) {
    return new Uint8Array(32); // 空區塊
  }

  if (txids.length === 1) {
    return txids[0];
  }

  // 複製列表以避免修改原始數據
  let level = [...txids];

  while (level.length > 1) {
    const nextLevel: Uint8Array[] = [];

    // 如果是奇數,複製最後一個
    if (level.length % 2 === 1) {
      level.push(level[level.length - 1]);
    }

    // 配對並雜湊
    for (let i = 0; i < level.length; i += 2) {
      const combined = concatBytes(level[i], level[i + 1]);
      nextLevel.push(sha256d(combined));
    }

    level = nextLevel;
  }

  return level[0];
}

// 使用範例
const txids = [
  hexToBytes('tx1_hash...'),
  hexToBytes('tx2_hash...'),
  hexToBytes('tx3_hash...'),
  hexToBytes('tx4_hash...')
];

const merkleRoot = computeMerkleRoot(txids);
console.log('Merkle Root:', bytesToHex(merkleRoot));

生成 Merkle 證明

interface MerkleProof {
  txid: Uint8Array;
  index: number;
  siblings: Array<{
    hash: Uint8Array;
    position: 'left' | 'right';
  }>;
  root: Uint8Array;
}

function generateMerkleProof(
  txids: Uint8Array[],
  targetIndex: number
): MerkleProof {
  if (targetIndex >= txids.length) {
    throw new Error('Index out of bounds');
  }

  const siblings: MerkleProof['siblings'] = [];
  let level = [...txids];
  let index = targetIndex;

  while (level.length > 1) {
    // 處理奇數情況
    if (level.length % 2 === 1) {
      level.push(level[level.length - 1]);
    }

    // 找到兄弟節點
    const siblingIndex = index % 2 === 0 ? index + 1 : index - 1;
    const position = index % 2 === 0 ? 'right' : 'left';

    siblings.push({
      hash: level[siblingIndex],
      position
    });

    // 計算下一層
    const nextLevel: Uint8Array[] = [];
    for (let i = 0; i < level.length; i += 2) {
      const combined = concatBytes(level[i], level[i + 1]);
      nextLevel.push(sha256d(combined));
    }

    level = nextLevel;
    index = Math.floor(index / 2);
  }

  return {
    txid: txids[targetIndex],
    index: targetIndex,
    siblings,
    root: level[0]
  };
}

// 驗證 Merkle 證明
function verifyMerkleProof(proof: MerkleProof): boolean {
  let current = proof.txid;

  for (const sibling of proof.siblings) {
    const combined = sibling.position === 'left'
      ? concatBytes(sibling.hash, current)
      : concatBytes(current, sibling.hash);

    current = sha256d(combined);
  }

  return bytesEqual(current, proof.root);
}

// 使用範例
const proof = generateMerkleProof(txids, 1); // 為第二筆交易生成證明
console.log('Proof valid:', verifyMerkleProof(proof));

序列化與反序列化

// 序列化 Merkle 證明
function serializeMerkleProof(proof: MerkleProof): Uint8Array {
  const parts: Uint8Array[] = [];

  // 交易 ID
  parts.push(proof.txid);

  // 索引 (4 bytes)
  const indexBuf = new Uint8Array(4);
  new DataView(indexBuf.buffer).setUint32(0, proof.index, true);
  parts.push(indexBuf);

  // 兄弟節點數量 (varint)
  parts.push(encodeVarInt(proof.siblings.length));

  // 位置位圖
  let positionBits = 0;
  for (let i = 0; i < proof.siblings.length; i++) {
    if (proof.siblings[i].position === 'right') {
      positionBits |= (1 << i);
    }
  }
  parts.push(encodeVarInt(positionBits));

  // 兄弟雜湊
  for (const sibling of proof.siblings) {
    parts.push(sibling.hash);
  }

  // Merkle Root
  parts.push(proof.root);

  return concatAllBytes(parts);
}

// 反序列化
function deserializeMerkleProof(data: Uint8Array): MerkleProof {
  let offset = 0;

  // 交易 ID
  const txid = data.slice(offset, offset + 32);
  offset += 32;

  // 索引
  const index = new DataView(data.buffer, offset).getUint32(0, true);
  offset += 4;

  // 兄弟節點數量
  const { value: siblingCount, bytesRead: countBytes } = decodeVarInt(data, offset);
  offset += countBytes;

  // 位置位圖
  const { value: positionBits, bytesRead: posBytes } = decodeVarInt(data, offset);
  offset += posBytes;

  // 兄弟雜湊
  const siblings: MerkleProof['siblings'] = [];
  for (let i = 0; i < siblingCount; i++) {
    const hash = data.slice(offset, offset + 32);
    offset += 32;
    const position = (positionBits & (1 << i)) ? 'right' : 'left';
    siblings.push({ hash: new Uint8Array(hash), position });
  }

  // Merkle Root
  const root = data.slice(offset, offset + 32);

  return {
    txid: new Uint8Array(txid),
    index,
    siblings,
    root: new Uint8Array(root)
  };
}

SPV 應用

輕客戶端驗證

SPV (Simple Payment Verification):

輕客戶端只需:
1. 區塊頭 (80 bytes each)
2. 相關交易的 Merkle 證明

驗證流程:
┌─────────────────────────────────────────────┐
│ 1. 下載所有區塊頭                            │
│    - 驗證 PoW                               │
│    - 驗證鏈連接                             │
│    - 大小: ~60 MB (到 2024年)               │
├─────────────────────────────────────────────┤
│ 2. 請求相關交易的 Merkle 證明               │
│    - 使用 Bloom Filter 或 Compact Filter    │
│    - 只下載匹配的交易                        │
├─────────────────────────────────────────────┤
│ 3. 驗證 Merkle 證明                         │
│    - 計算證明路徑                           │
│    - 比對區塊頭中的 Merkle Root             │
├─────────────────────────────────────────────┤
│ 4. 確認深度                                 │
│    - 等待足夠的確認數                        │
│    - 降低雙花風險                           │
└─────────────────────────────────────────────┘

merkleblock 訊息

P2P 訊息: merkleblock

結構:
┌─────────────────────────────────────┐
│ block_header (80 bytes)             │
├─────────────────────────────────────┤
│ total_transactions (uint32)         │
├─────────────────────────────────────┤
│ hash_count (varint)                 │
├─────────────────────────────────────┤
│ hashes (32 bytes × hash_count)      │
├─────────────────────────────────────┤
│ flag_bytes (varint)                 │
├─────────────────────────────────────┤
│ flags (flag_bytes bytes)            │
└─────────────────────────────────────┘

flags 用於重建部分 Merkle 樹:
- 使用深度優先遍歷
- 1 = 下降到子節點或包含交易
- 0 = 跳過子樹

這允許高效傳輸多個交易的證明

Witness Merkle Tree

SegWit Witness Commitment (BIP-141):

區塊包含兩個 Merkle 樹:
1. 傳統 Merkle 樹 (在區塊頭)
   - 使用 TXID
   - 不包含 witness 數據

2. Witness Merkle 樹 (在 coinbase)
   - 使用 WTXID
   - 包含 witness 數據

Witness Commitment 結構:
┌─────────────────────────────────────┐
│ Coinbase 交易的 OP_RETURN 輸出       │
├─────────────────────────────────────┤
│ 6a (OP_RETURN)                      │
│ 24 (push 36 bytes)                  │
│ aa21a9ed (commitment header)        │
│ <32-byte witness_root_hash>         │
└─────────────────────────────────────┘

Witness Root 計算:
1. 計算所有 WTXID
2. Coinbase WTXID = 0x00...00
3. 構建 Merkle 樹
4. commitment = SHA256(witness_root || witness_nonce)

安全考量

CVE-2012-2459

Merkle 樹重複攻擊:

問題:
當交易數量為奇數時,最後一個交易被複製
這可能導致兩個不同的區塊有相同的 Merkle Root

攻擊場景:
原始區塊: [TxA, TxB, TxC]
Merkle 計算: [TxA, TxB, TxC, TxC]

偽造區塊: [TxA, TxB, TxC, TxC]
Merkle 計算: [TxA, TxB, TxC, TxC]
(相同的 Merkle Root!)

後果:
- 可能導致節點接受無效區塊
- 或拒絕有效區塊

修復:
- 節點現在檢查交易列表是否有重複
- 如果最後兩筆交易相同,拒絕區塊

64 字節交易攻擊

內部節點偽造攻擊:

觀察:
- Merkle 內部節點 = 64 bytes (兩個 32-byte 雜湊)
- 如果交易剛好 64 bytes,可能被誤認為內部節點

攻擊:
創建 64 byte 的交易,其內容是兩個精心選擇的雜湊

防護:
- Bitcoin Core 要求交易至少 65 bytes
- 這通過最小交易大小規則強制執行
- 標準規則要求更大的最小值

效能優化

Merkle 樹優化技術:

1. 緩存中間節點
   - 計算時保存所有層級
   - 生成證明時直接查找

2. 並行計算
   - 每層的雜湊計算可以並行
   - 適合 GPU 加速

3. 增量更新
   - 添加交易時只更新受影響的路徑
   - 不需要重建整個樹

4. 批量證明
   - 多個交易共享部分路徑
   - 減少傳輸數據

證明大小比較 (1000 筆交易):
- 單個交易: ~10 × 32 = 320 bytes
- 10 個相鄰交易: ~350 bytes (共享路徑)
- 下載整個區塊: ~250 KB

與其他結構比較

特性 Merkle Tree Merkle Patricia Trie Verkle Tree
使用 Bitcoin Ethereum Ethereum 2.0
證明大小 O(log n) O(log n) 但更大 O(1) 常數
更新效率 O(log n) O(log n) O(log n)
複雜度

相關資源

已複製連結
已複製到剪貼簿