以太坊密碼學原語互動式教學:從橢圓曲線到 Merkle Patricia Tree
本文以互動式教學方式解析以太坊核心密碼學原語,包括 Keccak 雜湊函數的海綿結構與實作、secp256k1 橢圓曲線密碼學的數學原理、ECDSA 簽章機制與 recovery id 解析、Merkle Patricia Tree 的混合設計與狀態驗證。透過大量圖解說明和 Python 程式碼範例,讓讀者從直觀理解密碼學而非陷入數學公式的泥沼。
以太坊密碼學原語互動式教學:從橢圓曲線到 Merkle Patricia Tree
說到以太坊的密碼學,很多人第一反應就是「不懂」「太數學了」「看著就頭疼」。我也是這麼走過來的——剛開始學區塊鏈的時候,每次看到「橢圓曲線」四個字就自動跳過,心想「反正我知道公鑰私鑰是怎麼回事就行了」。
後來被現實毒打多了,才發現不懂密碼學真的很吃虧。比如:
- 為什麼以太坊地址是 20 bytes 而不是 32 bytes?
- 為什麼簽章有不同的 recovery id?
- 為什麼 Merkle Patricia Tree 長得那麼奇怪?
這些問題不懂密碼學根本沒法回答。所以今天這篇文章,我決定用一種輕鬆有趣的方式,把以太坊核心的密碼學原語全部拆解給你看。會有一些數學,但我不會讓你算微分方程式;會有一些程式碼,但我不會讓你一行行去實現。我們的目的不是成為密碼學博士,而是搞懂「為什麼以太坊這樣設計」。
起點:Keccak 雜湊函數——區塊鏈的萬物之源
為什麼區塊鏈離不開雜湊函數?
在開始講 Keccak 之前,我想先回答一個根本問題:為什麼區塊鏈這麼喜歡用雜湊函數?
答案超級簡單:三個特性讓它成為區塊鏈的命根子。
雜湊函數的三個核心特性:
1. 確定性(Deterministic)
hash("hello") 每次算出來都是一樣的
不像隨機數,同樣輸入每次結果不同
2. 單向性(One-way)
從輸出反推輸入是不可能的
就像從煎好的漢堡推不回生肉排
3. 抗碰撞(Collision Resistance)
幾乎不可能找到兩個不同輸入產生相同輸出
就像找不到兩把完全不同的鑰匙開同一把鎖
區塊鏈處處用到這些特性:
- 區塊的 hash 靠它
- 交易的 hash 靠它
- Merkle Tree 的計算靠它
- 智慧合約地址靠它
Keccak 的故事:NIST 競賽的贏家
Keccak 不是橫空出世的。2007 年,美國國家標準與技術研究院(NIST)舉辦了一個公開競賽,要找一個新的 SHA-3 雜湊函數標準。經過幾年的激烈角逐,Keccak 在 2012 年擊敗其他競爭者,成為 SHA-3 的基礎。
不過有個小插曲:最終標準出來時,NIST 做了一個「微妙的」修改,叫做「調整參數」。這個調整讓很多密碼學家很不爽,覺得這破壞了 Keccak 的某些數學特性。不過這是個政治議題而非技術議題,咱們就不展開了。
以太坊選擇了原始的 Keccak(而非 NIST 調整後的版本),所以你在以太坊看到的是 keccak256 而不是 sha3_256。這也是為什麼有時候你在網上搜到的 SHA-3 教程算不出以太坊的結果——因為根本不是同一個函數!
動手玩:keccak256 的直觀理解
# 用 Python 計算 keccak256
from web3 import Web3
# 這個你應該很熟悉:以太坊地址就是 keccak256 公鑰的後 20 bytes
message = "Hello, Ethereum!"
message_hash = Web3.keccak(text=message)
print(f"原始訊息: {message}")
print(f"Hash (hex): {message_hash.hex()}")
print(f"Hash (bytes): {message_hash}")
print(f"長度: {len(message_hash)} bytes")
# 試著改一個字元看看 hash 變成什麼
message2 = "Hello, Etherelum!" # 把 'e' 改成 'e'
hash2 = Web3.keccak(text=message2)
print("\n--- 改了一個字元後 ---")
print(f"Hash (hex): {hash2.hex()}")
print(f"和原來的 hash 一樣嗎? {message_hash == hash2}")
執行結果(概念):
原始訊息: Hello, Ethereum!
Hash (hex): 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
長度: 32 bytes (256 bits)
--- 改了一個字元後 ---
Hash (hex): 0x9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e
和原來的 hash 一樣嗎? False
注意到了嗎?只改了一個字元,整個 hash 翻天覆地。這就是所謂的「雪崩效應」——輸入的一點點變化,會導致輸出完全不同。這種特性讓 hash 成為區塊鏈「指紋」功能的完美選擇。
Keccak 的內部結構:不看會後悔
Keccak 的核心是一個叫做「海綿結構」(Sponge Construction)的設計。這名字挺浪漫的對吧?想象一塊海綿在吸收數據,然後擠出精華。
Keccak 海綿結構:
┌─────────────────────────────────────────────────────┐
│ 海綿函數 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 吸收階段 │ → │ 擠出階段 │ │ 狀態 │ │
│ │(Absorb) │ │(Squeeze)│ │ (1600b) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 輸入數據被切成小塊,一塊一塊「吸收」進去 │
│ 吸收完後,一塊一塊「擠出」作為輸出 │
│ │
└─────────────────────────────────────────────────────┘
1600 bits 的狀態 = 5×5×64 = 800 bytes
這是一個 5×5 的「片」(plane),每個片 64 bits
每一輪的運算叫做「round」,總共有 24 個 rounds。每個 round 包括五個步驟:
- θ(Theta):列混合
- ρ(Rho):位移
- π(Pi):置換
- χ(Chi):行混淆
- ι(Iota):添加常數
數學細節我就不展開了——說實在的,我也不完全搞懂了每一個步驟背後的數學證明。重點是:這整套設計被密碼學家仔細分析過,目前沒有發現已知的弱點。
橢圓曲線密碼學:你的私鑰其實是個大秘密
橢圓曲線不是橢圓
這裡有個冷知識:橢圓曲線跟橢圓其實沒什麼關係。它之所以叫「橢圓」,是因為它的公式跟計算橢圓周長的積分有點像——僅此而已。
以太坊用的是一條叫做 secp256k1 的橢圓曲線。它的方程式很簡單:
y² = x³ + 7 (在有限域 Fp 上)
就這樣?對,就這麼簡單。當然,實際上是在一個很大的有限域上計算,而且所有運算都要 mod p,其中:
p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
= 115792089237316195423570985008687907853269984665640564039457584007908834671663
這個數字大得荒謬——大到你寫都寫不完整。但它正好讓你在 256 bits 的空間裡工作,這是電腦最舒服的位元數。
橢圓曲線上的「加法」
橢圓曲線有個神奇的性質:在這條曲線上,你可以定義一種「加法」運算,而且這種加法滿足交換群的所有特性——封閉性、結合性、單位元、逆元素。
橢圓曲線加法的幾何意義:
情況一:P + Q(P 和 Q 是不同的點)
把 P 和 Q 連成一條線
這條線會和曲線交於第三個點 R'
然後把 R' 沿 X 軸反射,得到 R
R 就是 P + Q 的結果
情況二:P + P = 2P(倍點)
在 P 點畫切線
切線和曲線交於 Q
把 Q 反射得到 2P
數學上,這個加法的公式是:
假設 P(x1, y1), Q(x2, y2)
P ≠ Q 時:
λ = (y2 - y1) / (x2 - x1) mod p
x3 = λ² - x1 - x2 mod p
y3 = λ(x1 - x3) - y1 mod p
P = Q 時(倍點):
λ = (3x1² + a) / (2y1) mod p
x3 = λ² - 2x1 mod p
y3 = λ(x1 - x3) - y1 mod p
我知道你看到這些公式想關掉視窗了。深呼吸,讓我給你一個更直觀的理解:
把橢圓曲線想成一個時鐘,只不過這個時鐘有 2^256 個刻度。在這個時鐘上,「加法」就是沿著曲線往前跳。跳一下是 G,跳兩下是 2G,跳 k 下是 kG。
公鑰和私鑰的誕生
這就是以太坊密碼學最優雅的部分——單向函數。
私鑰:一個隨機的 256 位整數,記作 k
公鑰:k 倍的生成點,記作 K = k × G
其中 G 是 secp256k1 的生成點
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
整個以太坊的安全性都建立在這個假設上:從 k 計算 K 很簡單,但從 K 反推 k 是不可能的。這叫做「離散對數問題」(Discrete Logarithm Problem),是密碼學家認為無法在合理時間內解決的問題。
# 實際操作看看
from eth_keys import keys
import os
# 生成的私鑰
private_key_bytes = os.urandom(32)
private_key = keys.PrivateKey(private_key_bytes)
# 推導公鑰
public_key = private_key.public_key
print(f"私鑰 (32 bytes): {private_key_bytes.hex()}")
print(f"公鑰 (64 bytes): {public_key.to_bytes().hex()}")
print(f"公鑰長度: {len(public_key.to_bytes())} bytes")
# 從公鑰無法反推私鑰——這就是密碼學的力量
以太坊地址是怎麼來的?
現在我們知道了公鑰。但以太坊地址不是直接用公鑰——而是經過兩次 hash:
地址計算流程:
1. 取公鑰(64 bytes,無壓縮格式)
04 || X || Y
2. 對整個公鑗做 keccak256
keccak256(04 || X || Y)
3. 取 hash 的最後 20 bytes
address = keccak256(...)[12:]
4. 通常顯示為 0x... 格式
address = "0x" + last_20_bytes_hex
為什麼只取最後 20 bytes?這是歷史原因和方便性的平衡。20 bytes = 40 個十六進位字元,長度適中,不太短也不太長。取「最後」而不是「最先」沒有密碼學原因,純粹是當年做設計時的一個任意選擇。
# 完整的地址推導
from web3 import Web3
from eth_keys import keys
# 生成一個測試私鑰
test_private_key = bytes.fromhex(
'a' * 64 # 這只是演示用,別用真的!
)
private_key = keys.PrivateKey(test_private_key)
public_key = private_key.public_key
# 以太坊地址:keccak256(公鑰)[12:]
public_key_bytes = public_key.to_bytes()
address = Web3.keccak(public_key_bytes)[12:].hex()
print(f"公鑰: {public_key_bytes.hex()}")
print(f"以太坊地址: 0x{address}")
ECDSA 簽章:證明你擁有私鑰
簽章的直覺
ECDSA(橢圓曲線數位簽章算法)讓你可以「證明」你擁有某個私鑰對應的公鑰,但又不把私鑰洩漏出去。這就像你在一份文件上簽名——銀行可以驗證是你的筆跡,但他們不會要求你把印章交給他們。
ECDSA 的基本原理:
簽名過程(你需要私鑰):
1. 選一個隨機數 k
2. 計算 R = k × G
3. 計算 r = R.x(取 R 的 x 座標)
4. 計算 s = k⁻¹ × (hash + r × private_key) mod p
驗證過程(只需要公鑰):
1. 計算 w = s⁻¹ mod p
2. 計算 u1 = hash × w mod p
3. 計算 u2 = r × w mod p
4. 計算 P = u1 × G + u2 × public_key
5. 驗證 P.x = r
如果成立,代表簽名有效
這套機制厲害的地方在於:驗證者從頭到尾都接觸不到私鑰或隨機數 k。只能看到 (r, s) 這個簽名組合,然後決定「有效」或「無效」。
recovery id 的秘密
如果你用 Web3.js 或其他庫生成過以太坊交易,你會發現簽名返回的不只是 r 和 s,還有一個 v 值(通常叫 recovery id 或 rec_id)。這個 v 的作用是什麼?
答案是:用來從簽名「恢復」出簽名者的地址。
從簽名恢復地址的原理:
給定 (r, s, v) 和消息 hash,有兩個可能的公鑰:
- 一個是 r 對應的 x 座標(假設 y 是偶數)
- 一個是 r 對應的 x 座標(假設 y 是奇數)
v 用來指定用哪個:
- v = 27 或 0:y 是偶數
- v = 28 或 1:y 是奇數
所以從簽名可以唯一確定公鑰
這就是以太坊著名的「EOA 帳戶模型」的基礎。沒有智能合約錢包之前,每筆交易只需要簽名——驗證者可以從簽名直接恢復出發送者地址,不需要事先知道這個地址。這種設計極大地簡化了帳戶模型。
// 用 ethers.js 看看簽名的結構
const ethers = require('ethers');
const wallet = new ethers.Wallet.createRandom();
const message = "Hello, Ethereum!";
const signature = wallet.signMessage(message);
console.log("簽名組成:");
console.log("r:", signature.slice(0, 66));
console.log("s:", "0x" + signature.slice(66, 130));
console.log("v:", parseInt(signature.slice(130, 132), 16));
// 驗證簽名
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
console.log("原始地址:", wallet.address);
console.log("恢復地址:", recoveredAddress);
console.log("匹配:", wallet.address === recoveredAddress);
Merkle Patricia Tree:以太坊的大殺器
為什麼區塊鏈需要 Merkle Tree?
先從普通 Merkle Tree 說起。假設你有 4 筆交易:
交易 A: "Alice 轉給 Bob 1 ETH"
交易 B: "Carol 轉給 Dave 2 ETH"
交易 C: "Eve 轉給 Frank 3 ETH"
交易 D: "Grace 轉給 Helen 4 ETH"
你把這些交易的 hash 組織成一個 Merkle Tree:
Root
/ \
HashAB HashCD
/ \ / \
HashA HashB HashC HashD
| | | |
A B C D
現在的好處是:
- 如果有人說「A 是正確的」,你只需要給他 A 和 HashB
- 他可以驗證 Hash(A) + Hash(B) = HashAB
- 然後問另一個人要 HashAB 的兄弟節點 HashCD
- 就能一路驗證到 Root
在比特幣和以太坊中,Merkle Tree 讓「輕客戶端」可以只下載區塊頭(header),就能驗證某筆交易確實在區塊中。這是 SPV(Simplified Payment Verification)的核心。
Patricia Trie:高效查詢的前綴樹
普通 Merkle Tree 的問題是:如果你要查「所有以 0x1234 開頭的帳戶」,Merkle Tree 完全幫不上忙。
Patricia Trie(又叫前綴樹或基數樹)解決了這個問題。它的特點是:
Patricia Trie 的查詢:
假設儲存了以下鍵值對:
- "apple": 100
- "app": 200
- "application": 300
- "banana": 400
儲存結構:
root
└── "app" # 共用前綴
├── "" -> 200 (app 本身)
├── "lication" -> 300
└── "le" -> 100
└── "banana" -> 400
查詢 "apple":
root → "app" → "le" → 找到!
時間複雜度 = O(key長度),不是 O(n)
每一步都跟 key 的下一個字元相關,所以查詢時間只取決於 key 的長度,與總共有多少資料無關。以太坊的帳戶有數百萬個,但查詢任意帳戶的狀態只需要十幾步。
Merkle Patricia Tree:以太坊的混合設計
以太坊沒有單純用 Merkle Tree 或 Patricia Trie,而是發明了一種混合結構:Merkle Patricia Trie(MPT)。這個設計結合了兩者的優點。
以太坊有三棵主要的 MPT:
1. 狀態 Trie(State Trie)
- 儲存所有帳戶的完整狀態
- Key = sha3(以太坊地址) 的最後 64 個 nibbles(4 bits)
- Value = rlp(帳戶資訊)
2. 儲存 Trie(Storage Trie)
- 每個合約帳戶有自己的 Storage Trie
- 儲存合約的所有狀態變數
- Key = sha3(變數 slot) 的最後 64 個 nibbles
3. 交易 Trie(Transaction Trie)
- 每個區塊有自己的 Transaction Trie
- Key = 交易在區塊中的索引
- Value = 交易的 RLP 編碼
4. 收據 Trie(Receipt Trie)
- 每個區塊有自己的 Receipt Trie
- 儲存交易的執行結果
MPT 的節點有三種類型:
MPT 節點類型:
1. 分支節點(Branch Node)
- 17 個子節點(0-15 的路徑 + 1 個 value)
- 用於分叉點
2. 葉子節點(Leaf Node)
- 路徑編碼(nibbles)
- 終端值
3. 擴展節點(Extension Node)
- 共用的路徑前綴
- 單個子節點
- 用於壓縮常見前綴
實際看看 MPT 的結構
# 用一個簡化的例子看看 MPT 的運作
# 這不是真正的以太坊客戶端代碼,只是教學示範
# 假設我們有兩個帳戶:
# 地址 0x1234...:餘額 1 ETH, nonce 0
# 地址 0x5678...:餘額 2 ETH, nonce 1
# 第一步:計算路徑
# 路徑 = keccak256(地址) 的 nibbles
address1 = "0x1234567890123456789012345678901234567890"
path1 = keccak256(address1.encode()) # 32 bytes = 64 nibbles
# 以太坊 MPT 只用路徑的最後 64 nibbles
path1_nibbles = nibbles(path1)[-64:]
# 第二步:構建葉子節點
# 葉子節點格式:encoded_path + value
leaf1 = encode_path(path1_nibbles, is_leaf=True) + rlp(value1)
# 第三步:構建分支和擴展節點
# 這就是以太坊客戶端做的事情...
MPT 的設計非常精巧,但代價是複雜性。以太坊的狀態爆炸問題(state bloat)很大程度上來自 MPT 的設計——每個狀態更新都需要更新整條路的 hash,這導致了龐大的計算和存儲開銷。這也是為什麼以太坊正在開發 Verkle Tree 作為未來的替代方案。
密碼學在以太坊的應用全景圖
各類操作的密碼學依賴
以太坊操作 ────────────────────────────────────────────────────── 密碼學原語
創建帳戶 (createAccount) → 橢圓曲線 (secp256k1) 產生私鑰/公鑰對
↓
keccak256 公鑰 → 取後 20 bytes = 地址
發送交易 (sendTransaction) → ECDSA 簽章交易
↓
keccak256(交易內容) = tx hash
↓
驗證簽章 → 恢復 sender 地址
部署合約 (deployContract) → 合約位元組碼 hash
↓
CREATE2: keccak256(0xff + sender + salt + code_hash)
= 預定址
狀態查詢 (eth_getBalance) → MPT 結構驗證
↓
merkle_proof: 路徑上的 hash 串聯
↓
驗證 = hash(leaf) 沿途正確計算
智能合約執行 (eth_call) → 狀態變數讀寫
↓
SLOAD: 從 MPT 讀取
↓
SSTORE: 更新 MPT
質押驗證 (Beacon Chain) → BLS 簽章聚合
↓
質押者註冊: keccak256 + 橢圓曲線
↓
區塊簽章: BLS 聚合多個簽章
密碼學的演化:以太坊的未來方向
當前面臨的挑戰:
1. 量子計算威脅
- 量子電腦可以破解 ECDSA(用 Shor's algorithm)
- 這是為什麼以太坊在研究後量子密碼學
2. 狀態爆炸
- MPT 的存儲開銷太大
- Verkle Tree 是潛在解決方案
3. 隱私問題
- 所有交易公開可見
- ZK 證明提供解決方向
未來的密碼學方向:
├── 後量子 ECDSA(基於 Hash 的簽章?)
├── Verkle Tree(替代 MPT)
├── ZK-SNARK/STARK(隱私和擴容)
├── 聚合簽章(BLS,減少驗證成本)
└── 多方計算(MPC,分散信任)
結語:密碼學是區塊鏈的根基
寫到這裡,我帶你看了以太坊密碼學的四大支柱:
- Keccak 雜湊函數:區塊鏈的指紋,無處不在
- 橢圓曲線 secp256k1:私鑰到公鑰的單向函數
- ECDSA:不透露私鑰的簽章機制
- Merkle Patricia Tree:高效、可驗證的狀態結構
這些密碼學原語不是獨立存在的——它們像齒輪一樣相互咬合,共同支撐起整個以太坊系統的運作。理解了這些基礎,你就有了看懂以太坊更深層設計的本錢。
下次當你看到「EIP-2930 改變了交易簽章格式」或「EIP-4844 引入 Blob」這類新聞時,希望你能會心一笑——因為你知道這些改動在密碼學層面意味著什麼。
密碼學的世界很深,但起點就在這裡。繼續探索吧!
延伸閱讀
免責聲明:本網站內容僅供教育與資訊目的。密碼學是高度專業的領域,實際系統的設計和實作需要專業審計。在部署任何基於密碼學的系統前,請諮詢合格的密碼學專家。
數據截止日期:2026-03-27
相關文章
- 以太坊密碼學互動實驗室:瀏覽器可執行範例與 secp256k1/ECDSA 深度教學 — 本文提供一套完整的以太坊密碼學互動式學習模組,讓讀者能夠在瀏覽器中直接執行和實驗密碼學運算。涵蓋 secp256k1 橢圓曲線運算、ECDSA 簽章與驗證、Keccak-256 雜湊、以及零知識證明等核心主題。所有範例都可以在現代瀏覽器中直接運行,無需額外的開發環境配置。提供完整的 JavaScript 程式碼範例,包括點加法、標量乘法、交易簽章模擬、Merkle 樹構建等互動實驗。
- 橢圓曲線離散對數問題的直覺解釋:以太坊密碼學初學者完整指南 — 橢圓曲線離散對數問題(ECDLP)是現代密碼學最重要的安全基石之一,也是以太坊地址和簽名系統安全性的核心保障。本文用直覺的比喻、日常生活的例子、和逐步建立的概念,幫助任何具有基礎數學素養的讀者掌握這個看似複雜的密碼學原語。我們從「什麼是離散對數」這個最基本的問題開始,逐步建立對橢圓曲線密碼學的直覺理解。
- 以太坊密碼學原語直覺解釋:橢圓曲線、布隆過濾器與 Keccak 的工程視角 — 本文從工程師的視角出發,提供橢圓曲線、布隆過濾器(Blooom Filter)等密碼學原語的直覺性解釋、完整的數學推導、以及可直接使用的程式碼範例,幫助讀者建立對這些密碼學原語的深入理解。涵蓋 ECDSA 簽名、Keccak-256 哈希、布隆過濾器的設計原理與實際應用。
- 橢圓曲線離散對數問題:從代數幾何到密碼學安全的直覺解釋 — 橢圓曲線離散對數問題(ECDLP)是以太坊密碼學安全的數學基石。本文從直覺出發,逐步建立對ECDLP的完整理解,涵蓋群論基礎、橢圓曲線幾何、離散對數問題的定義與困難性、以及在以太坊中的實際應用場景。我們將深入分析為何256位金鑰能提供與4096位RSA相當的安全性,並探討量子計算對現有密碼系統的潛在威脅。這是理解以太坊底層密碼學安全性的必讀文章。
- 以太坊密碼學基礎完整指南:橢圓曲線密碼學、簽章機制與 Merkle Tree 結構 — 本文深入分析以太坊密碼學系統的三大支柱:secp256k1 橢圓曲線與 ECDSA 簽章機制的數學原理、KECCAK-256 雜湊函數的設計特點、以及 Patricia Merkle Trie 資料結構在狀態管理中的關鍵角色。我們從密碼學理論出發,經過詳盡的數學推導,最終落實到 Solidity、Go 與 Rust 的實際程式碼範例。涵蓋離散對數問題、點加法/倍增運算、ECDSA 簽章驗證、Merkle Proof、EIP-1559 等核心概念的完整技術解析。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案完整列表
- Solidity 文檔 智慧合約程式語言官方規格
- EVM 代碼庫 EVM 實作的核心參考
- Alethio EVM 分析 EVM 行為的正規驗證
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!