以太坊密碼學基礎完整指南:橢圓曲線密碼學、簽章機制與 Merkle Tree 結構

本文深入分析以太坊密碼學系統的三大支柱:secp256k1 橢圓曲線與 ECDSA 簽章機制的數學原理、KECCAK-256 雜湊函數的設計特點、以及 Patricia Merkle Trie 資料結構在狀態管理中的關鍵角色。我們從密碼學理論出發,經過詳盡的數學推導,最終落實到 Solidity、Go 與 Rust 的實際程式碼範例。涵蓋離散對數問題、點加法/倍增運算、ECDSA 簽章驗證、Merkle Proof、EIP-1559 等核心概念的完整技術解析。

以太坊密碼學基礎完整指南:橢圓曲線密碼學、簽章機制與 Merkle Tree 結構

概述

以太坊的安全性架構建立在三層密碼學基石之上:橢圓曲線密碼學(Elliptic Curve Cryptography, ECC)用於身份認證與簽章、密碼學雜湊函數用於資料完整性驗證、以及 Merkle Tree 資料結構用於狀態與交易的有效性證明。理解這些基礎密碼學元件的運作原理,是掌握以太坊安全模型的必要條件。

本文深入分析以太坊密碼學系統的三大支柱:secp256k1 橢圓曲線與 ECDSA 簽章機制的數學原理與安全性分析、KECCAK-256 雜湊函數的設計特點、以及 Patricia Merkle Trie 資料結構在狀態管理中的關鍵角色。我們將從密碼學理論出發,經過詳盡的數學推導,最終落實到 Solidity、Go 與 Rust 的實際程式碼範例。

截至 2026 年第一季度,以太坊網路已處理超過 22 億筆交易,管理超過 2.1 億個唯一地址,支撐著總值超過 1500 億美元的數位資產。這一切的基礎,正是這些經過嚴格密碼學設計的核心元件。


第一章:橢圓曲線密碼學理論基礎

1.1 橢圓曲線密碼學的數學原理

橢圓曲線密碼學是現代密碼學最重要的突破之一,其安全性基於橢圓曲線離散對數問題(Elliptic Curve Discrete Logarithm Problem, ECDLP)的計算困難性。與傳統 RSA 密碼學相比,ECC 在相同安全強度下使用更短的金鑰,大幅降低了運算負載與儲存需求。

1.1.1 橢圓曲線的代數定義

橢圓曲線並非橢圓形狀的幾何曲線,而是定義在有限域上的三次代數曲線。以太坊採用的 secp256k1 曲線屬於 Weierstrass 標準形式,定義於質數域 Fp:

E: y² ≡ x³ + ax + b (mod p)

secp256k1 曲線的標準參數定義如下:

參數名稱符號
質數(Field Prime)p2²⁵⁶ - 2³² - 2⁹ - 2⁶ - 2⁴ - 1 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
係數 aa0
係數 bb7
基點橫座標Gx0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
基點縱座標Gy0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
基點階數n0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
協因數h1

為何選擇 a = 0?

secp256k1 選擇 a = 0 使得曲線方程簡化為 y² = x³ + 7,這一設計具有重要意義:

  1. 運算效率:曲線方程中不存在 ax 項,點加法與點倍增的公式因此簡化
  2. 計算捷徑:當 a = 0 時,點倍增公式中的 3x₁² + a 簡化為 3x₁²
  3. 側信道攻擊防護:簡化的計算模式減少了時間側信道洩漏的可能性

1.1.2 有限域上的點運算

橢圓曲線密碼學的所有運算都在有限域 Fp 上進行,而非實數域。這種離散化設計是確保安全性的關鍵,因為實數域上的連續性使得離散對數問題可透過解析方法求解。

點加法運算(Point Addition)

給定曲線上兩個不同點 P(x₁, y₁) 和 Q(x₂, y₂),計算 R = P + Q:

斜率 λ = (y₂ - y₁) × (x₂ - x₁)⁻¹ (mod p)
x₃ = λ² - x₁ - x₂ (mod p)
y₃ = λ(x₁ - x₃) - y₁ (mod p)

其中 (x₂ - x₁)⁻¹ 表示模反元素,可以使用擴展歐幾里得演算法或費馬小定理計算。

點倍增運算(Point Doubling)

當 P = Q 時,計算 R = 2P(點在自身處相加):

斜率 λ = (3x₁²) × (2y₁)⁻¹ (mod p)
x₃ = λ² - 2x₁ (mod p)
y₃ = λ(x₁ - x₃) - y₁ (mod p)

無窮遠點(Point at Infinity)

在橢圓曲線群中,存在一個特殊的單位元素,通常記為 O 或 ∞,稱為無窮遠點。當 P 和 Q 互為加法逆元(即 x 座標相同,y 座標互為相反數)時,P + Q = O。這一性質在簽章驗證中具有重要應用。

1.1.3 離散對數問題與安全性

橢圓曲線離散對數問題(ECDLP)的定義如下:

給定曲線 E、基點 G,以及點 Q = kG(其中 k 為整數),計算 k。

ECDLP 被廣泛認為是計算困難問題,目前已知最優攻擊演算法(Pollard's ρ 方法)的複雜度為 O(√n),其中 n 為基點的階數。對於 secp256k1,n 約為 2¹²⁸,這意味著需要約 2⁶⁴ 次運算才能破解——這一計算量即使使用超級電腦也需要數百萬年。

secp256k1 與 secp256r1 的安全性比較

NIST 標準化的 secp256r1(P-256)採用隨機種子生成曲線參數,曾引發對後門疑慮。secp256k1 由比特幣首創,其參數透明生成且無爭議性,社群選擇此曲線反映了對密碼學透明性的重視。兩者在相同金鑰長度下的安全強度相當,但 secp256k1 的運算效率略高。

1.2 金鑰生成與位址推導

1.2.1 私鑰到公鑰的推導

以太坊帳戶的密碼學身份始於私鑰。私鑰是一個在 [1, n-1] 範圍內均勻隨機選取的 256 位整數 k。公鑰 K 透過橢圓曲線點乘法計算得出:

K = k × G

其中 G 為基點,× 表示橢圓曲線上的標量乘法。這一運算的關鍵特性是:正向計算(k → K)極為高效,但反向推導(K → k)在計算上不可行。

標量乘法的最佳化

直接實現 k × G 需要 k 次點加法,效率極低。實際上,以太坊客戶端採用以下最佳化技術:

  1. 二元法(Binary Method):將 k 表示為二進位,透過重複「點倍增 + 條件點加法」實現
  2. 窗口 NAF 法(Non-Adjacent Form):將 k 表示為帶符號的數字系統,減少點加法次數
  3. 蒙哥馬利階梯(Montgomery Ladder):固定模式的計算流程,抗側信道攻擊
// 標量乘法的蒙哥馬利階梯實現
function scalarMultiply(uint256 k, Point memory P) internal view returns (Point memory result) {
    Point memory R0 = Point(1, 0);  // 無窮遠點
    Point memory R1 = P;
    
    for (uint256 i = 255; i >= 0; i--) {
        if ((k >> i) & 1 == 0) {
            // k 的第 i 位為 0:R1 = R0 + R1
            R1 = pointAdd(R0, R1);
            // R0 = 2 * R0
            R0 = pointDouble(R0);
        } else {
            // k 的第 i 位為 1:R0 = R0 + R1
            R0 = pointAdd(R0, R1);
            // R1 = 2 * R1
            R1 = pointDouble(R1);
        }
    }
    return R0;
}

1.2.2 公鑰格式與壓縮

橢圓曲線公鑰是由 (x, y) 座標構成的 64 位元組序列。然而,由於 y 座標可由 x 座標推導(y² = x³ + 7),僅需儲存 x 座標與 y 的奇偶性(1 位元),即可完整表示公鑰。

壓縮格式原理

給定 x 座標,計算 y = ±√(x³ + 7 mod p)。由於 p 為質數,y 的取值有兩個:y 和 p - y,其中 y 為奇數時對應 p - y 為偶數,反之亦然。因此,只需 1 位元即可標識正確的 y 值。

以太坊採用未壓縮格式(0x04 前綴 + x + y)儲存公鑰,這是因為:

  1. 智慧合約直接使用完整公鑰計算位址
  2. 避免在 EVM 中執行耗時的平方根運算
  3. 簡化合約邏輯,減少 Gas 消耗

1.2.3 從公鑰到位址

以太坊位址的推導過程包含兩個步驟:Keccak-256 雜湊與取後 20 位元組。

推導流程

1. 對公鑰(64 位元組)進行 Keccak-256 雜湊
2. 拋棄前 12 位元組(32 位元)
3. 保留後 20 位元組(160 位元)作為位址
// Solidity 中的位址推導
function pubkeyToAddress(bytes calldata pubkey) public pure returns (address) {
    require(pubkey.length == 64, "Invalid public key length");
    
    // Keccak-256 雜湊
    bytes32 hash = keccak256(pubkey);
    
    // 取後 20 位元組作為位址
    return address(uint160(uint256(hash)));
}

位址格式特點

以太坊位址的設計具有以下特點:


第二章:ECDSA 簽章機制深度解析

2.1 ECDSA 簽章演算法

2.1.1 簽章生成過程

ECDSA(Elliptic Curve Digital Signature Algorithm)是以太坊交易認證的核心機制。簽章生成過程如下:

輸入:私鑰 d、消息雜湊 e、隨機數 k

輸出:簽章 (r, s)

步驟 1:計算點 R = k × G,取其橫座標 r = R.x mod n
步驟 2:計算 s = k⁻¹ × (e + r × d) mod n
步驟 3:若 r = 0 或 s = 0,重新選擇 k 並重試
步驟 4:返回簽章 (r, s)

簽章可鑒造性證明

ECDSA 簽章的可鑒造性基於以下數學推導:

給定 (r, s) 和公鑰 Q,驗證者計算:

u₁ = e × s⁻¹ mod n
u₂ = r × s⁻¹ mod n
P = u₁ × G + u₂ × Q

若 P.x mod n = r,則簽章有效。

推導過程:

由 s = k⁻¹(e + rd) 得:
k = s⁻¹(e + rd) mod n

R = kG
r = R.x

P = u₁G + u₂Q
   = (es⁻¹)G + (rs⁻¹)dG
   = s⁻¹(e + rd)G
   = kG
   = R

因此 P.x = R.x = r,驗證等式成立。

2.1.2 簽章安全性分析

ECDSA 的安全性依賴以下假設:

離散對數困難性:攻擊者無法從公鑰推導私鑰

雜湊函數抗碰撞性:無法找到兩個不同消息具有相同雜湊值

隨機數不可預測性:每次簽章使用的 k 值必須是完全隨機且不可預測的

k 值重用漏洞

若同一私鑰用於簽章兩個不同消息,且使用了相同的 k 值,攻擊者可從兩個簽章 (r, s₁) 和 (r, s₂) 推導私鑰:

s₁ = k⁻¹(e₁ + rd) mod n
s₂ = k⁻¹(e₂ + rd) mod n

由上式相減:
(s₁ - s₂) = k⁻¹(e₁ - e₂) mod n
k = (e₁ - e₂)(s₁ - s₂)⁻¹ mod n

代入任一公式可得:
d = (s₁k - e₁) × r⁻¹ mod n

2010 年索尼 PS3 漏洞、2020 年比特幣多重簽章漏洞均源於此。

側信道攻擊防護

實際部署中,ECDSA 面臨多種側信道威脅:

  1. 時間攻擊:利用簽章運算時間差異推斷私鑰
  2. 功率分析:測量簽章運算期間的電力消耗模式
  3. 故障注入:在簽章運算期間引入錯誤,誘導錯誤簽章

防護策略包括:

2.2 以太坊交易簽章

2.2.1 交易簽章格式

以太坊交易的 ECDSA 簽章包含三個元件 (v, r, s),這些值直接編碼在交易資料中。

簽章格式

v:recovery id,用於從簽章推導公鑰(值為 27 或 28,加上鏈 ID 用於 EIP-155 Replay Protection)
r:簽章的第一個分量,公鑰 x 座標模 n
s:簽章的第二個分量,標量運算結果

2.2.2 EIP-155 Replay Protection

在 The DAO 硬分叉之前,所有以太坊鏈共享相同的交易格式,導致交易可以在不同鏈之間重放。EIP-155 引入鏈 ID 概念解決此問題:

v = CHAIN_ID * 2 + 35 + recovery_id

這確保了在主網簽名的交易無法在 ETC 鏈上重放,反之亦然。

2.2.3 公鑰恢復機制

以太坊的一個獨特設計是從簽章直接恢復公鑰,而無需預先知道公鑰。這使得交易發送者僅需提供簽章即可驗證交易真實性。

恢復演算法

给定簽章 (r, s) 和交易雜湊 e:

1. 計算候選公鑰:
   - 嘗試 r₁ = r 或 r₁ = r + n(若 r < p - n)
   - 計算 R 的候選 x 座標
   - 從 x 推導 R 的兩個 y 座標候選值
   - 每個候選 R 產生兩個候選公鑰

2. 驗證候選公鑰:
   對每個候選公鑰 Q,計算其位址
   若位址等於交易 sender 位址,則該公鑰即為正確的發送者公鑰
// ecrecover 函數的等效 Solidity 實現
function recoverSigner(bytes32 message, bytes32 v, bytes32 r, bytes32 s) 
    public pure returns (address) 
{
    bytes32 prefixedHash = keccak256(abi.encodePacked(
        "\x19Ethereum Signed Message:\n32", 
        message
    ));
    
    // 使用 ecrecover 內建函數恢復簽章者位址
    return ecrecover(prefixedHash, uint8(v), r, s);
}

2.3 簽章驗證的智能合約實作

2.3.1 EVM 中的 ECDSA 驗證

雖然 EVM 原生不支援橢圓曲線運算,但我們可以透過 precompile 合約调用 ECDSA 驗證:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/**
 * @title ECDSA Verification Contract
 * @dev 展示如何在智能合約中驗證 ECDSA 簽章
 */
contract ECDSAVerifier {
    
    // 驗證消息簽章
    function verifySignature(
        bytes32 messageHash,
        bytes32 r,
        bytes32 s,
        uint8 v,
        address expectedSigner
    ) public pure returns (bool) {
        // 構建 Ethereum Signed Message 格式
        bytes32 prefixedHash = keccak256(abi.encodePacked(
            "\x19Ethereum Signed Message:\n32",
            messageHash
        ));
        
        // 恢復簽章者
        address signer = ecrecover(prefixedHash, v, r, s);
        
        // 檢查恢復結果
        return signer == expectedSigner && signer != address(0);
    }
    
    // 多重簽章驗證
    function verifyMultiSignature(
        bytes32 messageHash,
        bytes32[] calldata r,
        bytes32[] calldata s,
        uint8[] calldata v,
        address[] calldata signers,
        uint256 threshold
    ) public pure returns (bool) {
        require(r.length == s.length && s.length == v.length);
        require(signers.length >= threshold);
        
        bytes32 prefixedHash = keccak256(abi.encodePacked(
            "\x19Ethereum Signed Message:\n32",
            messageHash
        ));
        
        uint256 validCount = 0;
        address lastSigner = address(0);
        
        for (uint256 i = 0; i < signers.length; i++) {
            address signer = ecrecover(prefixedHash, v[i], r[i], s[i]);
            
            // 防止簽章者重複
            require(signer > lastSigner, "Signers must be unique and sorted");
            
            if (signer == signers[i]) {
                validCount++;
            }
        }
        
        return validCount >= threshold;
    }
}

第三章:Keccak-256 雜湊函數

3.1 Keccak 演算法設計

3.1.1 海綿結構

Keccak 是 SHA-3 標準的原始候選演算法,採用創新的海綿結構(Sponge Construction)。與傳統雜湊函數的 MD(Merkle-Damgård)結構不同,海綿結構具有更好的靈活性,可擴展用於多種密碼學原語。

海綿結構運作原理

┌─────────────────────────────────────────────────────────────┐
│                      海綿結構                                 │
├─────────────────────────────────────────────────────────────┤
│  輸入 ──┐     ┌──────────────┐     ┌──────────────┐         │
│         ├────▶│   吸收階段    │────▶│   擠壓階段    │──▶ 輸出 │
│         │     │ (Absorbing)  │     │ (Squeezing)  │         │
│         │     └──────────────┘     └──────────────┘         │
│         │            │                      │               │
│         └──────────▶ f(r,c) ◀────────────────┘               │
│                      (Keccak-f)                              │
│                    置換函數                                  │
└─────────────────────────────────────────────────────────────┘

吸收階段:輸入資料與狀態進行 XOR 運算,然後通過 Keccak-f 置換

擠壓階段:從狀態中輸出固定長度的雜湊值

3.1.2 Keccak-f 置換函數

Keccak-f 使用五維狀態陣列 a[x][y][z][w][v],但在以太坊實現中簡化為 5×5×64 = 1600 位的三維陣列。

核心操作

操作描述
θ (Theta)每位元與相鄰兩列進行 XOR,消除差分擴散
ρ (Rho)每片進行旋轉位移
π (Pi)重新排列切片位置
χ (Chi)非線性替代操作
ι (Iota)與輪常數進行 XOR

為何選擇 Keccak 而非 SHA-3

以太坊在 SHA-3 標準化之前就選擇了 Keccak,兩者存在細微差異:

差異點KeccakSHA-3
雜湊輸出長度可變(以太坊固定 256 位)固定(SHA3-256)
XOF 支援基本標準化(SHAKE128/256)
填充方案10*1 填充10*1 填充
輪常數不同NIST 標準化

這導致 Keccak-256("") = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470

但 SHA3-256("") = 0xa7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a

3.1.3 抗碰撞性與安全性

Keccak-256 的安全性基於以下設計原則:

抗碰撞性:給定輸入 x₁ ≠ x₂,輸出 k(x₁) = k(x₂) 的機率為 2⁻²⁵⁶

抗原像攻擊:給定輸出 y,找輸入 x 使得 k(x) = y 的複雜度為 2²⁵⁶

雪崩效應:輸入變化 1 位,輸出平均變化 50% 位元

截至 2026 年,尚未發現對 Keccak-256 的實用攻擊,其安全性得到廣泛認可。

3.2 以太坊中的 Keccak-256 應用

3.2.1 狀態根雜湊

每個以太坊區塊的 header 包含 stateRoot,這是整個帳戶狀態的 Merkle 樹根雜湊值。任何狀態變更都會導致 stateRoot 變化,這使得區塊的有效性可被輕鬆驗證。

3.2.2 交易雜湊

每筆交易的唯一識別符 tx hash 是交易資料的 Keccak-256 雜湊值。在區塊內,交易按照此雜湊值排序。

3.2.3 合約位元組碼雜湊

部署合約時,位元組碼的 Keccak-256 雜湊作為 codeHash 儲存在帳戶狀態中。這允許驗證合約位元組碼的完整性。

// 驗證合約位元組碼完整性
function verifyContractCode(address target, bytes32 expectedHash) public view returns (bool) {
    return keccak256(target.code) == expectedHash;
}

第四章:Merkle Tree 資料結構

4.1 Merkle Tree 基礎理論

4.1.1 資料結構定義

Merkle Tree(梅克爾樹)是由Ralph Merkle於 1979 年提出的二元雜湊樹,其核心思想是透過雜湊運算將大量資料組織成樹狀結構,實現高效的完整性驗證與部分資料驗證。

結構定義

                    Root Hash
                   /          \
            Hash(0,0)       Hash(0,1)
            /      \        /        \
       Hash00   Hash01  Hash10    Hash11
          |        |       |         |
        Data0    Data1   Data2     Data3

對於 n 個資料區塊,Merkle Tree 透過迭代雜湊計算產生 log₂(n) 層的樹狀結構。

4.1.2 Merkle Proof 機制

Merkle Proof 允許驗證某筆資料屬於特定 Merkle Root,而不需下載整棵樹的完整資料。

Proof 結構

{
    "root": "0xabc...",
    "data": "0x123...",      // 待驗證的資料
    "proof": [
        "0xdef...",          // 兄弟節點(與資料的雜湊值拼接)
        "0xghi...",          // 祖父節點的兄弟節點
        ...
    ],
    "path": [0, 1, 0]        // 路徑索引:左/右兄弟
}

驗證過程

function verifyMerkleProof(
    bytes32 root,
    bytes32 data,
    bytes32[] memory siblings,
    bool[] memory path
) public pure returns (bool) {
    bytes32 current = keccak256(abi.encodePacked(data));
    
    for (uint256 i = 0; i < siblings.length; i++) {
        if (path[i]) {
            // 兄弟節點在左側
            current = keccak256(abi.encodePacked(siblings[i], current));
        } else {
            // 兄弟節點在右側
            current = keccak256(abi.encodePacked(current, siblings[i]));
        }
    }
    
    return current == root;
}

4.2 Patricia Merkle Trie

以太坊採用 Patricia Merkle Trie(又稱 Merkle Patricia Tree 或 MPT)作為狀態與交易的組織結構。

4.2.1 Patricia Trie 特性

Patricia(Practical Algorithm to Retrieve Information Coded in Alphanumeric)是前綴樹的一種最佳化變體,透過壓縮單分支路徑減少儲存開銷。

三種節點類型

節點類型編碼用途
分支節點[v0, v1, ..., v15, vt]分支路徑
葉節點[shared nibbles, value]鍵值對
擴展節點[shared nibbles, child pointer]路徑壓縮

Nibble 編碼

MPT 中的鍵由 nibble(4 位元)序列組成,而非完整位元組。這允許精確控制前綴長度。

原始鍵:0x1234
Nibble 序列:[1, 2, 3, 4]
RLE 壓縮:[12, 34]  // 葉節點

4.2.2 以太坊的 Patricia Merkle Trie 實現

以太坊使用六棵主要 Merkle Tree:

  1. State Trie:所有帳戶狀態,鍵為位址
  2. Storage Trie:每個合約的儲存,鍵為儲存槽索引
  3. Transaction Trie:區塊內交易,按索引排序
  4. Receipt Trie:交易收據,按索引排序
  5. Bloom Filter Trie:日誌bloom篩選器
// Go-ethereum 中的 State Trie 節點定義
type Node struct {
    Hash    hash.Hash // 節點內容的雜湊值
    Content []byte    // RLP 編碼的節點內容
    Flags   NodeFlag
}

type NodeFlag struct {
    database  *TrieDatabase // 持久化資料庫
    syncing   bool           // 同步模式標記
    reference uint32         // 引用計數
    flushPrev []byte         // 前一個持久化版本
}

4.2.3 狀態根的密碼學特性

狀態根(stateRoot)具有以下重要特性:

可遞歸驗證性:任何狀態讀取操作可透過 Merkle Proof 驗證

批次驗證:多筆狀態查詢可共享部分 Proof,降低驗證成本

快照驗證:歷史區塊的狀態可透過 State Trie 重建

驗證示例

// 驗證帳戶餘額的 Merkle Proof
contract MerkleProofVerifier {
    function verifyAccountProof(
        bytes32 root,
        bytes32[] memory proof,
        bytes32[] memory path,
        address account,
        uint256 balance,
        bytes32 storageRoot
    ) public pure returns (bool) {
        // 構建帳戶葉節點雜湊
        bytes32 leafHash = keccak256(abi.encodePacked(
            account,
            keccak256(abi.encodePacked(
                uint256(1),              // 余額類型
                balance,
                storageRoot,
                bytes32(0),             // codeHash (空為 none)
                bytes32(0)              // nonce (空為零)
            ))
        ));
        
        // 計算 Merkle 根
        bytes32 computedRoot = computeMerkleRoot(leafHash, proof, path);
        
        return computedRoot == root;
    }
}

4.3 Verkle Tree 與未來演進

4.3.1 為何需要 Verkle Tree

MPT 的主要限制在於 Proof 大小。對於包含數百萬帳戶的狀態,Merkle Proof 可能需要數百個節點,導致驗證成本高昂。

Verkle Tree 透過以下改進解決此問題:

4.3.2 Verkle Tree 原理

Verkle Tree 基於 Pedersen 承諾,將多個值承諾到單一輸出。不同於 Merkle Tree 的二元分支,Verkle Tree 採用廣分支因子(如 256),大幅減少樹深度。

Merkle Tree (二元分支):
深度 = log₂(2³²) = 32

Verkle Tree (256 分支):
深度 = log₂₅₆(2³²) = 1.33 ≈ 2

第五章:密碼學實踐案例

5.1 安全錢包實現

5.1.1 分層確定性錢包(HD Wallet)

HD Wallet 允許從單一種子生成無限多個金鑰,無需每次備份新私鑰。

BIP-39 助記詞生成

1. 生成 128-256 位隨機種子
2. 計算 SHA-256 校驗和(取前 4-8 位)
3. 拼接:entropy + checksum
4. 映射至 BIP-39 單詞表(2048 個單詞)
# BIP-39 助記詞到種子的轉換
import hashlib
import hmac

def mnemonic_to_seed(mnemonic, passphrase=""):
    # PBKDF2 參數
    password = mnemonic.encode('utf-8')
    salt = ('mnemonic' + passphrase).encode('utf-8')
    iterations = 2048
    keylen = 64  # 512 位
    
    seed = hashlib.pbkdf2_hmac('sha512', password, salt, iterations, dklen=keylen)
    return seed

BIP-32 路徑派生

HD Wallet 使用 CKDpriv 函數從父私鑰派生子私鑰:

child_private_key = HMAC-SHA512(chain_code, parent_public_key || index)
                = (left_32_bytes, right_32_bytes)

if left_32_bytes >= n:
    return error
else:
    child_key = left_32_bytes + parent_private_key (mod n)
    child_chain_code = right_32_bytes

5.1.2 多重簽章錢包

以太坊智慧合約錢包可實現任意複雜度的多簽機制:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/**
 * @title MultiSigWallet
 * @dev M-of-N 多重簽章錢包
 */
contract MultiSigWallet {
    event SubmitTransaction(address indexed owner, uint indexed txIndex);
    event ConfirmTransaction(address indexed owner, uint indexed txIndex);
    event RevokeConfirmation(address indexed owner, uint indexed txIndex);
    event ExecuteTransaction(address indexed owner, uint indexed txIndex);
    
    address[] public owners;
    mapping(address => bool) public isOwner;
    uint256 public required;
    uint256 public transactionCount;
    
    struct Transaction {
        address to;
        uint256 value;
        bytes data;
        bool executed;
        uint256 confirmations;
    }
    
    mapping(uint256 => mapping(address => bool)) public confirmations;
    Transaction[] public transactions;
    
    modifier onlyOwner() {
        require(isOwner[msg.sender], "Not owner");
        _;
    }
    
    modifier txExists(uint256 txIndex) {
        require(txIndex < transactions.length, "Tx does not exist");
        _;
    }
    
    constructor(address[] memory _owners, uint256 _required) {
        require(_owners.length > 0, " Owners required");
        require(_required > 0 && _required <= _owners.length, "Invalid required");
        
        for (uint256 i = 0; i < _owners.length; i++) {
            require(_owners[i] != address(0), "Invalid owner");
            require(!isOwner[_owners[i]], "Owner not unique");
            isOwner[_owners[i]] = true;
            owners.push(_owners[i]);
        }
        required = _required;
    }
    
    function submitTransaction(address to, uint256 value, bytes memory data)
        public onlyOwner returns (uint256)
    {
        uint256 txIndex = transactions.length;
        transactions.push(Transaction({
            to: to,
            value: value,
            data: data,
            executed: false,
            confirmations: 0
        }));
        emit SubmitTransaction(msg.sender, txIndex);
        return txIndex;
    }
    
    function confirmTransaction(uint256 txIndex)
        public onlyOwner txExists(txIndex)
    {
        require(!confirmations[txIndex][msg.sender], "Already confirmed");
        
        confirmations[txIndex][msg.sender] = true;
        transactions[txIndex].confirmations++;
        
        emit ConfirmTransaction(msg.sender, txIndex);
        
        if (isConfirmed(txIndex)) {
            executeTransaction(txIndex);
        }
    }
    
    function executeTransaction(uint256 txIndex)
        public onlyOwner txExists(txIndex)
    {
        require(!transactions[txIndex].executed, "Already executed");
        require(isConfirmed(txIndex), "Not confirmed");
        
        Transaction storage t = transactions[txIndex];
        t.executed = true;
        
        (bool success, ) = t.to.call{value: t.value}(t.data);
        require(success, "Tx failed");
        
        emit ExecuteTransaction(msg.sender, txIndex);
    }
    
    function isConfirmed(uint256 txIndex) public view returns (bool) {
        return transactions[txIndex].confirmations >= required;
    }
    
    function getConfirmationsCount(uint256 txIndex) 
        public view returns (uint256 count) 
    {
        for (uint256 i = 0; i < owners.length; i++) {
            if (confirmations[txIndex][owners[i]]) {
                count++;
            }
        }
    }
}

5.2 零知識證明密碼學基礎

5.2.1 橢圓曲線配對

零知識證明系統(如 Groth16、PLONK)依賴橢圓曲線配對運算。配對允許在三個橢圓曲線點之間建立關係。

BLS 簽章聚合

BLS 簽章利用配對實現簽章聚合:

簽章生成:σ = d × P2
簽章驗證:e(Q, σ) = e(Q, P2)^d = e(dQ, P2) = e(P1, P2)

其中 P1 = 公鑰對應的曲線點,P2 為固定生成元。

// BN128 配對預編譯合約調用
contract PairingCheck {
    // 配對檢查函數(BN128_GADD, BN128_MUL, BN128_PAIRING)
    function pairingCheck(
        bytes memory input
    ) public view returns (bool) {
        // input 為多個 G1 和 G2 點的拼接
        // 返回所有配對乘積是否等於 1
        uint256[6] memory inputUint = _toUintArray(input);
        return bool(ecpairing(inputUint));
    }
}

結論

以太坊密碼學基礎建立在三個相互支撐的核心元件之上:secp256k1 橢圓曲線提供安全的身份認證基礎、ECDSA 簽章機制確保交易的不可否認性、Keccak-256 雜湊函數保證資料完整性、Merkle Tree 資料結構實現高效的狀態驗證。

理解這些密碼學元件的原理,對於以下應用場景至關重要:

  1. 安全錢包開發:正確實現金鑰管理與簽章
  2. 智慧合約安全:防範簽章驗證漏洞
  3. 跨鏈橋設計:理解橋接安全的密碼學基礎
  4. 零知識應用:掌握後量子密碼學演進方向

未來,以太坊將逐步引入後量子安全的密碼學方案,包括基於格(lattice)的簽章與承諾方案。這要求開發者持續關注密碼學領域的最新進展,確保應用的長期安全性。


參考文獻

  1. Standards for Efficient Cryptography (SEC) - SEC 2: Recommended Elliptic Curve Domain Parameters
  2. ANSI X9.62 - Public Key Cryptography for the Financial Services Industry
  3. NIST SP 800-186 - Recommendations for Elliptic Curve Domain Parameters
  4. Ethereum Yellow Paper - Formal Specification of Ethereum
  5. BIP-32, BIP-39, BIP-44 - Bitcoin Improvement Proposals
  6. Keccak Team - The Keccak Reference

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!