secp256k1 橢圓曲線密碼學實作:以太坊密碼學原語工程指南

本指南從工程師視角出發,提供 secp256k1 橢圓曲線的完整實作,包括有限域運算、點加法與倍點運算、標量乘法、以及 ECDSA 簽名生成與驗證的詳細程式碼。理解這些底層密碼學原語不僅有助於深入理解區塊鏈安全機制,更能為開發高安全性應用奠定基礎。

secp256k1 橢圓曲線密碼學實作:以太坊密碼學原語工程指南

概述

secp256k1 是比特幣和以太坊使用的橢圓曲線密碼學標準,其數學性質是區塊鏈安全的基石。本指南從工程師視角出發,提供 secp256k1 橢圓曲線的完整實作,包括有限域運算、點加法與倍點運算、標量乘法、以及簽名生成與驗證的詳細程式碼。理解這些底層密碼學原語不僅有助於深入理解區塊鏈安全機制,更能為開發高安全性應用奠定基礎。

第一章:數學基礎與 secp256k1 參數

1.1 橢圓曲線數學

橢圓曲線密碼學(Elliptic Curve Cryptography, ECC)是一種基於橢圓曲線代數結構的公鑰密碼學。與 RSA 相比,ECC 在相同安全強度下使用更短的密鑰,大幅減少計算與儲存成本。

橢圓曲線方程式

Weierstrass 形式:
y² = x³ + ax + b (mod p)

secp256k1 參數:
a = 0
b = 7
p = 2²⁵⁶ - 2³² - 2⁹ - 2⁶ - 2⁴ - 2⁸ - 2³⁸ - 2¹⁶ - 2⁴ - 2⁶ - 2⁹⁹ - 2¹⁹²
  = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

G(生成元)= (Gx, Gy)
Gx = 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
Gy = 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8

n(階)= FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0

h(餘因子)= 1

1.2 secp256k1 參數詳解

# secp256k1 曲線參數

class Secp256k1Parameters:
    """
    secp256k1 曲線參數定義
    
    所有參數均為質數,可在 secg.org 的 SEC 2 標準中找到
    """
    
    # 曲線方程 y² = x³ + ax + b (mod p) 的係數
    # secp256k1 使用 a = 0,這是比其他曲線(如 secp256r1 的 a = -3)更簡單的設計
    A = 0
    B = 7
    
    # 質數 p(有限域的階)
    # 這個特殊的質數選擇使得模運算可以被高效實現
    P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
    
    # 生成元 G 的座標
    GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
    GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
    
    # 生成元的階(曲線上點的數量)
    # n 是滿足 n * G = O(無窮遠點)的最小正整數
    N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
    
    # 餘因子(用於優化計算)
    # h = #E(Fp) / n,其中 #E(Fp) 是曲線上的總點數
    H = 1
    
    # 安全參數:用於確保標量乘法不會泄露私鑰信息
    # 低位不為零,防止某些攻擊
    SECURITY_CHECK_LOW_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
    
    @classmethod
    def validate_point(cls, x, y):
        """
        驗證點 (x, y) 是否在曲線上
        
        驗證方程:y² ≡ x³ + 7 (mod p)
        """
        # 檢查座標範圍
        if x <= 0 or x >= cls.P or y <= 0 or y >= cls.P:
            return False
            
        # 驗證曲線方程
        left = (y * y) % cls.P
        right = (pow(x, 3, cls.P) + cls.B) % cls.P
        
        return left == right

第二章:有限域運算實現

2.1 模運算基礎

有限域(Finite Field)是密碼學的數學基礎。在 secp256k1 中,所有運算都在模質數 p 的有限域 Fp 上進行。

# 有限域運算模組

class FieldElement:
    """
    有限域 Fp 元素
    
    封裝模 p 的算術運算
    p = 2^256 - 2^32 - 2^9 - 2^6 - 2^4 - 2^3 - 2^8 - 2^38 - 2^16 - 2^4 - 2^6 - 2^99 - 2^192
    """
    
    def __init__(self, value: int, p: int = Secp256k1Parameters.P):
        """
        初始化有限域元素
        
        參數:
        - value: 元素值(自動取模)
        - p: 質數(預設為 secp256k1 的 p)
        """
        if value < 0:
            # 負數取模確保結果為正
            self.value = value % p
        else:
            self.value = value % p
        self.p = p
        
    def __repr__(self):
        return f"FieldElement({hex(self.value)})"
        
    def __eq__(self, other):
        if not isinstance(other, FieldElement):
            return False
        return self.value == other.value and self.p == other.p
        
    def __add__(self, other):
        """加法:(a + b) mod p"""
        if self.p != other.p:
            raise ValueError("Cannot add elements from different fields")
        result = self.value + other.value
        if result >= self.p:
            result -= self.p
        return FieldElement(result, self.p)
        
    def __sub__(self, other):
        """減法:(a - b) mod p"""
        if self.p != other.p:
            raise ValueError("Cannot subtract elements from different fields")
        result = self.value - other.value
        if result < 0:
            result += self.p
        return FieldElement(result, self.p)
        
    def __mul__(self, other):
        """乘法:(a * b) mod p"""
        if isinstance(other, FieldElement):
            if self.p != other.p:
                raise ValueError("Cannot multiply elements from different fields")
            result = (self.value * other.value) % self.p
        else:
            # 純量乘法
            result = (self.value * other) % self.p
        return FieldElement(result, self.p)
        
    def __pow__(self, exponent: int):
        """冪運算:a^e mod p"""
        return FieldElement(pow(self.value, exponent, self.p), self.p)
        
    def __neg__(self):
        """加法逆元:-a mod p"""
        if self.value == 0:
            return FieldElement(0, self.p)
        return FieldElement(self.p - self.value, self.p)
        
    def inverse(self):
        """
        乘法逆元:a^(-1) mod p
        
        使用擴展歐幾里得演算法或費馬小定理
        費馬小定理:a^(-1) ≡ a^(p-2) mod p(當 p 為質數時)
        
        這是 secp256k1 中最常用的方法,因為 p 是質數
        """
        # 使用費馬小定理計算乘法逆元
        # pow(base, exponent, mod) 使用快速冪演算法,複雜度 O(log p)
        return FieldElement(pow(self.value, self.p - 2, self.p), self.p)
        
    def __truediv__(self, other):
        """除法:(a * b^(-1)) mod p"""
        if isinstance(other, FieldElement):
            if self.p != other.p:
                raise ValueError("Cannot divide elements from different fields")
            return self * other.inverse()
        else:
            return self * FieldElement(other, self.p).inverse()


class FieldMath:
    """
    高效的有限域運算工具類
    
    提供針對 secp256k1 優化的運算函數
    """
    
    # secp256k1 的質數 p(用於位元運算優化)
    P = Secp256k1Parameters.P
    
    @staticmethod
    def mod_add(a: int, b: int) -> int:
        """
        模加法
        
        優化版本:避免使用 % 運算子
        """
        result = a + b
        if result >= FieldMath.P:
            result -= FieldMath.P
        return result
        
    @staticmethod
    def mod_sub(a: int, b: int) -> int:
        """
        模減法
        
        避免分支的版本:
        result = a - b
        if result < 0: result += p
        """
        result = a - b
        if result < 0:
            result += FieldMath.P
        return result
        
    @staticmethod
    def mod_mul(a: int, b: int) -> int:
        """
        模乘法
        
        使用 Python 內建的 pow 函數
        複雜度:O(log n)
        """
        return (a * b) % FieldMath.P
        
    @staticmethod
    def mod_inv(a: int) -> int:
        """
        模乘法逆元
        
        使用擴展歐幾里得演算法
        複雜度:O(log p)
        """
        if a == 0:
            return 0
            
        # 擴展歐幾里得演算法
        # 計算 a^(-1) mod p 使得 a * a^(-1) ≡ 1 (mod p)
        t, new_t = 0, 1
        r, new_r = FieldMath.P, a
        
        while new_r != 0:
            quotient = r // new_r
            t, new_t = new_t, t - quotient * new_t
            r, new_r = new_r, r - quotient * new_r
            
        if r > 1:
            # a 在模 p 下不可逆
            raise ValueError(f"{a} has no modular inverse mod {FieldMath.P}")
            
        if t < 0:
            t = t + FieldMath.P
            
        return t
        
    @staticmethod
    def mod_sqrt(a: int) -> int:
        """
        模平方根
        
        使用 Tonelli-Shanks 演算法
        對於 secp256k1,由於 p ≡ 3 (mod 4),
        可以使用更簡單的方法:
        sqrt(a) = a^((p+1)/4) mod p
        """
        # 檢查 p ≡ 3 (mod 4)
        # secp256k1: p = 2^256 - 2^32 - 2^9 - ... ≡ 3 (mod 4)
        
        # 計算 a^((p+1)/4) mod p
        exponent = (FieldMath.P + 1) // 4
        result = pow(a, exponent, FieldMath.P)
        
        # 驗證結果
        if (result * result) % FieldMath.P != a:
            raise ValueError(f"{a} has no square root mod {FieldMath.P}")
            
        return result

2.2 高效模運算(secp256k1 優化)

secp256k1 的質數 p 選擇使得模運算可以被高度優化:

# secp256k1 優化運算

class Secp256k1FieldOptimized:
    """
    secp256k1 優化有限域運算
    
    優化技巧:
    1. p = 2^256 - 2^32 - 2^9 - 2^6 - 2^4 - 2^3 - 2^8 - 2^38 - 2^16 - 2^4 - 2^6 - 2^99 - 2^192
       這個特殊形式允許使用進位傳播減法
    
    2. 對於 32 位元系統的優化
    """
    
    # p 的 32 位元分塊表示
    # 這使得定點運算更高效
    P0 = 0xFFFFFC2F
    P1 = 0xFFFFFFFF
    P2 = 0xFFFFFFFF
    P3 = 0xFFFFFFFF
    P4 = 0xFFFFFFFF
    P5 = 0xFFFFFFFF
    P6 = 0xFFFFFFFF
    P7 = 0xFFFFFFFF
    
    @staticmethod
    def add_optimized(a: int, b: int) -> int:
        """
        優化加法
        
        使用 64 位元累加器避免分支
        """
        # 分塊加法
        a0 = a & 0xFFFFFFFF
        a1 = (a >> 32) & 0xFFFFFFFF
        a2 = (a >> 64) & 0xFFFFFFFF
        a3 = (a >> 96) & 0xFFFFFFFF
        a4 = (a >> 128) & 0xFFFFFFFF
        a5 = (a >> 160) & 0xFFFFFFFF
        a6 = (a >> 192) & 0xFFFFFFFF
        a7 = (a >> 224) & 0xFFFFFFFF
        
        b0 = b & 0xFFFFFFFF
        b1 = (b >> 32) & 0xFFFFFFFF
        b2 = (b >> 64) & 0xFFFFFFFF
        b3 = (b >> 96) & 0xFFFFFFFF
        b4 = (b >> 128) & 0xFFFFFFFF
        b5 = (b >> 160) & 0xFFFFFFFF
        b6 = (b >> 192) & 0xFFFFFFFF
        b7 = (b >> 224) & 0xFFFFFFFF
        
        # 加法(可能溢位)
        carry = 0
        r0 = a0 + b0
        carry = r0 >> 32
        r0 &= 0xFFFFFFFF
        
        r1 = a1 + b1 + carry
        carry = r1 >> 32
        r1 &= 0xFFFFFFFF
        
        r2 = a2 + b2 + carry
        carry = r2 >> 32
        r2 &= 0xFFFFFFFF
        
        r3 = a3 + b3 + carry
        carry = r3 >> 32
        r3 &= 0xFFFFFFFF
        
        r4 = a4 + b4 + carry
        carry = r4 >> 32
        r4 &= 0xFFFFFFFF
        
        r5 = a5 + b5 + carry
        carry = r5 >> 32
        r5 &= 0xFFFFFFFF
        
        r6 = a6 + b6 + carry
        carry = r6 >> 32
        r6 &= 0xFFFFFFFF
        
        r7 = a7 + b7 + carry
        
        result = r0 | (r1 << 32) | (r2 << 64) | (r3 << 96) | \
                 (r4 << 128) | (r5 << 160) | (r6 << 192) | (r7 << 224)
        
        # 模 p 修正
        if r7 >= 0x10000000:  # 需要減去 p
            # 進位傳播減法
            result = result - Secp256k1FieldOptimized.P
            
        return result
        
    @staticmethod
    def mul_optimized(a: int, b: int) -> int:
        """
        優化乘法
        
        使用 Python 的 arbitrary precision integer
        對於實際部署,應使用 assembly 或硬體加速
        """
        # 標準乘法後取模
        return (a * b) % Secp256k1Parameters.P

第三章:橢圓曲線點運算

3.1 點的表示與無窮遠點

在橢圓曲線密碼學中,點需要特殊表示:

# 橢圓曲線點

class Point:
    """
    橢圓曲線點
    
    表示方式:
    - (x, y):普通點
    - None:無窮遠點 O(單位元素)
    """
    
    def __init__(self, x: int, y: int, curve=None):
        """
        初始化曲線點
        
        參數:
        - x, y:點的座標(若為無窮遠點則 x=y=None)
        - curve:曲線參數(用於驗證點是否在曲線上)
        """
        self.x = x
        self.y = y
        self.curve = curve
        
        # 驗證點是否在曲線上(僅在非無窮遠點時)
        if curve is not None and x is not None and y is not None:
            if not curve.is_on_curve(x, y):
                raise ValueError(f"Point ({x}, {y}) is not on the curve")
                
    def __repr__(self):
        if self.x is None or self.y is None:
            return "Point(Infinity)"
        return f"Point({hex(self.x)}, {hex(self.y)})"
        
    def __eq__(self, other):
        """檢查兩點是否相等"""
        return self.x == other.x and self.y == other.y
        
    def __neg__(self):
        """
        點的加法逆元
        
        在 secp256k1 上,P 的加法逆元是 (x, -y mod p)
        """
        if self.x is None:
            return self  # O + O = O
            
        return Point(self.x, (-self.y) % self.curve.p, self.curve)
        
    def is_infinity(self) -> bool:
        """檢查是否為無窮遠點"""
        return self.x is None or self.y is None


class Secp256k1Curve:
    """
    secp256k1 曲線定義
    """
    
    A = 0
    B = 7
    P = Secp256k1Parameters.P
    N = Secp256k1Parameters.N
    G = Point(Secp256k1Parameters.GX, Secp256k1Parameters.GY)
    
    def is_on_curve(self, x: int, y: int) -> bool:
        """
        驗證點是否在曲線上
        
        方程:y² ≡ x³ + 7 (mod p)
        """
        return (y * y) % self.P == (pow(x, 3, self.P) + self.B) % self.P
        
    def point_at_infinity(self):
        """返回無窮遠點"""
        return Point(None, None, self)

3.2 點加法與倍點運算

點加法和倍點是 ECC 的核心運算:

# 點加法與倍點運算

class PointOperations:
    """
    橢圓曲線點運算
    
    核心公式推導:
    
    點加法 P + Q:
    - 若 P = O,則 P + Q = Q
    - 若 Q = O,則 P + Q = P
    - 若 P ≠ Q:
      λ = (y₂ - y₁) / (x₂ - x₁)
      x₃ = λ² - x₁ - x₂
      y₃ = λ(x₁ - x₃) - y₁
    
    倍點 2P(P + P):
    - λ = (3x₁² + a) / (2y₁)
    - x₃ = λ² - 2x₁
    - y₃ = λ(x₁ - x₃) - y₁
    
    其中 a = 0(secp256k1)
    """
    
    def __init__(self, curve: Secp256k1Curve):
        self.curve = curve
        
    def add(self, p1: Point, p2: Point) -> Point:
        """
        點加法:P + Q
        
        特殊情況處理:
        1. P = O:返回 Q
        2. Q = O:返回 P
        3. Q = -P:返回 O
        """
        # 處理無窮遠點
        if p1.is_infinity():
            return p2
        if p2.is_infinity():
            return p1
            
        x1, y1 = p1.x, p1.y
        x2, y2 = p2.x, p2.y
        
        # 檢查是否為同一点(使用加法逆元)
        if x1 == x2:
            if y1 == y2:
                # 同點,使用倍點公式
                return self.double(p1)
            else:
                # Q = -P,返回無窮遠點
                return self.curve.point_at_infinity()
                
        # 一般加法公式
        # 計算斜率 λ
        # λ = (y₂ - y₁) / (x₂ - x₁) mod p
        
        # 分子
        numerator = (y2 - y1) % self.curve.P
        
        # 分母
        denominator = (x2 - x1) % self.curve.P
        
        # 計算逆元
        denom_inv = pow(denominator, self.curve.P - 2, self.curve.P)
        
        # λ = numerator * denominator^(-1) mod p
        lam = (numerator * denom_inv) % self.curve.P
        
        # 計算新點座標
        # x₃ = λ² - x₁ - x₂ mod p
        x3 = (lam * lam - x1 - x2) % self.curve.P
        
        # y₃ = λ(x₁ - x₃) - y₁ mod p
        y3 = (lam * (x1 - x3) - y1) % self.curve.P
        
        return Point(x3, y3, self.curve)
        
    def double(self, p: Point) -> Point:
        """
        倍點運算:2P
        
        公式(當 a = 0):
        λ = (3x₁²) / (2y₁) mod p
        x₃ = λ² - 2x₁ mod p
        y₃ = λ(x₁ - x₃) - y₁ mod p
        """
        if p.is_infinity():
            return p
            
        x1, y1 = p.x, p.y
        
        # 檢查 y = 0(此時 2P = O)
        if y1 == 0:
            return self.curve.point_at_infinity()
            
        # 計算 λ = (3x₁²) / (2y₁) mod p
        numerator = (3 * x1 * x1) % self.curve.P
        
        denominator = (2 * y1) % self.curve.P
        
        denom_inv = pow(denominator, self.curve.P - 2, self.curve.P)
        
        lam = (numerator * denom_inv) % self.curve.P
        
        # 計算新點座標
        x3 = (lam * lam - 2 * x1) % self.curve.P
        y3 = (lam * (x1 - x3) - y1) % self.curve.P
        
        return Point(x3, y3, self.curve)
        
    def subtract(self, p1: Point, p2: Point) -> Point:
        """
        點減法:P - Q
        
        等價於 P + (-Q)
        """
        return self.add(p1, -p2)

3.3 標量乘法(Scalar Multiplication)

標量乘法是 ECC 的核心運算,用於計算 kP(P 加到自己 k 次):

# 標量乘法

class ScalarMultiplication:
    """
    標量乘法:kP
    
    這是 ECC 中最重要的運算,用於:
    - 私鑰到公鑰的轉換:Q = d * G
    - 公鑰運算:多個公鑰的線性組合
    
    演算法:
    1. 樸素加法(O(n)):每次加 P 太慢
    2. 二進制法/加倍-相加法(O(log n)):利用 k 的二進制表示
    3. 視窗 NAF 法(Windowed NAF):減少加法次數
    4. Shamir's Trick:同時計算多個標量乘法
    """
    
    def __init__(self, curve: Secp256k1Curve):
        self.curve = curve
        self.point_ops = PointOperations(curve)
        
    def multiply_binary(self, k: int, p: Point) -> Point:
        """
        二進制標量乘法(加倍-相加法)
        
        演算法:
        1. 將 k 轉換為二進制
        2. 從左到右遍歷每一位
        3. 每位先倍點,再(有條件地)加 P
        
        複雜度:O(log k) 點加法
        """
        if k == 0:
            return self.curve.point_at_infinity()
            
        if k < 0:
            # 處理負數標量:(-k) * P = k * (-P)
            return self.multiply_binary(-k, -p)
            
        result = None
        current = p
        
        # 處理 k 的每一位
        while k:
            if k & 1:
                # 該位為 1:相加
                if result is None:
                    result = current
                else:
                    result = self.point_ops.add(result, current)
                    
            #  無論該位如何,都需要倍點
            current = self.point_ops.double(current)
            
            # 移到下一位
            k >>= 1
            
        return result
        
    def multiply_windowed(self, k: int, p: Point, window_size: int = 4) -> Point:
        """
        視窗標量乘法
        
        優化版本:預先計算並快取 2^i * P (i = 0, 1, ..., 2^w - 1)
        
        參數:
        - window_size w:視窗大小,通常選擇 4 或 5
        - 需要預計算 2^w 個點
        
        優點:
        - 減少加法次數
        - 缺點:需要更多記憶體
        """
        if k == 0:
            return self.curve.point_at_infinity()
            
        # 預計算 2^i * P for i = 0, ..., 2^w - 1
        precomputed = [p]
        for i in range(1, 2 ** window_size):
            precomputed.append(self.point_ops.add(precomputed[i - 1], p))
            
        result = self.curve.point_at_infinity()
        
        # 處理 k 的每 w 位
        while k > 0:
            # 提取低 w 位
            digit = k & ((1 << window_size) - 1)
            
            # w 次倍點
            for _ in range(window_size):
                result = self.point_ops.double(result)
                
            # 加上下一個預計算的點
            result = self.point_ops.add(result, precomputed[digit])
            
            k >>= window_size
            
        return result
        
    def multiply_naf(self, k: int, p: Point, window_size: int = 5) -> Point:
        """
        NAF(非相鄰形式)標量乘法
        
        NAF 特性:
        - 任意整數有唯一的 NAF 表示
        - NAF 中非零位的密度較低
        - 對於 secp256k1 等曲線,可以減少平均加法次數
        
        NAF 減法比普通減法更快
        """
        if k == 0:
            return self.curve.point_at_infinity()
            
        # 計算 k 的 NAF 表示
        naf = self._compute_naf(k)
        
        # 預計算
        precomputed = self._precompute_naf(p, window_size)
        
        result = self.curve.point_at_infinity()
        
        # 從左到右處理 NAF
        for digit in naf:
            result = self.point_ops.double(result)
            
            if digit > 0:
                result = self.point_ops.add(result, precomputed[digit])
            elif digit < 0:
                result = self.point_ops.subtract(result, precomputed[-digit])
                
        return result
        
    def _compute_naf(self, k: int) -> list:
        """
        計算整數 k 的 NAF(非相鄰形式)
        
        NAF 編碼:
        - 每位為 -1, 0, 或 1
        - 任意兩個連續位不為非零
        """
        naf = []
        while k > 0:
            if k & 1:
                # k 為奇數
                digit = 2 - (k & 3)  # 根據 k mod 4 選擇 1 或 -1
                k = k - digit
            else:
                digit = 0
            naf.append(digit)
            k >>= 1
        return naf
        
    def _precompute_naf(self, p: Point, window_size: int) -> dict:
        """
        為 NAF 方法預計算
        """
        precomputed = {0: self.curve.point_at_infinity()}
        
        # 2^w 個預計算點
        for i in range(1, 2 ** (window_size - 1)):
            if i == 1:
                precomputed[1] = p
            else:
                # 使用重複加倍計算
                if i % 2 == 1:
                    precomputed[i] = self.point_ops.add(precomputed[i - 1], p)
                else:
                    precomputed[i] = self.point_ops.double(precomputed[i // 2])
                    
        # 添加負值(存儲在字典中以供快速查找)
        for i in range(1, 2 ** (window_size - 1)):
            precomputed[-i] = -precomputed[i]
            
        return precomputed

3.4 固定基點標量乘法優化

對於 secp256k1 的生成元 G,有特殊的優化方法:

# 固定基點優化

class FixedBaseMultiplication:
    """
    固定基點標量乘法優化
    
    當基點固定(如 secp256k1 的 G)時,
    可以使用更激進的預計算策略
    
    常用方法:
    1. 固定基點視窗法(GLV 專用)
    2. 批次處理(Straus-Shamir 方法)
    """
    
    def __init__(self):
        self.curve = Secp256k1Curve()
        self.G = self.curve.G
        self.point_ops = PointOperations(self.curve)
        
    def multiply_with_precomputed(self, k: int, precomputed_base: list) -> Point:
        """
        使用預計算表的標量乘法
        
        適用場景:
        - secp256k1 公鑰生成
        - 批量公鑰計算
        
        預計算表通常包含:
        - 256 個點(每個位元一個)
        - 每個點為 2^i * G
        """
        result = self.curve.point_at_infinity()
        
        # 處理 k 的每一位
        for i in range(len(precomputed_base)):
            if k & 1:
                result = self.point_ops.add(result, precomputed_base[i])
            k >>= 1
            
        return result
        
    def generate_base_table(self, num_points: int = 256) -> list:
        """
        生成 secp256k1 生成元的預計算表
        
        table[i] = 2^i * G
        
        這個表可以重複使用以節省計算
        """
        table = [self.G]
        current = self.G
        
        for _ in range(1, num_points):
            current = self.point_ops.double(current)
            table.append(current)
            
        return table
        
    def multiply_glv(self, k: int) -> Point:
        """
        Gallant-Lambert-Vanstone (GLV) 方法
        
        專門針對固定基點的優化
        
        原理:
        - 將標量 k 拆分為 k1 和 k2
        - 同時計算 k1*G 和 k2*G'
        - 其中 G' = λ*G,λ 是曲線的輔助參數
        
        優點:
        - 減少約 50% 的點加法次數
        - 安全強度不變
        
        secp256k1 的 GLV 參數:
        λ = 0x1000000000000000000000000000000000000000000000000000000000000000
        """
        # GLV 標量拆分
        # 這是一個簡化版本,實際實現需要更精確的參數
        lambda_param = 0x1000000000000000000000000000000000000000000000000000000000000000
        
        # 將 k 拆分為 k1 + k2 * λ
        k1 = k % lambda_param
        k2 = (k * 0x1000003D1) % lambda_param  # 近似值
        
        # 計算 G' = λ * G
        lambda_g = self.multiply_scalar(lambda_param, self.G)
        
        # 分別計算
        p1 = self.multiply_scalar(k1, self.G)
        p2 = self.multiply_scalar(k2, lambda_g)
        
        # 合併結果
        return self.point_ops.add(p1, p2)
        
    def multiply_scalar(self, k: int, point: Point) -> Point:
        """
        一般標量乘法(使用二元法)
        """
        result = self.curve.point_at_infinity()
        current = point
        
        while k:
            if k & 1:
                result = self.point_ops.add(result, current)
            current = self.point_ops.double(current)
            k >>= 1
            
        return result

第四章:ECDSA 簽名與驗證

4.1 簽名生成

ECDSA(橢圓曲線數位簽章演算法)是以太坊交易的基礎:

# ECDSA 簽名

class ECDSASignature:
    """
    ECDSA 簽名結構
    
    簽名由兩個值組成:
    - r:公鑰雜湊在曲線上的 x 座標
    - s:簽章值
    
    簽名格式:
    - 65 bytes(r: 32 bytes, s: 32 bytes, v: 1 byte)
    - 或 64 bytes(無 v,v 通常由 recovery id 推導)
    """
    
    def __init__(self, r: int, s: int, v: int = None):
        self.r = r
        self.s = s
        self.v = v  # recovery id (0-3),用於公鑰恢復
        
    def __repr__(self):
        return f"Signature(r={hex(self.r)}, s={hex(self.s)}, v={self.v})"
        
    def to_bytes(self, compressed: bool = False) -> bytes:
        """
        轉換為位元組格式
        """
        if compressed:
            # 33 bytes: 0x02/0x03 + x座標
            prefix = 0x02 if self.v & 1 == 0 else 0x03
            return bytes([prefix]) + self.r.to_bytes(32, 'big')
        else:
            # 65 bytes: r + s + v
            return self.r.to_bytes(32, 'big') + self.s.to_bytes(32, 'big')


class ECDSA:
    """
    ECDSA 簽名與驗證
    
    簽名演算法:
    1. 選擇隨機 nonce k
    2. 計算 R = k * G
    3. r = R.x mod n
    4. s = k^(-1) * (hash + r * d) mod n
       其中 d 是私鑰
    
    驗證演算法:
    1. 計算 w = s^(-1) mod n
    2. u1 = w * hash mod n
    3. u2 = w * r mod n
    4. P = u1 * G + u2 * Q
    5. 驗證 P.x mod n == r
    """
    
    def __init__(self, curve: Secp256k1Curve):
        self.curve = curve
        
    def sign(self, message_hash: int, private_key: int, 
             nonce: int = None) -> ECDSASignature:
        """
        簽名生成
        
        參數:
        - message_hash:訊息雜湊值(32 bytes)
        - private_key:私鑰 d
        - nonce:可選的隨機數 k(用於除錯或確定性簽名)
        
        返回:
        - (r, s) 簽名對
        """
        # 驗證私鑰範圍
        if private_key <= 0 or private_key >= self.curve.N:
            raise ValueError("Invalid private key")
            
        # 驗證訊息雜湊
        if message_hash <= 0 or message_hash >= self.curve.N:
            raise ValueError("Invalid message hash")
            
        # 選擇隨機 nonce(生產環境應使用 RFC 6979)
        if nonce is None:
            nonce = self._generate_nonce(message_hash, private_key)
        else:
            # 驗證 nonce
            if nonce <= 0 or nonce >= self.curve.N:
                raise ValueError("Invalid nonce")
                
        # 步驟 1-2:計算 R = k * G
        G = self.curve.G
        R = self._scalar_multiply(nonce, G)
        
        # 檢查 R 是否為無窮遠點
        if R.is_infinity():
            raise ValueError("R is point at infinity")
            
        # 步驟 3:r = R.x mod n
        r = R.x % self.curve.N
        
        if r == 0:
            raise ValueError("r = 0, retry with different nonce")
            
        # 步驟 4:s = k^(-1) * (hash + r * d) mod n
        k_inv = pow(nonce, self.curve.N - 2, self.curve.N)  # N 是質數,可用費馬小定理
        
        s = (k_inv * (message_hash + r * private_key)) % self.curve.N
        
        if s == 0:
            raise ValueError("s = 0, retry with different nonce")
            
        # DER 格式化時,通常需要低 s 值
        # 但我們保留原始 s 以便公鑰恢復
        
        # 計算 recovery id
        v = 0 if R.y % 2 == 0 else 1
        if R.x != R.x % self.curve.N:
            v += 2
            
        return ECDSASignature(r, s, v)
        
    def verify(self, message_hash: int, signature: ECDSASignature, 
               public_key: Point) -> bool:
        """
        簽名驗證
        
        步驟:
        1. 驗證 r, s 在有效範圍內
        2. w = s^(-1) mod n
        3. u1 = w * e mod n
        4. u2 = w * r mod n
        5. P = u1 * G + u2 * Q
        6. 驗證 P.x mod n == r
        """
        r, s = signature.r, signature.s
        
        # 驗證 r, s 範圍
        if r <= 0 or r >= self.curve.N:
            return False
        if s <= 0 or s >= self.curve.N:
            return False
            
        # 驗證公鑰
        if public_key.is_infinity():
            return False
        if not self.curve.is_on_curve(public_key.x, public_key.y):
            return False
            
        # 步驟 1:w = s^(-1) mod n
        w = pow(s, self.curve.N - 2, self.curve.N)
        
        # 步驟 2:u1 = w * e mod n, u2 = w * r mod n
        e = message_hash % self.curve.N
        u1 = (w * e) % self.curve.N
        u2 = (w * r) % self.curve.N
        
        # 步驟 3:P = u1 * G + u2 * Q
        G = self.curve.G
        u1G = self._scalar_multiply(u1, G)
        u2Q = self._scalar_multiply(u2, public_key)
        P = self._point_add(u1G, u2Q)
        
        # 步驟 4:驗證
        if P.is_infinity():
            return False
            
        v = P.x % self.curve.N
        return v == r
        
    def _scalar_multiply(self, k: int, point: Point) -> Point:
        """標量乘法(使用二元法)"""
        result = self.curve.point_at_infinity()
        current = point
        
        while k:
            if k & 1:
                result = self._point_add(result, current)
            current = self._point_add(current, current)
            k >>= 1
            
        return result
        
    def _point_add(self, p1: Point, p2: Point) -> Point:
        """點加法"""
        if p1.is_infinity():
            return p2
        if p2.is_infinity():
            return p1
            
        x1, y1 = p1.x, p1.y
        x2, y2 = p2.x, p2.y
        
        if x1 == x2:
            if y1 == y2:
                # 倍點
                lam = (3 * x1 * x1) % self.curve.P
                denom_inv = pow(2 * y1, self.curve.P - 2, self.curve.P)
            else:
                return self.curve.point_at_infinity()
        else:
            lam = (y2 - y1) % self.curve.P
            denom_inv = pow(x2 - x1, self.curve.P - 2, self.curve.P)
            
        lam = (lam * denom_inv) % self.curve.P
        x3 = (lam * lam - x1 - x2) % self.curve.P
        y3 = (lam * (x1 - x3) - y1) % self.curve.P
        
        return Point(x3, y3, self.curve)
        
    def _generate_nonce(self, message_hash: int, private_key: int) -> int:
        """
        生成隨機 nonce(生產環境應使用 RFC 6979)
        
        RFC 6979 提供確定性的 nonce 生成方法
        這確保相同的訊息和私鑰總是產生相同的簽名
        """
        # 簡化的隨機 nonce(僅用於教學)
        # 生產環境必須使用 RFC 6979
        import secrets
        nonce = secrets.randbelow(self.curve.N - 1) + 1
        return nonce

4.2 公鑰恢復

以太坊使用特殊的公鑰恢復機制,允許從簽名直接恢復公鑰:

# 公鑰恢復

class PublicKeyRecovery:
    """
    公鑰恢復
    
    以太坊使用 ECDSA 的「公鑰恢復」特性:
    從簽名 (r, s) 可以恢復兩個可能的公鑰
    
    恢復演算法:
    1. 計算 r' = r mod n
    2. 計算候選 x 座標
    3. 從 x 計算可能的 y 座標(兩種)
    4. 驗證候選公鑰
    """
    
    def __init__(self, curve: Secp256k1Curve):
        self.curve = curve
        
    def recover_public_key(self, message_hash: int, 
                          signature: ECDSASignature) -> list:
        """
        從簽名恢復公鑰
        
        恢復公式:
        e = message_hash mod n
        r = signature.r
        s = signature.s
        
        候選點:
        x = r + j * n(j = 0 或 1)
        
        從 x 恢復 y:
        y² = x³ + 7 mod p
        y = sqrt(y²) mod p
        
        最後驗證:r == (k * G).x mod n
        其中 k 需要從 s 和恢復的點計算
        """
        r, s = signature.r, signature.s
        v = signature.v
        
        if r <= 0 or r >= self.curve.N:
            raise ValueError("Invalid r")
        if s <= 0 or s >= self.curve.N:
            raise ValueError("Invalid s")
            
        # 確定 y 座標的奇偶性
        y_parity = v & 1
        
        # 計算候選 x 座標
        x = r
        
        # 從 x 計算 y
        y_squared = (pow(x, 3, self.curve.P) + self.curve.B) % self.curve.P
        y = pow(y_squared, (self.curve.P + 1) // 4, self.curve.P)
        
        # 選擇正確奇偶性的 y
        if (y & 1) != y_parity:
            y = self.curve.P - y
            
        # 恢復點 R
        R = Point(x, y, self.curve)
        
        # 計算 e = -message_hash mod n
        e = (-message_hash) % self.curve.N
        
        # 計算候選公鑰 Q = s * R + e * G
        s_inv = pow(s, self.curve.N - 2, self.curve.N)
        
        sR = self._scalar_multiply(s_inv, R)
        eG = self._scalar_multiply(e, self.curve.G)
        Q = self._point_add(sR, eG)
        
        # 驗證恢復的公鑰
        if not self._verify_public_key(Q):
            raise ValueError("Public key recovery failed")
            
        return [Q]
        
    def recover_public_key_with_candidates(self, message_hash: int,
                                          signature: ECDSASignature) -> list:
        """
        恢復所有可能的公鑰(考慮 v 的兩個可能值)
        
        由於從 (r, s) 可以恢復兩個可能的公鑰
        需要透過 v 來區分
        """
        candidates = []
        
        for v_candidate in [0, 1, 2, 3]:
            try:
                sig = ECDSASignature(signature.r, signature.s, v_candidate)
                pk = self.recover_public_key(message_hash, sig)
                candidates.extend(pk)
            except:
                continue
                
        # 去重
        seen = set()
        unique = []
        for pk in candidates:
            pk_tuple = (pk.x, pk.y)
            if pk_tuple not in seen:
                seen.add(pk_tuple)
                unique.append(pk)
                
        return unique
        
    def _scalar_multiply(self, k: int, point: Point) -> Point:
        """標量乘法"""
        result = self.curve.point_at_infinity()
        current = point
        
        while k:
            if k & 1:
                result = self._point_add(result, current)
            current = self._point_add(current, current)
            k >>= 1
            
        return result
        
    def _point_add(self, p1: Point, p2: Point) -> Point:
        """點加法"""
        if p1.is_infinity():
            return p2
        if p2.is_infinity():
            return p1
            
        x1, y1 = p1.x, p1.y
        x2, y2 = p2.x, p2.y
        
        if x1 == x2:
            if y1 == y2:
                lam = (3 * x1 * x1) % self.curve.P
                denom_inv = pow(2 * y1, self.curve.P - 2, self.curve.P)
            else:
                return self.curve.point_at_infinity()
        else:
            lam = (y2 - y1) % self.curve.P
            denom_inv = pow(x2 - x1, self.curve.P - 2, self.curve.P)
            
        lam = (lam * denom_inv) % self.curve.P
        x3 = (lam * lam - x1 - x2) % self.curve.P
        y3 = (lam * (x1 - x3) - y1) % self.curve.P
        
        return Point(x3, y3, self.curve)
        
    def _verify_public_key(self, pk: Point) -> bool:
        """驗證公鑰有效性"""
        if pk.is_infinity():
            return False
        if not self.curve.is_on_curve(pk.x, pk.y):
            return False
            
        # 驗證 n * pk = O
        n_pk = self._scalar_multiply(self.curve.N, pk)
        return n_pk.is_infinity()

第五章:實際應用與整合

5.1 以太坊地址生成

# 以太坊地址生成

class EthereumAddress:
    """
    以太坊地址生成
    
    流程:
    1. 生成隨機私鑰(256 位元隨機數)
    2. 使用 secp256k1 計算公鑰:Q = d * G
    3. 取公鑰的 Keccak-256 雜湊
    4. 取最後 20 位元組作為地址
    
    地址格式:
    - 原始:40 個十六進位字元
    - EIP-55:加入混合格式(如 0x1234...abcd)
    """
    
    def __init__(self):
        self.curve = Secp256k1Curve()
        self.ecdsa = ECDSA(self.curve)
        self.scalar_mul = ScalarMultiplication(self.curve)
        self.point_ops = PointOperations(self.curve)
        
    def generate_private_key(self) -> int:
        """
        生成安全的隨機私鑰
        
        重要:
        - 必須使用密碼學安全的隨機數生成器
        - 私鑰必須在 [1, n-1] 範圍內
        """
        import secrets
        
        while True:
            # 生成 256 位元隨機數
            private_key = int.from_bytes(
                secrets.randbits(256).to_bytes(32, 'big'),
                'big'
            )
            
            # 驗證範圍
            if 0 < private_key < self.curve.N:
                return private_key
                
    def private_key_to_public_key(self, private_key: int) -> bytes:
        """
        從私鑰計算公鑰
        
        Q = d * G
        
        返回:
        - 未壓縮公鑰:04 + x + y(65 bytes)
        - 壓縮公鑰:02/03 + x(33 bytes)
        """
        # 標量乘法
        G = self.curve.G
        Q = self.scalar_mul.multiply_binary(private_key, G)
        
        # 返回未壓縮格式
        x = Q.x.to_bytes(32, 'big')
        y = Q.y.to_bytes(32, 'big')
        
        return bytes([0x04]) + x + y
        
    def public_key_to_address(self, public_key: bytes) -> str:
        """
        從公鑰計算以太坊地址
        
        1. 取 Keccak-256(public_key)
        2. 取最後 20 bytes
        3. 轉換為十六進位字串
        """
        # 使用 Keccak-256(不是 SHA-3!)
        from Crypto.Hash import keccak
        
        keccak_hash = keccak.new(digest_bits=256)
        keccak_hash.update(public_key)
        hash_bytes = keccak_hash.digest()
        
        # 取最後 20 位元組
        address_bytes = hash_bytes[-20:]
        
        # 轉換為十六進位
        address = '0x' + address_bytes.hex()
        
        return address
        
    def generate_address(self) -> dict:
        """
        生成完整的以太坊地址
        
        返回:
        {
            'private_key': hex,
            'public_key': hex,
            'address': hex
        }
        """
        # 生成私鑰
        private_key = self.generate_private_key()
        
        # 計算公鑰
        public_key = self.private_key_to_public_key(private_key)
        
        # 計算地址
        address = self.public_key_to_address(public_key)
        
        return {
            'private_key': hex(private_key),
            'public_key': public_key.hex(),
            'address': address
        }

5.2 以太坊交易簽名

# 以太坊交易簽名

class EthereumTransaction:
    """
    以太坊交易簽名
    
    交易類型:
    - Legacy Transaction (EIP-155)
    - EIP-2930 (Access List)
    - EIP-1559 (Type 2)
    """
    
    def __init__(self):
        self.curve = Secp256k1Curve()
        self.ecdsa = ECDSA(self.curve)
        
    def sign_transaction(self, tx_params: dict, private_key: int) -> bytes:
        """
        簽名交易
        
        參數:
        - nonce: 交易計數
        - gas_price / max_fee_per_gas: Gas 價格
        - gas_limit: Gas 上限
        - to: 目標地址
        - value: 轉帳金額
        - data: 輸入數據
        - chain_id: 鏈 ID
        
        流程:
        1. RLP 編碼交易(不含 signature)
        2. 計算 Keccak-256 雜湊
        3. 使用私鑰簽名
        4. 拼接 signature 到交易
        """
        # 提取交易參數
        tx_type = tx_params.get('type', 0)
        nonce = tx_params['nonce']
        chain_id = tx_params['chain_id']
        
        if tx_type == 2:
            # EIP-1559
            max_priority_fee_per_gas = tx_params['max_priority_fee_per_gas']
            max_fee_per_gas = tx_params['max_fee_per_gas']
            gas_limit = tx_params['gas']
            to = tx_params['to']
            value = tx_params['value']
            data = tx_params.get('data', b'')
            
            # 建構交易物件(RLP 編碼前)
            tx_for_signing = {
                'chain_id': chain_id,
                'nonce': nonce,
                'max_priority_fee_per_gas': max_priority_fee_per_gas,
                'max_fee_per_gas': max_fee_per_gas,
                'gas': gas_limit,
                'to': to,
                'value': value,
                'data': data,
                'access_list': tx_params.get('access_list', []),
                'max_fee_per_blob_gas': tx_params.get('max_fee_per_blob_gas', 0),
                'blob_hashes': tx_params.get('blob_hashes', [])
            }
            
        elif tx_type == 1:
            # EIP-2930
            # ...
            pass
            
        else:
            # Legacy Transaction
            gas_price = tx_params['gas_price']
            gas_limit = tx_params['gas']
            to = tx_params['to']
            value = tx_params['value']
            data = tx_params.get('data', b'')
            
        # 計算交易雜湊
        tx_hash = self._hash_for_signing(tx_for_signing, tx_type)
        
        # 簽名
        signature = self.ecdsa.sign(tx_hash, private_key)
        
        # 處理 s 值(必須低於 n/2 以防止簽章重播攻擊)
        # 這是 BIP-62 的要求
        if signature.s > self.curve.N // 2:
            signature.s = self.curve.N - signature.s
            signature.v = 1 - signature.v
            
        # 編碼完整交易
        signed_tx = self._encode_signed_transaction(
            tx_for_signing, 
            signature,
            tx_type
        )
        
        return signed_tx
        
    def _hash_for_signing(self, tx: dict, tx_type: int) -> int:
        """
        計算交易雜湊(用於簽名)
        
        對於 EIP-1559,雜湊 = Keccak256(0x02 || RLP(chain_id, nonce, ...))
        """
        # RLP 編碼交易
        rlp_encoded = self._rlp_encode(tx, tx_type)
        
        # 添加交易類型前綴
        if tx_type > 0:
            encoded = bytes([tx_type]) + rlp_encoded
        else:
            encoded = rlp_encoded
            
        # Keccak-256
        from Crypto.Hash import keccak
        keccak_hash = keccak.new(digest_bits=256)
        keccak_hash.update(encoded)
        
        return int.from_bytes(keccak_hash.digest(), 'big')

結論

secp256k1 橢圓曲線密碼學是以太坊安全的數學基石。理解其底層實作對於:

開發者的價值

  1. 安全開發:深入理解密碼學原語,有助於避免常見的安全錯誤
  2. 效能優化:了解運算複雜度,可針對特定場景進行優化
  3. 除錯能力:在密碼學相關 bug 發生時,能快速定位問題根源

關鍵技術要點

  1. 有限域運算:所有計算都在模質數 p 的有限域上進行
  2. 點運算:加法、倍點、標量乘法是核心
  3. 標量乘法優化:二元法、視窗法、GLV 等技術大幅提升效率
  4. ECDSA 簽名:數位簽章提供不可否認的認證

安全提醒

本指南的程式碼主要用於教學目的,深入理解 secp256k1 的工程師應進一步研究比特幣和以太坊的實際密碼學實現(如 libsecp256k1)。


免責聲明:本文旨在教育目的。密碼學實作涉及高度安全性要求,請勿將本指南中的程式碼直接用於生產環境。生產環境應使用經過正式安全審計的密碼學庫。

最後更新日期:2026 年 3 月 22 日

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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