以太坊密碼學原語直覺解釋:橢圓曲線、布隆過濾器與 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()

參考資源

密碼學標準

  1. SEC 2: Recommended Elliptic Curve Domain Parameters
  2. ANSI X9.62: Public Key Cryptography for the Financial Services Industry
  3. FIPS 180-4: Secure Hash Standard (SHA-2)
  4. FIPS 202: SHA-3 Standard

以太坊相關

1.以太坊黃皮書

  1. EIP-155: Simple Replay Attack Protection
  2. EIP-198: Big Integer Modular Exponentiation
  3. EIP-2098: Compact Signature Representation

學習資源

  1. "Understanding Cryptography" - Christof Paar
  2. "Programming Bitcoin" - Jimmy Song
  3. Keccak Team Website: keccak.team

本文為密碼學原語的技術指南,旨在幫助讀者理解底層原理。實際部署密碼學系統時,強烈建議使用經過充分測試和審計的現成庫,而非自行實現。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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