以太坊密碼學原語直覺解釋:橢圓曲線、布隆過濾器與 Keccak 的工程視角
本文從工程師的視角出發,提供橢圓曲線、布隆過濾器(Blooom Filter)等密碼學原語的直覺性解釋、完整的數學推導、以及可直接使用的程式碼範例,幫助讀者建立對這些密碼學原語的深入理解。涵蓋 ECDSA 簽名、Keccak-256 哈希、布隆過濾器的設計原理與實際應用。
以太坊密碼學原語直覺解釋:橢圓曲線、布隆過濾器與 Keccak 的工程視角
概述
密碼學是以太坊安全的基石。理解橢圓曲線、布隆過濾器(Blooom Filter)等密碼學原語的工作原理,對於開發安全的智能合約、設計高效的區塊鏈應用、以及評估協議安全性至關重要。本文從工程師的視角出發,提供直覺性的解釋、完整的數學推導、以及可直接使用的程式碼範例,幫助讀者建立對這些密碼學原語的深入理解。
不同於純數學的抽象敘述,本文強調「為什麼需要這樣設計」以及「如何在實際應用中使用」。無論你是智能合約開發者、區塊鏈架構師,還是安全研究者,本文都將幫助你建立實用的密碼學直覺。
一、密碼學基礎回顧
1.1 密碼學在以太坊中的角色
以太坊的密碼學應用場景可分為以下幾類:
密碼學在以太坊中的應用:
┌─────────────────────────────────────────────────────────────────────┐
│ 以太坊密碼學應用 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 身份識別 │
│ ├─ 帳戶地址:keccak256(公鑰) % 2^160 │
│ ├─ 簽名驗證:ECDSA(secp256k1 橢圓曲線) │
│ └─ 私鑰保護:橢圓曲線離散對數難題 │
│ │
│ 資料完整性 │
│ ├─ 區塊哈希:Keccak-256 │
│ ├─ 交易哈希:keccak256(交易資料) │
│ ├─ Merkle 樹:成對哈希 │
│ └─ 狀態根:多層級哈希驗證 │
│ │
│ 隱私保護 │
│ ├─ 零知識證明:zk-SNARKs / zk-STARKs │
│ ├─ 承諾方案:Pedersen Commitment │
│ └─ 同態加密:部分同態用於計算驗證 │
│ │
│ 共識機制 │
│ ├─ 隨機數信標:VRF(Verifiable Random Function) │
│ ├─ 驗證者投票:BLS 簽名聚合 │
│ └─ 延遲函數:VDF(Verifiable Delay Function) │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.2 單向函數與難題假設
所有密碼學安全的基礎是「單向函數」——易於計算但難以反向的函數:
單向函數定義:
一個函數 f: X → Y 被稱為單向函數,當且僅當:
1. 前向計算容易:
∀x ∈ X,計算 f(x) 是容易的(多項式時間)
2. 反向計算困難:
∀y ∈ Image(f),找到 x' 使得 f(x') = y 是困難的
(沒有概率多項式時間算法)
密碼學難題假設:
1. 整數分解難題(RSA 基礎)
- 給定 N = p × q,求 p 和 q
- 目前最佳算法:數域篩選法,指數亞演算法
2. 離散對數難題(DLP)
- 給定 g, h ∈ G,求 x 使得 g^x = h
- 目前最佳算法:指標計算法,指數時間
3. 橢圓曲線離散對數難題(ECDLP)
- 給定 P, Q ∈ E(F_p),求 x 使得 Q = x × P
- 目前最佳算法:Pollard's rho,指數時間
- 優勢:同等安全強度下,金鑰長度更短
二、橢圓曲線密碼學
2.1 橢圓曲線的直覺理解
橢圓曲線並不是橢圓,而是一種具有特殊代數結構的平面曲線:
橢圓曲線的一般方程:
y² = x³ + ax + b (其中 4a³ + 27b² ≠ 0 以確保無奇點)
以太坊使用的曲線:secp256k1
參數定義:
- a = 0
- b = 7
- p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
- n = 115792089237316195423570985008687907852837564279074904382605163141518161494337(階)
- G = 生成元基點
為什麼選擇 a=0, b=7?
- 曲線方程簡化為:y² = x³ + 7
- 這稱為「Curve25519 家族」的設計哲學
- 簡化的計算減少實現錯誤的可能性
直覺解釋:可以把橢圓曲線想像成一組滿足特定規則的「點」。這些點之間可以進行「加法」運算,而這個加法運算有一個特殊性質——連續加法形成一個循環群,類似於時鐘的 12 小時制。
點加法的直覺理解:
想象一條橢圓曲線在平面上,選擇兩個點 P 和 Q:
1. 連接 P 和 Q,畫一條直線
2. 這條直線會與曲線交於第三個點 R'
3. 關於 x 軸反射 R' 得到 R
4. 那麼 R = P + Q
特殊情況:
- P = Q 時,使用切線而非連接線
- P + (-P) = O(無窮遠點)
代數定義:
設 P = (x1, y1), Q = (x2, y2), R = P + Q = (x3, y3)
若 P ≠ Q:
λ = (y2 - y1) / (x2 - x1)
x3 = λ² - x1 - x2
y3 = λ(x1 - x3) - y1
若 P = Q(倍點):
λ = (3x1² + a) / (2y1)
x3 = λ² - 2x1
y3 = λ(x1 - x3) - y1
2.2 離散對數難題的工程意義
橢圓曲線離散對數難題(ECDLP)是橢圓曲線密碼學安全的核心:
ECDLP 定義:
給定:
- 基點 G(公開)
- 公鑰 Q = x × G(公開)
- 橢圓曲線參數(公開)
求解:
- 私鑰 x(保密)
直覺解釋:
「我知道 G 和 2G、3G、4G...,但我不知道 x 的具體值,
只知道 Q = x × G」
這就像:
- 公開:時鐘的起點和終點
- 保密:走了多少步
- 困難:只能一步一步數,不知道捷徑
安全性比較:
┌─────────────────────────────────────────────────────────────────────┐
│ 安全強度與金鑰長度對比 │
├──────────────────────┬──────────────────────┬──────────────────────┤
│ 對稱密鑰(AES) │ RSA/整數分解 │ 橢圓曲線 │
├──────────────────────┼──────────────────────┼──────────────────────┤
│ 80 bits │ 1024 bits │ 160 bits │
│ 112 bits │ 2048 bits │ 224 bits │
│ 128 bits │ 3072 bits │ 256 bits │
│ 192 bits │ 7680 bits │ 384 bits │
│ 256 bits │ 15360 bits │ 512 bits │
└──────────────────────┴──────────────────────┴──────────────────────┘
以太坊選擇 secp256k1(256 bits 安全性):
- 金鑰大小僅 32 bytes
- 比 RSA-2048 小約 8 倍
- 簽名生成/驗證速度快 10-100 倍
2.3 完整數學推導:標量乘法
標量乘法是橢圓曲線密碼學中最常用的運算:Q = x × G
"""
橢圓曲線標量乘法實現
標量乘法的核心問題:
- 直接計算 x 個 G 需要 O(x) 次加法
- 當 x 很大時(如 256 位私鑰),這是不可行的
解決方案:二元法(Binary Method)
- 將 x 表示為二進制
- 從左到右或從右到左處理
- 每次處理一位,只需要倍點或加點
"""
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
if self.x is None and self.y is None:
return "Point at Infinity (O)"
return f"Point({self.x:#x}, {self.y:#x})"
class EllipticCurve:
def __init__(self, a, b, p):
"""
初始化橢圓曲線:y² = x³ + ax + b (mod p)
"""
self.a = a
self.b = b
self.p = p
def is_on_curve(self, P):
"""檢查點是否在曲線上"""
if P.x is None: # 無窮遠點
return True
lhs = (P.y * P.y) % self.p
rhs = (P.x**3 + self.a * P.x + self.b) % self.p
return lhs == rhs
def point_add(self, P, Q):
"""點加法:P + Q"""
# P 為無窮遠點
if P.x is None:
return Q
# Q 為無窮遠點
if Q.x is None:
return P
# P = -Q(互為加法逆元)
if P.x == Q.x and (P.y + Q.y) % self.p == 0:
return Point(None, None) # 無窮遠點
# 計算斜率
if P.x != Q.x or P.y != Q.y:
# P ≠ Q 普通情況
lam = (Q.y - P.y) * pow(Q.x - P.x, -1, self.p) % self.p
else:
# P = Q 倍點
lam = (3 * P.x * P.x + self.a) * pow(2 * P.y, -1, self.p) % self.p
# 計算新點
x3 = (lam * lam - P.x - Q.x) % self.p
y3 = (lam * (P.x - x3) - P.y) % self.p
return Point(x3, y3)
def scalar_multiply(self, k, P):
"""
標量乘法:k × P
使用二元法(Binary Method / Double-and-Add)
時間複雜度:O(log k)
直覺:
- 將 k 視為二進制:k = Σ bi × 2^i
- k × P = Σ bi × (2^i × P)
- 2^i × P 可以通過重複倍點計算
"""
result = Point(None, None) # 無窮遠點作為單位元
addend = Point(P.x, P.y) # 複製點
while k:
# 如果當前位為 1,則加到結果
if k & 1:
result = self.point_add(result, addend)
# 倍點(為下一位做準備)
addend = self.point_add(addend, addend)
# 移向下一位
k >>= 1
return result
# 以太坊 secp256k1 曲線參數
SECP256K1_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
SECP256K1_A = 0
SECP256K1_B = 7
SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
# secp256k1 的生成元 G
SECP256K1_GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
SECP256K1_GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
# 創建曲線實例
curve = EllipticCurve(SECP256K1_A, SECP256K1_B, SECP256K1_P)
G = Point(SECP256K1_GX, SECP256K1_GY)
# 測試標量乘法
private_key = 0x1234567890abcdef # 範例私鑰
public_key = curve.scalar_multiply(private_key, G)
print(f"私鑰: {private_key:#x}")
print(f"公鑰: {public_key}")
# 驗證:k × G + (-k) × G = O
neg_k_public = curve.scalar_multiply(-private_key % SECP256K1_N, G)
sum_point = curve.point_add(public_key, neg_k_public)
print(f"kG + (-k)G = O: {sum_point.x is None and sum_point.y is None}")
2.4 ECDSA 簽名:完整實現
ECDSA(橢圓曲線數位簽名算法)是以太坊交易的基礎:
ECDSA 簽名流程:
輸入:
- 私鑰 d(隨機數)
- 消息 m
- 曲線參數 (p, a, b, G, n)
簽名生成:
1. 計算消息哈希:e = H(m)
2. 選擇隨機數 k(1 ≤ k < n)
3. 計算點 R = k × G = (x1, y1)
4. 計算 r = x1 mod n(如果 r = 0,重新選擇 k)
5. 計算 s = k⁻¹ × (e + r × d) mod n(如果 s = 0,重新選擇 k)
6. 返回簽名 (r, s)
簽名驗證:
輸入:
- 公鑰 Q = d × G
- 消息 m
- 簽名 (r, s)
驗證步驟:
1. 驗證 r, s ∈ [1, n-1]
2. 計算 e = H(m)
3. 計算 u1 = e × s⁻¹ mod n
4. 計算 u2 = r × s⁻¹ mod n
5. 計算 P = u1 × G + u2 × Q
6. 如果 P = O 或 P.x mod n ≠ r,拒絕簽名
7. 否則,接受簽名
數學原理:
P = u1 × G + u2 × Q
= u1 × G + u2 × (d × G)
= (u1 + u2 × d) × G
= (e × s⁻¹ + r × d × s⁻¹) × G
= s⁻¹ × (e + r × d) × G
= s⁻¹ × k × G
= k × G = R
因此,如果簽名有效,P.x mod n = R.x = r
"""
ECDSA 簽名與驗證完整實現
"""
import hashlib
class ECDSA:
def __init__(self, curve, G, n):
self.curve = curve
self.G = G
self.n = n
def hash_message(self, message):
"""Keccak-256 哈希(以太坊風格)"""
if isinstance(message, str):
message = message.encode('utf-8')
return int.from_bytes(
hashlib.new('keccak256', message).digest(),
'big'
)
def sign(self, private_key, message):
"""
ECDSA 簽名生成
安全性注意事項:
- k 必須是密碼學安全的隨機數
- 絕對不能重用 k(會導致私鑰暴露)
- k 不能有可預測的模式
"""
e = self.hash_message(message)
# 生成隨機數 k(實際應用中需要密碼學安全的 RNG)
k = self._generate_k(message, private_key)
# 計算 R = k × G
R = self.curve.scalar_multiply(k, self.G)
# 如果 R.x mod n = 0,失敗
r = R.x % self.n
if r == 0:
raise ValueError("Random k resulted in r = 0")
# 計算 s = k⁻¹ × (e + r × d) mod n
s = pow(k, -1, self.n) * (e + r * private_key) % self.n
# 如果 s = 0,失敗
if s == 0:
raise ValueError("Random k resulted in s = 0")
# RFC 6979:使用確定性 k(避免隨機數漏洞)
# 這裡使用簡化版本
return (r, s)
def _generate_k(self, message, private_key):
"""RFC 6979 確定性 k 生成(簡化版)"""
# 實際實現應使用完整的 RFC 6979
import secrets
k = int.from_bytes(secrets.token_bytes(32), 'big') % self.n
if k < 1:
k = 1
return k
def verify(self, public_key, message, signature):
"""
ECDSA 簽名驗證
數學原理:
如果簽名 (r, s) 有效,則:
P = u1 × G + u2 × Q
其中 u1 = e × s⁻¹ mod n, u2 = r × s⁻¹ mod n
這意味著 P.x mod n = r(當簽名有效時)
"""
r, s = signature
e = self.hash_message(message)
# 步驟 1:驗證 r, s 在有效範圍內
if not (1 <= r < self.n and 1 <= s < self.n):
return False
# 步驟 2:計算 s 的模逆元
s_inv = pow(s, -1, self.n)
# 步驟 3:計算 u1 和 u2
u1 = (e * s_inv) % self.n
u2 = (r * s_inv) % self.n
# 步驟 4:計算 P = u1 × G + u2 × Q
P = self.curve.point_add(
self.curve.scalar_multiply(u1, self.G),
self.curve.scalar_multiply(u2, public_key)
)
# 步驟 5:驗證 P 不是無窮遠點
if P.x is None:
return False
# 步驟 6:驗證 P.x mod n = r
return (P.x % self.n) == r
# 使用示例
ecdsa = ECDSA(curve, G, SECP256K1_N)
# 生成簽名
private_key = 0x1234567890abcdef
public_key = curve.scalar_multiply(private_key, G)
message = "Hello, Ethereum!"
signature = ecdsa.sign(private_key, message)
print(f"簽名: r={signature[0]:#x}, s={signature[1]:#x}")
# 驗證簽名
is_valid = ecdsa.verify(public_key, message, signature)
print(f"簽名有效: {is_valid}")
# 測試篡改消息
is_valid_tampered = ecdsa.verify(public_key, "Tampered message", signature)
print(f"篡改消息後驗證: {is_valid_tampered}") # 應該為 False
2.5 以太坊地址生成
以太坊地址是公鑰的 Keccak-256 哈希的後 20 字節:
"""
以太坊地址生成:從私鑰到地址
"""
def private_key_to_address(private_key_hex):
"""
將私鑰轉換為以太坊地址
流程:
1. 私鑰 → 公鑰(橢圓曲線標量乘法)
2. 公鑰(64 bytes, 未壓縮)→ Keccak-256 哈希
3. 取哈希後 20 bytes 作為地址
4. 轉換為 0x 前綴的十六進制字串
注意:以太坊使用 Keccak-256,而非 SHA-256
"""
# 1. 解析私鑰
private_key = int(private_key_hex, 16)
# 2. 生成公鑰(64 bytes: x || y)
public_key_point = curve.scalar_multiply(private_key, G)
# 3. 構建未壓縮公鑰(04 前綴)
public_key_uncompressed = bytes.fromhex('04')
public_key_uncompressed += public_key_point.x.to_bytes(32, 'big')
public_key_uncompressed += public_key_point.y.to_bytes(32, 'big')
# 4. Keccak-256 哈希
from hashlib import new as hashlib_new
address_hash = hashlib_new('keccak256', public_key_uncompressed).digest()
# 5. 取後 20 bytes
address = '0x' + address_hash[-20:].hex()
return address
# 示例
private_key = "0x" + "ab" * 32 # 示例私鑰
address = private_key_to_address(private_key)
print(f"以太坊地址: {address}")
# 驗證常見地址
print(f"Vitalik 地址: {private_key_to_address('0x' + '00' * 31 + '01')}")
三、布隆過濾器
3.1 布隆過濾器直覺理解
布隆過濾器是一種空間效率極高的概率數據結構,用於測試元素是否屬於集合:
布隆過濾器核心思想:
問題:如何快速判斷「某元素可能存在於集合中」或「確定不存在」?
解決方案:
1. 使用一個位陣列(bit array)初始化為 0
2. 使用 k 個獨立的哈希函數
3. 對於每個加入的元素,將 k 個哈希位置設為 1
4. 查詢時,檢查所有 k 個位置是否都為 1
特性:
- 肯定不會有「漏報」(false negative):存在的元素一定會被檢測到
- 可能會有「誤報」(false positive):不存在的元素可能被誤判為存在
- 空間效率極高:每元素約 9.6 bits(1% 誤報率時)
- 不支持刪除操作
布隆過濾器示意圖:
初始化(m = 18 bits, k = 3 個哈希函數):
[0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
添加元素 "apple"(假設 h1=2, h2=5, h3=10):
[0] [0] [1] [0] [0] [1] [0] [0] [0] [0] [1] [0] [0] [0] [0] [0] [0] [0]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
添加元素 "banana"(假設 h1=5, h2=9, h3=14):
[0] [0] [1] [0] [0] [1] [0] [0] [0] [1] [1] [0] [0] [0] [1] [0] [0] [0]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
查詢 "apple":
- h1=2, h2=5, h3=10 → 全部為 1 → 返回 True(可能存在)
查詢 "orange"(假設 h1=3, h2=7, h3=15):
- h1=3 → 0 → 返回 False(確定不存在)
3.2 數學推導:參數選擇
布隆過濾器的最優參數可以通過數學推導得出:
符號定義:
- n:預期插入元素數量
- m:位陣列大小(bits)
- k:哈希函數數量
- p:誤報率(false positive rate)
數學推導:
1. 單個哈希函數將元素映射到 m 個位置之一
某個特定位置為 0 的概率:(1 - 1/m)
2. k 個哈希函數都沒有映射到某位置的概率:
P(該位置為 0) = (1 - 1/m)^k
3. 插入 n 個元素後,該位置仍為 0 的概率:
P(該位置為 0) = (1 - 1/m)^(kn) ≈ e^(-kn/m)
4. 因此,該位置為 1 的概率(誤報概率):
p = 1 - (1 - 1/m)^(kn) ≈ 1 - e^(-kn/m)
最優 k 推導:
對 p = (1 - e^(-kn/m))^k 求導並設為 0
得到最優解:k = (m/n) × ln(2)
最優 m 推導(給定 n 和目標 p):
p = (1 - e^(-kn/m))^k
取 k = (m/n) × ln(2) 代入:
p = (1 - e^(-ln(2)))^((m/n)×ln(2))
= (0.5)^((m/n)×ln(2))
兩邊取對數:
ln(p) = (m/n) × (ln(2))²
m = n × ln(2)² / ln(p)
= n × 0.7 / ln(p)
≈ n × 1.44 / ln(p)
常用參數表:
┌─────────────────────────────────────────────────────────────────────┐
│ 布隆過濾器參數選擇表 │
├─────────────┬─────────────┬─────────────┬───────────────────────────┤
│ 元素數(n) │ 誤報率(p) │ m bits │ k 個哈希函數 │
├─────────────┼─────────────┼─────────────┼───────────────────────────┤
│ 1,000 │ 1% │ 9,580 │ 7 │
│ 1,000 │ 0.1% │ 14,390 │ 10 │
│ 1,000 │ 0.01% │ 19,210 │ 14 │
│ 10,000 │ 1% │ 95,800 │ 7 │
│ 10,000 │ 0.1% │ 143,900 │ 10 │
│ 100,000 │ 1% │ 958,000 │ 7 │
│ 100,000 │ 0.1% │ 1,439,000 │ 10 │
│ 1,000,000 │ 1% │ 9,580,000 │ 7 │
│ 1,000,000 │ 0.1% │ 14,390,000 │ 10 │
└─────────────┴─────────────┴─────────────┴───────────────────────────┘
空間節省:
- 使用布隆過濾器 vs 哈希表
- 1% 誤報率:節省約 90% 空間
- 0.1% 誤報率:節省約 85% 空間
3.3 完整實現
"""
布隆過濾器完整實現
"""
import hashlib
class BloomFilter:
"""
布隆過濾器實現
參數:
- size: 位陣列大小(bits)
- hash_count: 哈希函數數量
"""
def __init__(self, size, hash_count):
self.size = size
self.hash_count = hash_count
self.bit_array = [0] * size
@classmethod
def with_fp_rate(cls, n, fp_rate):
"""
根據預期元素數量和目標誤報率創建布隆過濾器
公式:
m = -n × ln(p) / (ln(2)²)
k = (m/n) × ln(2)
"""
import math
m = -(n * math.log(fp_rate)) / (math.log(2) ** 2)
k = (m / n) * math.log(2)
return cls(int(m), int(k))
def _get_hash_positions(self, item):
"""
計算元素的 k 個哈希位置
使用雙哈希技巧(Double Hashing):
h(i) = h1 + i × h2 mod m
這樣只需要兩個哈希函數:
- h1 = hash(item)
- h2 = hash(item + salt)
"""
if isinstance(item, str):
item = item.encode('utf-8')
elif isinstance(item, int):
item = item.to_bytes((item.bit_length() + 7) // 8, 'big')
# 計算兩個基礎哈希
h1 = int.from_bytes(
hashlib.sha256(item).digest()[:8], 'big'
) % self.size
h2 = int.from_bytes(
hashlib.sha256(item + b'salt').digest()[:8], 'big'
) % self.size
# 雙哈希生成 k 個位置
for i in range(self.hash_count):
yield (h1 + i * h2) % self.size
def add(self, item):
"""添加元素到布隆過濾器"""
for position in self._get_hash_positions(item):
self.bit_array[position] = 1
def __contains__(self, item):
"""檢查元素是否可能存在"""
return all(
self.bit_array[pos] == 1
for pos in self._get_hash_positions(item)
)
def might_contain(self, item):
"""與 __contains__ 相同"""
return self.__contains__(item)
def get_false_positive_rate(self, n_added):
"""
估算當前誤報率
基於:p = (1 - e^(-kn/m))^k
"""
import math
return (1 - math.exp(-self.hash_count * n_added / self.size)) ** self.hash_count
# 以太坊應用示例:錢包地址白名單
def create_address_whitelist(addresses, fp_rate=0.001):
"""
創建地址白名單布隆過濾器
用途:
- 快速檢查地址是否在白名單中
- 節省儲存空間(相比完整地址列表)
- 區塊瀏覽器惡意地址檢測
"""
bloom = BloomFilter.with_fp_rate(len(addresses), fp_rate)
for address in addresses:
bloom.add(address.lower()) # 統一為小寫
return bloom
# 示例使用
whitelist = create_address_whitelist([
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1',
'0x8Ba1f109551bD432803012645Ac136ddd64DBA72',
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', # vitalik.eth
])
# 測試
test_addresses = [
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1', # 在白名單中
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', # 在白名單中
'0x1234567890abcdef1234567890abcdef12345678', # 不在白名單中
]
for addr in test_addresses:
result = addr in whitelist
print(f"{addr[:20]}...: {'可能存在' if result else '確定不存在'}")
3.4 以太坊中的布隆過濾器應用
以太坊的事件日誌系統使用布隆過濾器來實現高效的主題過濾:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title EthereumEventFilter
* @dev 以太坊事件過濾器實現
*
* 以太坊使用布隆過濾器來快速識別哪些區塊可能包含感興趣的日誌
* 客戶端先檢查區塊的bloom filter,如果不通過則跳過
* 如果通過,再精確搜索日誌
*/
contract EthereumEventFilter {
// 合約事件
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
/**
* @dev 計算地址的布隆過濾器位置
*
* 以太坊的布隆過濾器使用 11 個不同的哈希位置
* h(i) = keccak256(address || k),取低 11 個 bits
*/
function getAddressBloomPositions(address addr) public pure returns (uint256[11] memory) {
bytes32 hash = keccak256(abi.encodePacked(addr));
uint256[11] memory positions;
for (uint256 i = 0; i < 11; i++) {
// 取 hash 的不同部分
positions[i] = uint256(keccak256(abi.encodePacked(hash, i))) % 2048;
}
return positions;
}
/**
* @dev 計算 topic 的布隆過濾器位置
*/
function getTopicBloomPositions(bytes32 topic) public pure returns (uint256[11] memory) {
uint256[11] memory positions;
for (uint256 i = 0; i < 11; i++) {
positions[i] = uint256(keccak256(abi.encodePacked(topic, i))) % 2048;
}
return positions;
}
/**
* @dev 模擬以太坊的日誌bloom filter
*
* 以太坊區塊頭包含一個 bloom filter:
* - 2048 bits (256 bytes)
* - 用於快速識別區塊中的日誌
* - indexed 參數會被加入 bloom filter
*/
struct LogBloom {
uint256[8] words; // 2048 bits = 8 × 256 bits
}
function addToBloom(LogBloom storage bloom, uint256 position) internal {
bloom.words[position / 256] |= 1 << (position % 256);
}
function checkBloom(LogBloom memory bloom, uint256 position) internal pure returns (bool) {
return (bloom.words[position / 256] & (1 << (position % 256))) != 0;
}
/**
* @dev 將事件添加到 bloom filter
*/
function addEventToBloom(
LogBloom storage bloom,
address[] memory addresses,
bytes32[] memory topics
) internal {
// 添加地址
for (uint256 i = 0; i < addresses.length; i++) {
uint256[11] memory positions = getAddressBloomPositions(addresses[i]);
for (uint256 j = 0; j < 11; j++) {
addToBloom(bloom, positions[j]);
}
}
// 添加 topics
for (uint256 i = 0; i < topics.length; i++) {
uint256[11] memory positions = getTopicBloomPositions(topics[i]);
for (uint256 j = 0; j < 11; j++) {
addToBloom(bloom, positions[j]);
}
}
}
}
四、Keccak 哈希函數
4.1 Keccak 與 SHA-3 的關係
以太坊使用 Keccak-256,而非標準化的 SHA-3:
重要區別:Keccak-256 ≠ SHA3-256
歷史:
- 2007 年:NIST 宣佈舉辦 SHA-3 競賽
- 2012 年:Keccak 被選為 SHA-3 標準
- 2015 年:NIST 發布 FIPS 202(SHA-3 標準)
- 問題:在標準化過程中,NIST 修改了內部結構
差異:
- SHA-3 家族使用名為 c=256 的配置(輸出長度 = 碰撞阻力)
- Keccak 原始設計使用 c=512(更高的安全性邊界)
- 以太坊使用 Keccak-256 = Keccak[c=512, r=1088]
這導致:
- keccak256("") = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
- sha3_256("") = 0x7a614fd1061ad1afb4cdf1dd1f510f2b517aecd4a78a81fc7b68a5d16bb1fb71
4.2 Keccak 海綿結構
Keccak 使用海綿結構(Sponge Construction):
Keccak 海綿結構:
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 海綿結構示意圖 │
│ │
│ ┌─────────────┐ │
│ │ 輸入 │ → ┌───┐ → ┌───────────────┐ → ┌───┐ → ┌─────────┐ │
│ │ (Padding) │ │ r │ │ │ │ r │ │ 輸出 │ │
│ └─────────────┘ └───┘ │ 吸收階段 │ └───┘ │ (擷取) │ │
│ │ (Absorbing) │ └─────────┘ │
│ │ │ │
│ ┌─────────────┐ │ ┌─────────┐ │ │
│ │ 初始狀態 │◄──────────│ │ Keccak │ │ │
│ │ (1600 b) │ │ │ f 函數 │ │ │
│ └─────────────┘ │ └─────────┘ │ │
│ │ │ │ │
│ └───────────────────┼───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ 擠出階段 │ │
│ │ (Squeezing) │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
參數說明:
- r (rate): 每次處理的位元數(對於 Keccak-256,r = 1088)
- c (capacity): 隱藏狀態(對於 Keccak-256,c = 512)
- 總狀態 = r + c = 1600 bits
操作階段:
1. 填充(Padding):
- 添加 '10*1' 到消息末尾
- 確保消息長度是 r 的倍數
2. 吸收階段(Absorbing):
- 將輸入分成 r-bit 塊
- 與狀態前 r 位 XOR
- 應用 Keccak-f 函數
- 重複直到處理完所有塊
3. 擠出階段(Squeezing):
- 從狀態讀取 r bits
- 應用 Keccak-f 函數(如需更多輸出)
- 重複直到獲得所需長度
4.3 Keccak-f 函數(狀態更新)
"""
Keccak-f 函數簡化實現
注意:這是一個概念性實現,用於教學目的
實際的 Keccak 實現需要優化的位操作
"""
def keccak_f(state):
"""
Keccak-f 函數:1600-bit 狀態的排列
狀態組織為 5×5×64 的三維陣列:
- 5 rows (x)
- 5 columns (y)
- 64 slices (z,每個 64 bits)
操作序列:θ → ρ → π → χ → ι
"""
# 初始化 5x5 狀態陣列(每個元素 64 bits)
A = [[state[64*(5*y + x):64*(5*y + x + 1)] for x in range(5)] for y in range(5)]
# 將 bytes 轉換為整數陣列以便操作
for y in range(5):
for x in range(5):
A[y][x] = int.from_bytes(A[y][x], 'little')
# Keccak-f rounds
R = 0x0000000000000001 # 初始圓常數
for round_num in range(24):
# θ (Theta): 列奇偶校驗混合
C = [A[y][0] ^ A[y][1] ^ A[y][2] ^ A[y][3] ^ A[y][4] for y in range(5)]
D = [C[(x - 1) % 5] ^ rot(C[(x + 1) % 5], 1) for x in range(5)]
for y in range(5):
for x in range(5):
A[y][x] ^= D[x]
# ρ (Rho): 每個平面的位移
# (x, y) → (y, (2x + 3y) mod 5)
# 這裡簡化處理
for y in range(5):
for x in range(5):
offset = ((y + 1) * (y + 2) // 2) % 64
A[y][x] = rot(A[y][x], offset)
# π (Pi): 平面置換
# (x, y) → (y, x)
B = [[0]*5 for _ in range(5)]
for y in range(5):
for x in range(5):
B[y][x] = A[(x - y) % 5][x]
A = B
# χ (Chi): 非線性行混合
for y in range(5):
for x in range(5):
A[y][x] ^= (~B[(y + 1) % 5][x]) & B[(y + 2) % 5][x]
# ι (Iota): 與圓常數 XOR
A[0][0] ^= R
R = rot(R, 1)
# 轉換回 bytes
result = bytearray(200) # 5*5*64 bits = 200 bytes
for y in range(5):
for x in range(5):
start = 64 * (5 * y + x)
result[start:start+8] = A[y][x].to_bytes(8, 'little')
return bytes(result)
def rot(x, n):
"""循環左移"""
n = n % 64
return ((x << n) | (x >> (64 - n))) & 0xFFFFFFFFFFFFFFFF
# Keccak-256 完整實現
def keccak256(message):
"""
Keccak-256 實現
參數:c=512, r=1088, output=256
"""
# 填充
if isinstance(message, str):
message = message.encode('utf-8')
# 添加 '10*1' 填充
message = bytearray(message)
message.append(0x01)
while len(message) * 8 % 1088 != 1024 - 256: # 1024 - 256 = 768,padding 後需要是 768 的倍數
message.append(0x00)
message.append(0x80)
# 初始化狀態(1600 bits = 200 bytes)
state = bytearray(200)
# 吸收階段
for i in range(0, len(message), 136): # 136 bytes = 1088 bits
block = message[i:i+136]
for j in range(len(block)):
state[j] ^= block[j]
state = keccak_f(bytes(state))
# 擠出 32 bytes
return state[:32]
# 測試
test_input = ""
expected_output = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
result = keccak256(test_input)
print(f"keccak256('') = {result.hex()}")
print(f"expected = {expected_output}")
print(f"Match: {result.hex() == expected_output}")
五、實際應用案例
5.1 零知識證明中的密碼學原語
零知識證明系統大量使用橢圓曲線和哈希函數:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title ZKProofVerifier
* @dev 簡化的 zk-SNARK 驗證器概念展示
*
* 實際的 zk-SNARK 驗證涉及複雜的配對運算
* 這裡展示核心概念
*/
contract ZKProofVerifier {
// 橢圓曲線點(簡化表示)
struct G1Point {
uint256 x;
uint256 y;
}
// 驗證金鑰(簡化)
G1Point public verifyingKey;
// 配對檢查結果
bool public lastVerification;
constructor() {
// 初始化驗證金鑰(實際來自 Trusted Setup)
// 這是簡化的示例金鑰
verifyingKey = G1Point(
0x1234567890abcdef1234567890abcdef12345678,
0xabcdefabcdefabcdefabcdefabcdefabcdefabcd
);
}
/**
* @dev 驗證 zk-SNARK 證明
*
* 驗證方程:
* e(A, B) = e(C, D) + e(E, F)
*
* 其中:
* - A, C, E 是證明者的公共輸入
* - B, D, F 是驗證金鑰的一部分
* - e() 是橢圓曲線配對
*/
function verifyProof(
// 證明(壓縮形式)
uint256[2] memory pi_a,
uint256[2][2] memory pi_b,
uint256[2] memory pi_c,
// 公共輸入
uint256[] memory inputs
) public returns (bool) {
// 簡化的驗證邏輯
// 實際需要配對運算
// 1. 驗證輸入數量
require(inputs.length > 0, "No public inputs");
// 2. 驗證證明格式
require(pi_a[0] != 0 || pi_a[1] != 0, "Invalid pi_a");
require(pi_b[0][0] != 0 || pi_b[0][1] != 0, "Invalid pi_b");
require(pi_c[0] != 0 || pi_c[1] != 0, "Invalid pi_c");
// 3. 模擬驗證(實際需要配對)
// 這裡用哈希來模擬複雜的配對檢查
bytes32 verificationHash = keccak256(abi.encodePacked(
pi_a[0], pi_a[1],
pi_b[0][0], pi_b[0][1], pi_b[1][0], pi_b[1][1],
pi_c[0], pi_c[1],
inputs
));
// 4. 記錄結果
lastVerification = uint256(verificationHash) % 2 == 1;
return lastVerification;
}
}
5.2 Merkle 樹中的哈希應用
"""
Merkle 樹:使用 Keccak-256 的完整性驗證
"""
class MerkleTree:
"""
Merkle 樹實現
用途:
- 快速驗證大量數據的完整性
- 輕客戶端驗證
- 以太坊區塊頭中的交易根和狀態根
"""
def __init__(self, data_hashes):
"""
初始化 Merkle 樹
參數:
- data_hashes: 數據的哈希列表(已處理成固定長度)
"""
self.leaves = data_hashes
self.tree = self._build_tree()
def _hash_pair(self, left, right):
"""哈希一對節點"""
if isinstance(left, bytes):
left = left.hex()
if isinstance(right, bytes):
right = right.hex()
# 以太坊使用 keccak256(left || right)
import hashlib
return hashlib.new('keccak256', bytes.fromhex(left) + bytes.fromhex(right)).digest()
def _build_tree(self):
"""構建 Merkle 樹"""
current_level = self.leaves
tree = [current_level]
while len(current_level) > 1:
next_level = []
# 配對處理
for i in range(0, len(current_level), 2):
left = current_level[i]
right = current_level[i + 1] if i + 1 < len(current_level) else current_level[i]
# 如果是奇數,最後一個複製
next_level.append(self._hash_pair(left, right))
tree.append(next_level)
current_level = next_level
return tree
def root(self):
"""返回 Merkle 根"""
return self.tree[-1][0] if self.tree else None
def get_proof(self, index):
"""
生成 Merkle 證明
返回:
- 兄弟節點列表
- 每個兄弟節點的位置(left 或 right)
"""
proof = []
proof_positions = []
for level in range(len(self.tree) - 1):
current_level = self.tree[level]
# 確定兄弟節點索引
if index % 2 == 0:
sibling_index = index + 1
position = 'right'
else:
sibling_index = index - 1
position = 'left'
# 添加兄弟節點(如果存在)
if sibling_index < len(current_level):
proof.append(current_level[sibling_index])
proof_positions.append(position)
else:
proof.append(current_level[index])
proof_positions.append(position)
# 移動到父節點
index = index // 2
return proof, proof_positions
@staticmethod
def verify_proof(root, leaf, proof, proof_positions):
"""
驗證 Merkle 證明
參數:
- root: Merkle 根
- leaf: 葉節點哈希
- proof: 兄弟節點列表
- proof_positions: 每個兄弟節點的位置
"""
import hashlib
current = leaf if isinstance(leaf, bytes) else bytes.fromhex(leaf)
root_expected = root if isinstance(root, bytes) else bytes.fromhex(root)
for sibling, position in zip(proof, proof_positions):
sibling_bytes = sibling if isinstance(sibling, bytes) else bytes.fromhex(sibling)
if position == 'left':
current = hashlib.new('keccak256', sibling_bytes + current).digest()
else:
current = hashlib.new('keccak256', current + sibling_bytes).digest()
return current == root_expected
# 使用示例
leaves = [f"leaf_{i}".encode() for i in range(8)]
import hashlib
leaf_hashes = [hashlib.new('keccak256', leaf).digest() for leaf in leaves]
tree = MerkleTree(leaf_hashes)
print(f"Merkle Root: {tree.root().hex()}")
# 生成並驗證證明
proof, positions = tree.get_proof(3)
print(f"Proof for leaf 3: {[p.hex()[:16] + '...' for p in proof]}")
print(f"Positions: {positions}")
is_valid = MerkleTree.verify_proof(tree.root(), leaf_hashes[3], proof, positions)
print(f"Proof valid: {is_valid}")
六、總結與最佳實踐
6.1 密碼學使用原則
密碼學安全開發原則:
┌─────────────────────────────────────────────────────────────────────┐
│ 安全密碼學開發清單 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 不要自己實現密碼學 │
│ ✗ 自創加密算法 │
│ ✗ 修改現有算法的「優化」 │
│ ✓ 使用經過審計的庫(如 OpenZeppelin、libsodium) │
│ │
│ 2. 正確使用參數 │
│ ✗ 選擇非標準曲線參數 │
│ ✓ 使用標準曲線(如 secp256k1, Ed25519) │
│ ✓ 遵循建議的金鑰長度 │
│ │
│ 3. 隨機數安全 │
│ ✗ 使用時間戳或用戶輸入作為隨機種子 │
│ ✓ 使用密碼學安全的隨機數生成器(CSPRNG) │
│ ✓ ECDSA 籤名使用 RFC 6979 確定性 k │
│ │
│ 4. 哈希函數選擇 │
│ ✓ 以太坊:使用 Keccak-256 │
│ ✓ 其他場景:根據需求選擇 SHA-256、SHA-3、Blake2 │
│ ✗ 不要混淆 Keccak 和 SHA-3 │
│ │
│ 5. 密鑰管理 │
│ ✓ 私鑰永遠不離開安全環境 │
│ ✓ 使用 HSM 或 KMS 管理生產環境密鑰 │
│ ✓ 實施密鑰輪換策略 │
│ │
└─────────────────────────────────────────────────────────────────────┘
6.2 推薦工具與庫
Python 密碼學工具:
- ecdsa: 橢圓曲線簽名
- pysha3: Keccak-256
- pycryptodome: 通用密碼學
- web3.py: 以太坊交互
Solidity 密碼學工具:
- OpenZeppelin Contracts: 標準實現
- solady: 優化版本
- solmate: 高效實現
JavaScript/TypeScript:
- ethers.js: 以太坊開發首選
- web3.js: 完整功能
- @noble/secp256k1: 高性能 secp256k1
七、以太坊地址生成:橢圓曲線密碼學的實作應用
7.1 以太坊地址生成的完整流程
以太坊地址是以太坊網路中識別帳戶的唯一標識符。地址的生成過程完全依賴橢圓曲線密碼學,以下是完整的技術流程:
以太坊地址生成流程:
┌─────────────────────────────────────────────────────────────────────┐
│ 以太坊地址生成完整流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 生成隨機私鑰 │
│ ┌────────────────────────────────────────────────────────────────┐│
│ │ 輸入:無(使用密碼學安全的隨機數生成器) ││
│ │ 輸出:256 位隨機私鑰 (k) ││
│ │ ││
│ │ 要求: ││
│ │ - 必須使用密碼學安全的隨機數生成器 (CSPRNG) ││
│ │ - 私鑰必須均勻分佈在 [1, n-1] 範圍內 ││
│ │ - 任何人都不能預測或控制私鑰的值 ││
│ │ ││
│ │ 示例(Python): ││
│ │ import secrets ││
│ │ private_key = secrets.randbits(256) # 256 位隨機數 ││
│ └────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ Step 2: 派生公鑰(橢圓曲線標量乘法) │
│ ┌────────────────────────────────────────────────────────────────┐│
│ │ 輸入:私鑰 k, 基點 G ││
│ │ 輸出:公鑰 Q = k × G ││
│ │ ││
│ │ 計算過程: ││
│ │ 1. 將 k 轉換為二進制 ││
│ │ 2. 初始化 R = O(無窮遠點) ││
│ │ 3. 遍歷 k 的每一位: ││
│ │ - R = 2 × R(倍點) ││
│ │ - 若該位為 1:R = R + G(加點) ││
│ │ ││
│ │ 示例:k = 13 (1101₂) ││
│ │ R = O ││
│ │ 第1位(1): R = O + G = G ││
│ │ 第2位(1): R = 2G + G = 3G ││
│ │ 第3位(0): R = 2×3G = 6G ││
│ │ 第4位(1): R = 2×6G + G = 13G = Q ✓ ││
│ └────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ Step 3: Keccak-256 哈希 │
│ ┌────────────────────────────────────────────────────────────────┐│
│ │ 輸入:未壓縮公鑰 (0x04 || X || Y) ││
│ │ 輸出:256 位哈希值 ││
│ │ ││
│ │ 以太坊使用 Keccak-256(非 NIST SHA-3): ││
│ │ hash = Keccak256(uncompressed_public_key) ││
│ │ ││
│ │ 重要:不要混淆 Keccak-256 和 SHA-3-256 ││
│ │ 它們的填充方式不同,會產生不同的結果 ││
│ └────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ Step 4: 取後 160 位 │
│ ┌────────────────────────────────────────────────────────────────┐│
│ │ 輸入:Keccak256(公鑰) - 256 位 ││
│ │ 輸出:以太坊地址 - 160 位(20 bytes) ││
│ │ ││
│ │ address = hash[12:] # 取後 160 位 ││
│ │ address = "0x" + address.hex() # 添加前綴 ││
│ │ ││
│ │ 示例: ││
│ │ hash = 0x-d34db33f... ││
│ │ address = 0xd34db33f... (40 hex chars) ││
│ └────────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────────┘
7.2 完整實現:以太坊地址生成器
以下是使用 Python 完整實現以太坊地址生成的程式碼:
"""
以太坊地址生成器
完整實現從私鑰到以太坊地址的轉換過程
"""
import hashlib
import secrets
from typing import Tuple, Optional
# secp256k1 曲線參數(以太坊使用的橢圓曲線)
class Secp256k1:
"""
secp256k1 橢圓曲線參數
曲線方程:y² = x³ + 7 (mod p)
"""
# Field prime
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
# Curve order (number of points on the curve)
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
# Base point (generator)
G = (
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
)
# Coefficient a
a = 0
# Coefficient b
b = 7
@staticmethod
def is_on_curve(x: int, y: int) -> bool:
"""檢查點是否在曲線上"""
return (y * y - x * x * x - Secp256k1.b) % Secp256k1.p == 0
@staticmethod
def point_add(x1: int, y1: int, x2: int, y2: int) -> Tuple[int, int]:
"""橢圓曲線點加法"""
if x1 == 0 and y1 == 0:
return (x2, y2)
if x2 == 0 and y2 == 0:
return (x1, y1)
if x1 == x2:
if (y1 + y2) % Secp256k1.p == 0:
return (0, 0) # 無窮遠點
# 倍點公式
lam = (3 * x1 * x1) * pow(2 * y1, -1, Secp256k1.p) % Secp256k1.p
else:
# 加法公式
lam = (y2 - y1) * pow(x2 - x1, -1, Secp256k1.p) % Secp256k1.p
x3 = (lam * lam - x1 - x2) % Secp256k1.p
y3 = (lam * (x1 - x3) - y1) % Secp256k1.p
return (x3, y3)
@staticmethod
def scalar_multiply(k: int, x: int, y: int) -> Tuple[int, int]:
"""橢圓曲線標量乘法(使用二元法)"""
result_x, result_y = 0, 0
current_x, current_y = x, y
while k:
if k & 1:
result_x, result_y = Secp256k1.point_add(
result_x, result_y, current_x, current_y
)
current_x, current_y = Secp256k1.point_add(
current_x, current_y, current_x, current_y
)
k >>= 1
return (result_x, result_y)
class EthereumAddressGenerator:
"""
以太坊地址生成器
完整實現從私鑰生成以太坊地址的流程
"""
def __init__(self):
self.curve = Secp256k1()
def generate_private_key(self) -> int:
"""
生成密碼學安全的私鑰
重要:
- 必須使用密碼學安全的隨機數生成器
- 私鑰必須在 [1, n-1] 範圍內
"""
while True:
# 生成 256 位隨機數
private_key = secrets.randbits(256)
# 確保私鑰在有效範圍內
if 1 <= private_key < self.curve.n:
return private_key
def private_key_to_hex(self, private_key: int) -> str:
"""將私鑰轉換為十六進制字符串"""
return f"{private_key:064x}"
def derive_public_key(self, private_key: int, compressed: bool = False) -> str:
"""
從私鑰派生公鑰
參數:
- private_key: 私鑰(整數)
- compressed: 是否返回壓縮格式
返回:
- 公鑰(十六進制字符串)
"""
# 標量乘法:Q = k × G
public_key_x, public_key_y = self.curve.scalar_multiply(
private_key,
self.curve.G[0],
self.curve.G[1]
)
if compressed:
# 壓縮格式:0x02 + x (如果 y 是偶數) 或 0x03 + x (如果 y 是奇數)
prefix = "02" if public_key_y % 2 == 0 else "03"
return f"0x{prefix}{public_key_x:064x}"
else:
# 未壓縮格式:0x04 + x + y
return f"0x04{public_key_x:064x}{public_key_y:064x}"
def keccak256(self, data: bytes) -> bytes:
"""
Keccak-256 哈希函數
以太坊使用 Keccak-256,不是 NIST SHA-3
兩者的填充方式不同
"""
return hashlib.new('keccak256', data).digest()
def public_key_to_address(self, public_key: str) -> str:
"""
將公鑰轉換為以太坊地址
流程:
1. 移除公鑰前綴(0x04)
2. 計算 Keccak-256 哈希
3. 取後 20 bytes(160 位)
"""
# 移除 0x 前綴
pub_key_hex = public_key[2:] if public_key.startswith('0x') else public_key
# 移除未壓縮公鑰的前綴 0x04
pub_key_bytes = bytes.fromhex(pub_key_hex[2:] if pub_key_hex.startswith('04') else pub_key_hex)
# 計算 Keccak-256 哈希
hash_bytes = self.keccak256(pub_key_bytes)
# 取後 20 bytes 作為地址
address = hash_bytes[-20:]
return f"0x{address.hex()}"
def private_key_to_address(self, private_key: int) -> str:
"""
完整流程:私鑰 → 公鑰 → 地址
"""
public_key = self.derive_public_key(private_key)
address = self.public_key_to_address(public_key)
return address
def generate_address(self) -> dict:
"""
生成完整的以太坊錢包資訊
"""
private_key = self.generate_private_key()
public_key = self.derive_public_key(private_key)
address = self.private_key_to_address(private_key)
return {
"private_key": self.private_key_to_hex(private_key),
"public_key": public_key,
"address": address
}
def demo():
"""演示以太坊地址生成過程"""
generator = EthereumAddressGenerator()
# 演示 1:使用隨機私鑰生成地址
print("=" * 60)
print("以太坊地址生成演示")
print("=" * 60)
wallet = generator.generate_address()
print(f"\n生成的錢包資訊:")
print(f" 私鑰:{wallet['private_key'][:32]}...")
print(f" 公鑰:{wallet['public_key'][:66]}...")
print(f" 地址:{wallet['address']}")
# 演示 2:使用已知私鑰生成地址(只用於測試)
print("\n" + "-" * 60)
print("已知私鑰測試")
print("-" * 60)
# 測試私鑰:1(最小有效私鑰)
test_private_key = 1
test_address = generator.private_key_to_address(test_private_key)
print(f"\n私鑰 1 的以太坊地址:{test_address}")
# 驗證:這個地址應該是 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf
expected = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf"
print(f"預期地址:{expected}")
print(f"驗證結果:{'✓ 正確' if test_address.lower() == expected.lower() else '✗ 錯誤'}")
# 演示 3:Keccak-256 vs SHA-3 差異
print("\n" + "-" * 60)
print("Keccak-256 vs SHA-3-256 差異演示")
print("-" * 60)
test_data = b"Hello, Ethereum!"
# Keccak-256
keccak_hash = hashlib.new('keccak256', test_data).digest()
# SHA-3-256(注意:hashlib 的 keccak 就是 Keccak-256)
# 如果需要 NIST SHA-3,需要使用 pysha3 或 hashlib.sha3_256
sha3_hash = hashlib.sha3_256(test_data).digest()
print(f"\n輸入:{test_data}")
print(f"Keccak-256:{keccak_hash.hex()}")
print(f"SHA-3-256:{sha3_hash.hex()}")
print(f"相同:{'否(兩者不同)' if keccak_hash != sha3_hash else '是'}")
if __name__ == "__main__":
demo()
7.3 ENS 名稱與地址的密碼學關聯
以太坊名稱服務(ENS)提供了人類可讀的名稱到以太坊地址的映射。這種映射關係同樣依賴密碼學來確保安全性和可驗證性。
"""
ENS 名稱解析的密碼學基礎
"""
class ENSResolver:
"""
ENS 解析器
展示 ENS 名稱如何與以太坊地址關聯
"""
def __init__(self, web3_instance):
self.w3 = web3_instance
# ENS 合約位址(主網)
self.ens_registry = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
def namehash(self, name: str) -> bytes:
"""
計算 ENS 名稱的 namehash
namehash 是 ENS 用於在區塊鏈上存儲名稱的標準方式
它確保:
1. 相同名稱總是產生相同的 hash
2. 子域名與父域名有可預測的 hash 關係
算法:
namehash(''name') = 0
namehash('name' + '.' + parent) = sha3(namehash(parent) + sha3('name'))
"""
# 確保是小寫(DNS 標準)
name = name.lower()
# 分割域名
labels = name.split('.')
# 從根 hash 開始
current_hash = bytes(32) # 32 個零字節
# 從最右邊的標籤開始處理
for label in reversed(labels):
if not label:
continue
# 計算標籤的 Keccak-256
import hashlib
label_hash = hashlib.new('keccak256', label.encode('ascii')).digest()
# 連接並哈希:sha3(current_hash + label_hash)
current_hash = hashlib.new('keccak256', current_hash + label_hash).digest()
return current_hash
def get_address(self, ens_name: str) -> Optional[str]:
"""
解析 ENS 名稱到以太坊地址
流程:
1. 計算 namehash
2. 查詢 ENS registry 獲取 resolver 地址
3. 調用 resolver 的 addr() 方法
"""
# 計算 namehash
node = self.namehash(ens_name)
# 這裡應該調用 ENS 合約
# resolver = ens_registry.resolver(node)
# address = resolver.addr(node)
return None # 簡化版本
@staticmethod
def demo_namehash():
"""演示 namehash 計算"""
import hashlib
test_names = [
"alice.eth",
"bob.alice.eth",
"wallet.vitalik.eth"
]
print("ENS Namehash 演示:")
print("-" * 60)
for name in test_names:
# 手動計算
labels = name.lower().split('.')
current_hash = bytes(32)
for label in reversed(labels):
if not label:
continue
label_hash = hashlib.new('keccak256', label.encode('ascii')).digest()
current_hash = hashlib.new('keccak256', current_hash + label_hash).digest()
print(f"\n{name}:")
print(f" Labels: {labels}")
print(f" Namehash: 0x{current_hash.hex()}")
7.4 HD 錢包與層級式派生
現代以太坊錢包使用 BIP-39 和 BIP-32 標準實現層級式確定性(HD)錢包。這允許從單一種子產生無限數量的地址。
HD 錢包架構:
┌─────────────────────────────────────────────────────────────────────┐
│ HD 錢包層級結構 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 種子 (Seed) │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ │ │ │
│ 主私鑰 主公鑰 │
│ (Master Private Key) (Master Public Key) │
│ │ │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ 硬化派生 / 正常派生 │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ 硬化子私鑰 硬化子私鑰 正常子私鑰 │
│ │ │ │ │
│ └──────────────┴──────────────┘ │
│ │ │
│ 更多層級派生... │
│ │ │
│ 以太坊地址 0, 1, 2... │
│ │
└─────────────────────────────────────────────────────────────────────┘
BIP-39 助記詞 → BIP-32 種子 → HD 樹
Path 示例:
m/44'/60'/0'/0/0 - 第一個以太坊帳戶的第一個地址
m/44'/60'/0'/0/1 - 第一個以太坊帳戶的第二個地址
m/44'/60'/1'/0/0 - 第二個以太坊帳戶(Coinbase 派生)
"""
HD 錢包實現(簡化版本)
"""
import hashlib
import hmac
from typing import Tuple
class HDWallet:
"""
層級式確定性錢包
實現 BIP-32 標準
"""
# BIP-32 常數
MASTER_SECRET = b"Bitcoin seed"
HARDENED_OFFSET = 0x80000000
def __init__(self, seed: bytes):
self.seed = seed
self.master_key = self.derive_master_key(seed)
@staticmethod
def from_mnemonic(mnemonic: str, passphrase: str = "") -> 'HDWallet':
"""
從助記詞創建 HD 錢包
流程:
1. 助記詞 + 可選密碼 → PBKDF2 → 種子
2. 種子 → 主私鑰
"""
# 這裡應該實現完整的 BIP-39
# 簡化版本
salt = f"mnemonic{passphrase}".encode()
import hashlib
seed = hashlib.pbkdf2_hmac(
'sha512',
mnemonic.encode(),
salt,
2048
)
return HDWallet(seed)
def derive_master_key(self, seed: bytes) -> dict:
"""派生主私鑰"""
# HMAC-SHA512(seed, "Bitcoin seed")
hmac_result = hmac.new(
self.MASTER_SECRET,
seed,
hashlib.sha512
).digest()
# 分為左半(私鑰)和右半(鏈碼)
private_key = int.from_bytes(hmac_result[:32], 'big')
chain_code = hmac_result[32:64]
# 確保私鑰有效
if private_key >= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141:
raise ValueError("Invalid private key")
return {
"private_key": private_key,
"chain_code": chain_code,
"public_key": self.private_key_to_public_key(private_key)
}
def private_key_to_public_key(self, private_key: int) -> Tuple[int, int]:
"""將私鑰轉換為公鑰"""
# 使用 secp256k1 標量乘法
# 這裡應該調用實際的 ECDSA 實現
G = (
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
)
# 簡化的標量乘法(實際應使用更高效的算法)
# 這裡只是示意
return (0, 0) # 佔位
def CKDpriv(self, parent_key: dict, index: int) -> dict:
"""
派生子私鑰(BIP-32 CKDpriv)
參數:
- parent_key: 父私鑰
- index: 子索引(可選 + HARDENED_OFFSET 表示硬化派生)
派生邏輯:
- 硬化派生:HMAC-SHA512(chain_code, 0x00 || parent_key || index)
- 正常派生:HMAC-SHA512(chain_code, parent_public_key || index)
"""
is_hardened = index >= self.HARDENED_OFFSET
if is_hardened:
# 硬化派生
data = b'\x00' + parent_key["private_key"].to_bytes(32, 'big')
else:
# 正常派生
# 需要父公鑰
parent_pub = self.private_key_to_public_key(parent_key["private_key"])
data = parent_pub[0].to_bytes(32, 'big') + parent_pub[1].to_bytes(32, 'big')
# 添加索引
data += index.to_bytes(4, 'big')
# HMAC-SHA512
hmac_result = hmac.new(
parent_key["chain_code"],
data,
hashlib.sha512
).digest()
# 派生子私鑰
il = int.from_bytes(hmac_result[:32], 'big')
ir = hmac_result[32:64]
child_key = (parent_key["private_key"] + il) % 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
if child_key == 0:
raise ValueError("Invalid child key")
return {
"private_key": child_key,
"chain_code": ir,
"public_key": self.private_key_to_public_key(child_key)
}
def derive_path(self, path: str) -> dict:
"""
根據路徑派生密鑰
路徑格式:m/44'/60'/0'/0/0
- m: 主密鑰
- 44': BIP-44 硬幣類型(ETH)
- 60': 以太坊
- 0': 帳戶層級(硬化)
- 0: 外部/內部鏈
- 0: 地址索引
"""
components = path.split('/')
if components[0] != 'm':
raise ValueError("Path must start with 'm'")
current_key = self.master_key
for component in components[1:]:
is_hardened = component.endswith("'")
index = int(component.rstrip("'"))
if is_hardened:
index += self.HARDENED_OFFSET
current_key = self.CKDpriv(current_key, index)
return current_key
def get_ethereum_address(self, private_key: int) -> str:
"""
從私鑰獲取以太坊地址
"""
import hashlib
# 獲取公鑰
pub_x, pub_y = self.private_key_to_public_key(private_key)
# 未壓縮公鑰
pub_key = pub_x.to_bytes(32, 'big') + pub_y.to_bytes(32, 'big')
# Keccak-256
address_hash = hashlib.new('keccak256', pub_key).digest()
# 取後 20 bytes
return "0x" + address_hash[-20:].hex()
def demo_hd_wallet():
"""演示 HD 錢包"""
print("=" * 60)
print("HD 錢包演示")
print("=" * 60)
# 從種子創建錢包(實際應使用 BIP-39 助記詞)
seed = b"0000111122223333444455556666777788889999"
wallet = HDWallet(seed)
print(f"\n主私鑰:{wallet.master_key['private_key']:064x}")
print(f"主公鑰:({wallet.master_key['public_key'][0]:064x}, ...)")
# 派生第一個以太坊地址
eth_path = "m/44'/60'/0'/0/0"
key = wallet.derive_path(eth_path)
print(f"\n派生子路徑:{eth_path}")
print(f"子私鑰:{key['private_key']:064x}")
# 獲取以太坊地址
address = wallet.get_ethereum_address(key["private_key"])
print(f"以太坊地址:{address}")
if __name__ == "__main__":
demo_hd_wallet()
參考資源
密碼學標準
- SEC 2: Recommended Elliptic Curve Domain Parameters
- ANSI X9.62: Public Key Cryptography for the Financial Services Industry
- FIPS 180-4: Secure Hash Standard (SHA-2)
- FIPS 202: SHA-3 Standard
以太坊相關
1.以太坊黃皮書
- EIP-155: Simple Replay Attack Protection
- EIP-198: Big Integer Modular Exponentiation
- EIP-2098: Compact Signature Representation
學習資源
- "Understanding Cryptography" - Christof Paar
- "Programming Bitcoin" - Jimmy Song
- Keccak Team Website: keccak.team
本文為密碼學原語的技術指南,旨在幫助讀者理解底層原理。實際部署密碼學系統時,強烈建議使用經過充分測試和審計的現成庫,而非自行實現。
相關文章
- 以太坊錢包安全實務進階指南:合約錢包與 EOA 安全差異、跨鏈橋接風險評估 — 本文深入探討以太坊錢包的安全性實務,特別聚焦於合約錢包與外部擁有帳戶(EOA)的安全差異分析,以及跨鏈橋接的風險評估方法。我們將從密碼學基礎出發,詳細比較兩種帳戶類型的安全模型,並提供完整的程式碼範例展示如何實現安全的多重簽名錢包。同時,本文系統性地分析跨鏈橋接面臨的各類風險,提供風險評估框架和最佳實踐建議,幫助讀者建立全面的錢包安全知識體系。
- 以太坊錢包安全模型深度比較:EOA、智慧合約錢包與 MPC 錢包的技術架構、風險分析與選擇框架 — 本文深入分析以太坊錢包技術的三大類型:外部擁有帳戶(EOA)、智慧合約錢包(Smart Contract Wallet)與多方計算錢包(MPC Wallet)。我們從技術原理、安全模型、風險維度等面向進行全面比較,涵蓋 ERC-4337 帳戶抽象標準、Shamir 秘密分享方案、閾值簽名等核心技術,並提供針對不同資產規模和使用場景的選擇框架。截至 2026 年第一季度,以太坊生態系統的錢包技術持續演進,理解這些技術差異對於保護數位資產至關重要。
- MPC 錢包完整技術指南:多方計算錢包架構、安全模型與實作深度分析 — 多方計算(Multi-Party Computation)錢包代表了區塊鏈資產安全管理的前沿技術方向。本文深入剖析 MPC 錢包的密碼學原理、主流實現方案、安全架構,涵蓋 Shamir 秘密分享、BLS 閾值簽名、分散式金鑰生成等核心技術,並提供完整的部署指南與最佳實踐建議。
- 社交恢復錢包技術實作完整指南:智慧合約錢包架構、守護者機制與安全設計深度分析 — 社交恢復錢包解決了傳統加密貨幣錢包的核心痛點:私鑰遺失導致資產永久無法訪問的問題。本文深入分析社交恢復錢包的技術架構,包括智慧合約實現、守護者機制設計、恢復流程、安全考量等各個層面,提供完整的程式碼範例和安全分析。
- 橢圓曲線離散對數問題複雜度分析:從數學基礎到密碼學安全 — 橢圓曲線離散對數問題(ECDLP)是現代密碼學最重要的數學假設之一,也是橢圓曲線密碼學安全性的基石。本文深入分析ECDLP的數學定義、Pollard's Rho等已知攻擊算法的複雜度、以及為什麼ECDLP在當前計算能力下是安全的。我們從群論基礎出發,逐步推導各種攻擊算法的複雜度,建立對橢圓曲線密碼學安全性的直觀理解。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案完整列表
- Solidity 文檔 智慧合約程式語言官方規格
- EVM 代碼庫 EVM 實作的核心參考
- Alethio EVM 分析 EVM 行為的正規驗證
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!