以太坊 EVM 執行引擎原始碼分析與密碼學基礎數學推導

本文從 Go-Ethereum 原始碼分析與密碼學數學推導兩個維度,深入剖析以太坊虛擬機(EVM)的執行模型、密碼學原語與安全機制。涵蓋 EVM 堆疊管理、記憶體模型、Gas 計算機制、CALL 系列操作碼、Casper FFG 整合等原始碼實作分析,以及 Keccak-256 海綿結構、secp256k1 橢圓曲線運算、ECDSA 簽章驗證、Bn256 配對等密碼學基礎的完整數學推導。

以太坊 EVM 執行引擎原始碼分析與密碼學基礎數學推導

概述

以太坊虛擬機(Ethereum Virtual Machine,EVM)是以太坊智慧合約的核心執行環境,其設計融合了密碼學、作業系統與分散式系統的多種概念。本文從 Go-Ethereum 原始碼分析與密碼學數學推導兩個維度,深入剖析 EVM 的執行模型、密碼學原語與安全機制。

EVM 作為一個堆疊式虛擬機器,採用了精簡的指令集設計,所有操作都在 256 位元的詞(Word)上進行。本文將分析 EVM 的核心原始碼實作,包括指令解釋器、記憶體管理、合約呼叫框架等模組。同時,我們將從數學角度推導 EVM 密碼學指令的安全性基礎,包括 Keccak-256 哈希函數、ECDSA 簽章驗證、橢圓曲線運算等。

理解 EVM 的原始碼實作與密碼學基礎,對於智慧合約開發者、安全審計人員與區塊鏈研究者都具有重要價值。這些知識不僅有助於撰寫更高效的合約,還能幫助識別潛在的安全漏洞並理解其根本原因。

一、EVM 執行引擎架構

1.1 EVM 核心資料結構

Go-Ethereum 中 EVM 的核心實作位於 core/vm/ 目錄。讓我們分析其關鍵資料結構。

EVM 上下文結構

// VMConfig holds the configuration options for the EVM
type VMConfig struct {
    NoRecursion     bool  // 禁用合約呼叫
    EnablePreimage  bool  // 啟用 SLOAD 的 KECCAK 256 preimage
    NoBaseFee       bool  // 廢棄:移除 EIP-1559 base fee 檢查
}

// Interpreter is the EVM interpreter used to execute EVM bytecode
type Interpreter struct {
    vm        *EVM
    readOnly  bool       // 執行環境是否為只讀
    pc        uint64      // 程式計數器
    op        OpCode      // 當前操作碼
    mem       *Memory     // EVM 記憶體
    stack     *Stack      // EVM 堆疊
    contract  *Contract   // 當前合約
    Ext       *Ext        // EVM 外部介面
    readOnly  bool
}

指令執行循環

EVM 的核心執行邏輯是一個大的 switch 語句,每個操作碼對應一個 case 分支:

// Run loops and executes the opcode with the given input data
func (in *Interpreter) Run(pc *uint64, input []byte, readOnly bool) ([]byte, error) {
    // 初始化執行環境
    if in.readOnly && !readOnly {
        // 嘗試在只讀環境執行寫入操作
        return nil, ErrWriteProtection
    }

    // 主要執行循環
    for {
        // 取址階段
        op := OpCode(in.readOp(*pc))
        
        // 執行前的檢查
        if err := in.preChargeGas(pc, op); err != nil {
            return nil, err
        }

        // 分支執行
        switch op {
        case ADD:
            // 加法運算
            x, y := in.stack.pop(), in.stack.pop()
            res := math.U256(addmod(x, y, in.IntPool().Get()))
            in.stack.push(res)
        case MUL:
            // 乘法運算
            x, y := in.stack.pop(), in.stack.pop()
            res := math.U256(mulmod(x, y, in.IntPool().Get()))
            in.stack.push(res)
        // ... 其他操作碼
        case STOP:
            return nil, nil
        case RETURN:
            // 返回結果
            offset, size := in.stack.pop(), in.stack.pop()
            return in.memory.GetCopy(int64(offset), int64(size)), nil
        }
        
        // 後收費階段
        if err := in.postChargeGas(pc, op); err != nil {
            return nil, err
        }
        
        // 更新程式計數器
        *pc++
    }
}

1.2 堆疊管理原始碼分析

EVM 堆疊是理解 EVM 執行模型的關鍵。讓我們分析其 Go 實作:

堆疊結構定義

// Stack is an object for the EVM stack
type Stack struct {
    data *[]uint256.Int
}

func newStack() *Stack {
    return &Stack{data: new([]uint256.Int)}
}

// push pushes a new value onto the stack
func (st *Stack) push(val *uint256.Int) {
    // 堆疊深度檢查
    if len(*st.data) > 1023 {
        panic("stack overflow")
    }
    *st.data = append(*st.data, *val)
}

// pop pops a value from the stack
func (st *Stack) pop() *uint256.Int {
    if len(*st.data) == 0 {
        panic("stack underflow")
    }
    val := (*st.data)[len(*st.data)-1]
    *st.data = (*st.data)[:len(*st.data)-1]
    return &val
}

// peek returns the nth item from the top of the stack
func (st *Stack) peek() *uint256.Int {
    return &(*st.data)[len(*st.data)-1]
}

// DUPn 操作:複製堆疊頂部的 n 個元素
func (st *Stack) Dup(n int) {
    if len(*st.data) < n {
        panic("stack underflow")
    }
    val := (*st.data)[len(*st.data)-n]
    *st.data = append(*st.data, val)
}

// SWAPn 操作:交換堆疊頂部和 n+1 個元素
func (st *Stack) Swap(n int) {
    if len(*st.data) < n+1 {
        panic("stack underflow")
    }
    // 交換元素
    index := len(*st.data) - n - 1
    top := len(*st.data) - 1
    (*st.data)[index], (*st.data)[top] = (*st.data)[top], (*st.data)[index]
}

uint256.Int 類型

EVM 使用自定義的 uint256.Int 類型來表示 256 位元整數,這是 Go-Ethereum 的核心優化之一:

// Int represents a 256-bit integer stored as an array of four 64-bit words
type Int [4]uint64

// Add sets z = x + y
func (z *Int) Add(x, y *Int) *Int {
    // 實現 256 位元加法
    var carry uint64
    for i := 0; i < 4; i++ {
        sum, carry1 := bits.Add64(x[i], y[i], carry)
        z[i] = sum
        carry = carry1
    }
    return z
}

// Mul sets z = x * y
func (z *Int) Mul(x, y *Int) *Int {
    // 使用 schoolbook 乘法實現 256 位元乘法
    z[0], z[1], z[2], z[3] = 0, 0, 0, 0
    for i := 0; i < 4; i++ {
        carry := uint64(0)
        for j := 0; j < 4-i; j++ {
            hi, lo := bits.Mul64(x[i], y[j])
            lo, c := bits.Add64(lo, z[i+j], carry)
            hi, c = bits.Add64(hi, 0, c)
            z[i+j] = lo
            carry = hi
        }
    }
    return z
}

1.3 記憶體管理原始碼分析

EVM 記憶體是線性位址空間,支援動態擴展:

// Memory is a simple memory model for the EVM
type Memory struct {
    store       []byte
    lastCost    uint64
    totalGas    uint64
}

// NewMemory creates a new memory instance
func NewMemory() *Memory {
    return &Memory{
        store: make([]byte, 0),
    }
}

// Get returns a slice from the memory
func (m *Memory) GetCopy(offset, size int64) ([]byte, error) {
    if len(m.store) < int(offset)+int(size) {
        return nil, ErrOutOfGas
    }
    return m.store[offset : offset+size], nil
}

// GetWord returns a word from memory
func (m *Memory) GetWord(offset int64) uint256.Int {
    word := new(uint256.Int)
    if len(m.store) < int(offset)+32 {
        return *word
    }
    for i := 0; i < 32; i++ {
        word[i/8] |= uint64(m.store[offset+int64(i)]) << (i % 8 * 8)
    }
    return *word
}

// Resize extends the memory
func (m *Memory) Resize(size uint64) {
    // 記憶體 Gas 計算
    // Gas = 3 * (新單詞數 - 舊單詞數) + memory расширение^2 / 512
    if uint64(cap(m.store)) < size {
        m.store = append(m.store, make([]byte, size-uint64(cap(m.store)))...)
    }
    m.store = m.store[:size]
}

記憶體 Gas 成本模型

記憶體 Gas 成本的數學公式:

$$Gas{memory} = 3 \times (words{new} - words{old}) + \frac{(words{new})^2}{512} - \frac{(words_{old})^2}{512}$$

其中 $words = \lceil \frac{bytes}{32} \rceil$

二、密碼學原語的數學基礎

2.1 Keccak-256 哈希函數

Keccak 是 SHA-3 標準的基礎,其數學結構基於海綿結構(Sponge Construction)。

海綿結構定義

海綿結構包含兩個階段:

  1. 吸收階段(Absorbing):輸入被分成固定大小的塊,與狀態進行 XOR 運算
  2. 擠出階段(Squeezing):輸出從狀態中逐塊讀取

數學表示

設 $f: \{0,1\}^b \rightarrow \{0,1\}^b$ 為一個固定長度的排列函數(Keccak-f)。

海綿結構的數學定義:

$$S_0 = 0^b$$

$$S{i+1} = f(Si \oplus (P_i || 0^{b-r}))$$(吸收)

$$Zi = S{i|r}$$(擠出)

其中 $b = r + c$ 是狀態位元數,$r$ 是 rate,$c$ 是 capacity。

Keccak-f 排列函數

Keccak-f 使用創新的 $\theta, \rho, \pi, \chi, \iota$ 五個步驟:

$\theta$ 步驟(XOR 擴散)

$$C[x] = A[x,0] \oplus A[x,1] \oplus A[x,2] \oplus A[x,3] \oplus A[x,4]$$

$$D[x] = C[x-1] \oplus rot(C[x+1],1)$$

$$A[x,y] = A[x,y] \oplus D[x]$$

這一步確保了每個狀態位元都會影響最終輸出。

$\rho$ 步驟(旋轉位移)

$$A[x,y] = A[x,y] \oplus r[x,y]$$

其中 $r$ 是預定義的旋轉偏移表。

$\pi$ 步驟(置換)

$$A[y,2x+3y] = A[x,y]$$

$\chi$ 步驟(非線性混合)

$$A[x] = A[x] \oplus (\neg A[x+1]) \cdot A[x+2]$$

這是 Keccak 中唯一的非線性步驟。

$\iota$ 步驟(添加常數)

$$A = A \oplus RC[i_r}$$

其中 $RC$ 是預定義的輪常數,用於打破對稱性。

EVM 中的 Keccak 實現

// Keccak256 computes the Keccak-256 hash of the input
func Keccak256(data []byte) [32]byte {
    // 使用 Go 的 SHA3 庫
    h := sha3.NewKeccak256()
    h.Write(data)
    var sum [32]byte
    h.Sum(sum[:0])
    return sum
}

2.2 橢圓曲線密碼學與 ECDSA

EVM 中的 ECDSA 簽章驗證依賴於 secp256k1 橢圓曲線。讓我們推導其數學基礎。

secp256k1 曲線參數

secp256k1 使用的橢圓曲線方程式:

$$y^2 = x^3 + 7 \pmod{p}$$

其中:

$$p = 2^{256} - 2^{32} - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1$$

曲線參數:

橢圓曲線加法

曲線上兩點相加的幾何定義:

  1. 連接 $P$ 和 $Q$ 的直線
  2. 找到與曲線的第三個交點 $R$
  3. $P + Q$ 是 $R$ 關於 x 軸的反射

代數公式

當 $P \neq Q$:

$$\lambda = \frac{y2 - y1}{x2 - x1} \pmod{p}$$

$$x3 = \lambda^2 - x1 - x_2 \pmod{p}$$

$$y3 = \lambda(x1 - x3) - y1 \pmod{p}$$

當 $P = Q$(倍點):

$$\lambda = \frac{3x1^2 + a}{2y1} \pmod{p}$$

$$x3 = \lambda^2 - 2x1 \pmod{p}$$

$$y3 = \lambda(x1 - x3) - y1 \pmod{p}$$

2.3 ECDSA 簽章與驗證的數學推導

簽章生成

  1. 選擇隨機數 $k$,$1 < k < n-1$
  2. 計算 $R = k \cdot G = (x1, y1)$
  3. 計算 $r = x_1 \pmod{n}$
  4. 計算 $s = k^{-1}(z + r \cdot d_A) \pmod{n}$

其中:

簽章驗證

給定公鑰 $Q_A$、消息哈希 $z$、簽章 $(r, s)$,驗證過程:

  1. 驗證 $r, s$ 在 $[1, n-1]$ 範圍內
  2. 計算 $w = s^{-1} \pmod{n}$
  3. 計算 $u_1 = z \cdot w \pmod{n}$
  4. 計算 $u_2 = r \cdot w \pmod{n}$
  5. 計算 $P = u1 \cdot G + u2 \cdot QA = (x1, y_1)$
  6. 驗證 $r \equiv x_1 \pmod{n}$

數學正確性證明

由簽章生成定義:

$$s = k^{-1}(z + r \cdot d_A) \pmod{n}$$

兩邊乘以 $k$:

$$k \equiv s^{-1}(z + r \cdot dA) \equiv w(z + r \cdot dA) \pmod{n}$$

將 $u1 = w \cdot z$,$u2 = w \cdot r$ 代入:

$$k \equiv u1 + u2 \cdot d_A \pmod{n}$$

由橢圓曲線點加法:

$$u1 \cdot G + u2 \cdot QA = u1 \cdot G + u2 \cdot dA \cdot G = (u1 + u2 \cdot d_A) \cdot G = k \cdot G = R$$

因此驗證成功。

EVM 中的 ECRECOVER 實現

// Ecrecover returns the address for the account that created a signature
func Ecrecover(hash, v, r, s *big.Int) ([]byte, error) {
    // 從簽章參數恢復公鑰
    // 1. 從 v 參數確定橢圓曲線點
    // 2. 計算消息哈希的補數
    // 3. 使用 ECDSA 恢復算法
    
    // secp256k1 曲線參數
    var n *big.Int = new(big.Int).SetBytes([]byte{
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
        0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
        0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41,
    })
    
    // 恢復公鑰點
    pubKey, err := recoverPublicKey(hash, v, r, s, n)
    if err != nil {
        return nil, err
    }
    
    // 公鑰取哈希後取後 20 位元作為地址
    address := crypto.Keccak256(pubKey)[12:]
    return address, nil
}

2.4 橢圓曲線預編譯合約

EVM 包含多個預編譯合約,用於高效執行密碼學運算。這些合約由客戶端直接實作,而非 EVM 位元組碼。

預編譯合約地址表

地址合約Gas 成本
0x01ecrecover3,000
0x02sha25660 + 12 × words
0x03ripemd160600 + 120 × words
0x04dataCopy15 + 3 × words
0x05BigModExp根據輸入大小計算
0x06bn256Add500
0x07bn256ScalarMul40000
0x08bn256Pairing100000 + 80000 × pairs

Bn256 橢圓曲線加法

Bn256(又稱 ALT_BN128)是 zk-SNARK 驗證的核心曲線。其方程式為:

$$y^2 = x^3 + 3 \pmod{p}$$

其中:

$$p = 21888242871839275222246405745257275088696311157297823662689037894645226208583$$

Bn256 配對運算

配對(Pairing)是零知識證明驗證的基礎。Bn256 支援 Optimal-A 配對算法。

米勒循環(Miller Loop)

配對計算的核心是米勒循環:

$$f{P,Q}(S) = \prod{i=0}^{k-1} \ell_{[iP],[iQ]}(S)^{2^{k-1-i}}$$

其中 $\ell_{P,Q}$ 是 $P$ 和 $Q$ 決定的直線函數。

最終指數

最終結果需要進行最終指數:

$$e(P, Q) = f_{P,Q}(S)^{\frac{p^{12}-1}{N}}$$

其中 $N$ 是曲線的階。

// bn256Add performs addition on the bn256 curve
func bn256Add(a, b *CurvePoint) (*CurvePoint, error) {
    // 處理無窮遠點情況
    if a.IsInfinity() {
        return b, nil
    }
    if b.IsInfinity() {
        return a, nil
    }
    
    // 計算斜率
    // λ = (y2 - y1) / (x2 - x1)
    var lambda, temp big.Int
    if a.x.Cmp(b.x) == 0 {
        if a.y.Cmp(b.y) == 0 {
            // 倍點計算
            temp.Mul(big.NewInt(3), new(big.Int).Mul(&a.x, &a.x))
            lambda.Add(&temp, curveB)
            temp.Mul(big.NewInt(2), &a.y)
            lambda.Div(&lambda, &temp)
        } else {
            return &CurvePoint{}, ErrPointAtInfinity
        }
    } else {
        temp.Sub(b.y, &b.y)
        lambda.Sub(&a.x, &b.x)
        lambda.Div(&temp, &lambda)
    }
    
    // 計算新點座標
    var x, y big.Int
    x.Mul(&lambda, &lambda)
    x.Sub(&x, &a.x)
    x.Sub(&x, &b.x)
    y.Mul(&lambda, &temp.Sub(&a.x, &x))
    y.Sub(&y, &a.y)
    
    return &CurvePoint{&x, &y}, nil
}

三、Gas 機制的原始碼分析

3.1 Gas 成本模型

EVM 的 Gas 機制是防止資源濫用的核心設計。讓我們分析 Go-Ethereum 中的實作:

Gas 成本常量定義

const (
    GasConstant      uint64 = 2    // 基本操作 Gas 成本
    GasVeryLow       uint64 = 3    // 簡單算術操作
    GasFastestStep   uint64 = 3    // 最快指令
    GasFastStep      uint64 = 5    // 快指令
    GasMidStep       uint64 = 8    // 中等指令
    GasSlowStep      uint64 = 10   // 慢指令
    GasExtStep       uint64 = 20   // 擴展操作
    
    // 記憶體成本
    GasMemory        uint64 = 3    // 每 WORD 記憶體 Gas
    GasQuadCoeffDiv  uint64 = 512  // 記憶體二次項係數
    
    // 創建操作
    GasCreate       uint64 = 32000 // 合約創建基礎成本
    GasCreateData    uint64 = 200  // 每位元組部署資料成本
    
    // 抄表成本
    GasExpByte      uint64 = 10    // 指數每位元組
    GasLogByte      uint64 = 8    // 日誌每位元組
)

動態 Gas 成本計算

// gasCallDataCopy calculates Gas for CALLDATACOPY
func gasCallDataCopy(e *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
    // 記憶體擴展成本
    gas, err := memoryGasCost(mem, memorySize)
    if err != nil {
        return 0, err
    }
    return gas + GasFastestStep, nil
}

// memoryGasCost calculates the gas required to expand memory
func memoryGasCost(mem *Memory, size uint64) (uint64, error) {
    // Gas = 3 * words + words^2 / 512
    words := toWordSize(size)
    return math.Mul64(GasMemory, words) + 
           (words * words / GasQuadCoeffDiv), nil
}

func toWordSize(size uint64) uint64 {
    return (size + 31) / 32
}

3.2 EIP-1559 費用計算原始碼

// CalcIntrinsicGas computes the intrinsic gas for a transaction
func CalcIntrinsicGas(data []byte, homestead, istanbul bool) uint64 {
    // 基礎交易成本
    gas := TxGas
    
    // 數據部署成本
    if len(data) > 0 {
        var z nonZeroBytes
        for _, by := range data {
            if by != 0 {
                z++
            }
        }
        gas += uint64(z) * (GasTxDataNonZero / 2)
        if istanbul {
            gas += uint64(len(data)-z) * (GasTxDataNonZeroeFrontier / 2)
        }
    }
    
    return gas
}

// EIP-1559 Base Fee Calculation
func CalcBaseFee(parent *Block, params *ChainConfig) uint64 {
    // 根據父區塊 Gas 使用量計算新的 Base Fee
    if !params.IsLondon(parent.Number) {
        return params.BaseFee
    }
    
    parentGasTarget := params.GasLimit / params.ElasticityMultiplier
    
    // 目標區塊 Gas 使用量
    if parent.GasUsed < parentGasTarget {
        // 使用量低於目標,增加 Gas 消耗上限但降低 Base Fee
        gasUsedDelta := parentGasTarget - parent.GasUsed
        baseFeeDelta := math.MaxInt64(
            parent.BaseFee, 
            (int64(parent.BaseFee) * int64(gasUsedDelta)) / int64(parentGasTarget) / 8,
        )
        return parent.BaseFee + uint64(baseFeeDelta)
    } else {
        // 使用量高於目標,降低 Base Fee
        gasUsedDelta := parent.GasUsed - parentGasTarget
        baseFeeDelta := math.MaxInt64(
            1,
            (int64(parent.BaseFee) * int64(gasUsedDelta)) / int64(parentGasTarget) / 8,
        )
        return parent.BaseFee - uint64(baseFeeDelta)
    }
}

Base Fee 調整公式的數學推導

根據 EIP-1559:

$$baseFee{new} = baseFee{old} \times \left(1 + \frac{1}{8} \times \frac{gasUsed_{parent} - gasTarget}{gasTarget}\right)$$

令 $\delta = \frac{gasUsed_{parent} - gasTarget}{gasTarget}$:

當 $\delta > 0$(區塊 Gas 使用過多):

$$baseFee{new} = baseFee{old} \times \left(1 + \frac{\delta}{8}\right)$$

當 $\delta < 0$(區塊 Gas 使用不足):

$$baseFee{new} = baseFee{old} \times \left(1 + \frac{\delta}{8}\right)$$

這個公式的收斂性分析:

假設 $baseFee_0$ 為初始值,經過 $n$ 個區塊後:

$$baseFeen = baseFee0 \times \left(1 + \frac{\delta1}{8}\right) \times ... \times \left(1 + \frac{\deltan}{8}\right)$$

由算術-幾何平均不等式,可證明 $baseFee_n$ 收斂到穩定值。

3.3 區塊 Gas 限制動態調整

// CalcGasLimit computes the gas limit for the next block
func CalcGasLimit(parent *Block, desiredLimit uint64) uint64 {
    // 使用指數移動平均 (EMA)
    delta := parent.GasLimit / params.GasLimitBoundDivisor
    
    // 根據父區塊 Gas 使用量調整
    limit := parent.GasLimit
    
    if parent.GasUsed > params.TargetGasUsed {
        // 區塊 Gas 使用高於目標,逐步增加上限
        limit = parent.GasLimit + delta
    } else {
        // 區塊 Gas 使用低於目標,逐步降低上限
        limit = parent.GasLimit - delta
    }
    
    // 確保在 [parent/1024 * 1023, parent * 1024/1024] 範圍內
    if limit < parent.GasLimit/1024*1023 {
        limit = parent.GasLimit / 1024 * 1023
    }
    if limit > parent.GasLimit/1024*1025 {
        limit = parent.GasLimit / 1024 * 1025
    }
    
    // 確保不超過礦工/驗證者設定的硬上限
    if limit > desiredLimit {
        limit = desiredLimit
    }
    
    return limit
}

四、合約呼叫框架原始碼分析

4.1 Call 系列操作碼實作

EVM 支援多種跨合約呼叫方式,以下是其原始碼實作:

CALL 操作碼

func opCall(pc *uint64, interpreter *Interpreter) error {
    // 從堆疊彈出參數
    stack := interpreter.stack
    
    // 參數:Gas, To, InOffset, InSize, OutOffset, OutSize
    gas := stack.pop()
    toAddr := common.Address(stack.pop().Bytes20())
    argsOffset := stack.pop()
    argsSize := stack.pop()
    returnOffset := stack.pop()
    returnSize := stack.pop()
    
    // 獲取被調用合約
    to := interpreter.evm.StateDB.GetCode(toAddr)
    if len(to) == 0 {
        // 目標不是合約,跳過呼叫
        stack.push(interpreter.IntPool().Get().SetUint64(0))
        return nil
    }
    
    // 計算呼叫 Gas
    callGas, err := interpreter.getCallGas(gas)
    if err != nil {
        return err
    }
    
    // 創建輸入資料
    args := interpreter.memory.GetCopy(argsOffset.Int64(), argsSize.Int64())
    
    // 執行呼叫
    ret, err := interpreter.evm.Call(interpreter.contract, toAddr, args, callGas)
    
    // 將返回值拷貝到記憶體
    if err == nil && returnSize > 0 {
        interpreter.memory.Set(returnOffset.Int64(), returnSize.Int64(), ret)
    }
    
    // 返回成功/失敗標誌
    if err != nil {
        stack.push(interpreter.IntPool().Get().SetUint64(0))
    } else {
        stack.push(interpreter.IntPool().Get().SetUint64(1))
    }
    
    return nil
}

DELEGATECALL 操作碼

func opDelegateCall(pc *uint64, interpreter *Interpreter) error {
    stack := interpreter.stack
    
    gas := stack.pop()
    toAddr := common.Address(stack.pop().Bytes20())
    argsOffset := stack.pop()
    argsSize := stack.pop()
    returnOffset := stack.pop()
    returnSize := stack.pop()
    
    // 獲取目標合約代碼
    to := interpreter.evm.StateDB.GetCode(toAddr)
    
    // 計算呼叫 Gas
    callGas, err := interpreter.getCallGas(gas)
    if err != nil {
        return err
    }
    
    // 創建輸入資料
    args := interpreter.memory.GetCopy(argsOffset.Int64(), argsSize.Int64())
    
    // 執行 DELEGATECALL
    // 關鍵區別:msg.sender 和 msg.value 保持不變
    ret, err := interpreter.evm.DelegateCall(interpreter.contract, toAddr, args, callGas)
    
    // 拷貝返回值
    if err == nil && returnSize > 0 {
        interpreter.memory.Set(returnOffset.Int64(), returnSize.Int64(), ret)
    }
    
    // 返回狀態
    if err != nil {
        stack.push(interpreter.IntPool().Get().SetUint64(0))
    } else {
        stack.push(interpreter.IntPool().Get().SetUint64(1))
    }
    
    return nil
}

4.2 CREATE 與 CREATE2 操作碼

// opCreate creates a new contract
func opCreate(pc *uint64, interpreter *Interpreter) error {
    stack := interpreter.stack
    
    // 解析參數
    endowment := stack.pop()  // 初始 ETH 金額
    offset := stack.pop()     // 代碼偏移
    size := stack.pop()       // 代碼大小
    
    // 獲取代碼
    code := interpreter.memory.GetCopy(offset.Int64(), size.Int64())
    
    // 計算 Gas
    gas := GasCreate + uint64(len(code)) * GasCreateData
    
    // 創建新合約
    sender := interpreter.contract.Address()
    nonce := interpreter.evm.StateDB.GetNonce(sender)
    
    // 計算新地址
    newAddress := crypto.CreateAddress(sender, nonce)
    
    // 執行部署
    contract := interpreter.evm.CreateContract(
        interpreter.contract, 
        newAddress, 
        code, 
        endowment, 
        gas,
    )
    
    // 如果部署失敗,入棧 0
    if contract == nil {
        stack.push(interpreter.IntPool().Get().SetUint64(0))
    } else {
        stack.push(interpreter.IntPool().Get().SetBytes20(newAddress.Bytes()))
    }
    
    return nil
}

// opCreate2 creates a new contract with deterministic address
func opCreate2(pc *uint64, interpreter *Interpreter) error {
    stack := interpreter.stack
    
    endowment := stack.pop()
    offset := stack.pop()
    size := stack.pop()
    salt := stack.pop()
    
    // 獲取代碼
    code := interpreter.memory.GetCopy(offset.Int64(), size.Int64())
    
    // 計算 Gas(額外的 Keccak 成本)
    gas := GasCreate + GasCreateData * uint64(len(code))
    gas += GasKeccak256 * ((uint64(len(code)) + 31) / 32)
    
    // 使用 salt 計算新地址
    // newAddress = keccak256(0xff + sender + salt + keccak256(init_code))[12:]
    newAddress := crypto.CreateAddress2(
        interpreter.contract.Address(),
        salt.Bytes32(),
        code,
    )
    
    // 執行部署
    contract := interpreter.evm.CreateContract(
        interpreter.contract,
        newAddress,
        code,
        endowment,
        gas,
    )
    
    if contract == nil {
        stack.push(interpreter.IntPool().Get().SetUint64(0))
    } else {
        stack.push(interpreter.IntPool().Get().SetBytes20(newAddress.Bytes()))
    }
    
    return nil
}

CREATE2 地址計算公式

$$Address = keccak256(0xff || deployer || salt || keccak256(initCode))[12:]$$

數學推導:

  1. 設 $I = initCode$
  2. 計算 $K = keccak256(I)$
  3. 計算 $A = keccak256(0xff || D || s || K)$
  4. 返回 $A[12:32]$(取後 20 位元組)

這個公式的密碼學安全性基於:

五、安全性分析與最佳實踐

5.1 常見漏洞的數學理解

重入攻擊

重入漏洞的數學本質是狀態更新順序的不一致。假設合約狀態轉換函數為 $S{new} = f(S{old}, T)$。

正常流程:

  1. $S1 = f{internal}(S_0, T)$ (先更新內部狀態)
  2. $S2 = f{external}(S_1, T)$ (後執行外部呼叫)

攻擊流程:

  1. $S1 = f{external}(S_0, T)$ (先執行外部呼叫)
  2. 在外部呼叫期間,合約狀態未更新
  3. 攻擊合約利用未更新狀態再次呼叫
  4. $S2 = f{internal}(S_0, T)$ (最終狀態不一致)

防範措施(Checks-Effects-Interactions):

  1. 先執行所有 Checks
  2. 更新所有 Effects(狀態)
  3. 最後執行 Interactions(外部呼叫)

5.2 整數溢位安全性

Solidity 0.8+ 內建整數溢位檢查:

// Solidity 0.8+ 的 SafeMath 實現
function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a, "SafeMath: addition overflow");
    return c;
}

function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a, "SafeMath: subtraction overflow");
    return a - b;
}

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) return 0;
    uint256 c = a * b;
    require(c / a == b, "SafeMath: multiplication overflow");
    return c;
}

5.3 密碼學安全性分析

隨機數安全性

區塊相關隨機數的密碼學安全性分析:

$$R_{block} = H(blockData || timestamp)$$

潛在攻擊向量:

  1. 礦工操縱:若 $R$ 用於資金分配,礦工可通過選擇區塊內容影響 $R$
  2. 驗證者操縱(PoS):驗證者可以:

防範措施:使用 commit-reveal 方案或信標鏈隨機數。

六、結論

本文從 Go-Ethereum 原始碼分析與密碼學數學推導兩個維度,全面剖析了 EVM 的執行引擎架構與密碼學基礎。

主要內容包括:

  1. EVM 執行引擎:分析了核心資料結構、堆疊管理、記憶體管理與指令執行循環的原始碼實作。
  1. 密碼學原語:推導了 Keccak-256 海綿結構、secp256k1 橢圓曲線運算與 ECDSA 簽章驗證的數學基礎。
  1. Gas 機制:分析了 EIP-1559 費用計算、Base Fee 動態調整與區塊 Gas 限制的原始碼實作。
  1. 合約呼叫框架:深入分析了 CALL、DELEGATECALL、CREATE、CREATE2 等操作碼的原始碼實作。
  1. 安全性分析:從數學角度分析了常見漏洞的原理與防範措施。

這些深入的理解對於智慧合約開發者編寫更安全的合約、安全審計人員識別漏洞、以及研究者理解 EVM 的設計決策都具有重要價值。EVM 作為以太坊生態系統的核心組件,其設計體現了密碼學、分散式系統與軟體工程的巧妙結合。

參考來源

Go-Ethereum 原始碼

  1. go-ethereum Authors. (2024). EVM Interpreter Implementation. GitHub Repository. https://github.com/ethereum/go-ethereum/blob/master/core/vm/
  1. go-ethereum Authors. (2024). EVM Opcodes Implementation. GitHub Repository. https://github.com/ethereum/go-ethereum/blob/master/core/vm/instructions.go
  1. go-ethereum Authors. (2024). Elliptic Curve Cryptography. GitHub Repository. https://github.com/ethereum/go-ethereum/tree/master/crypto/secp256k1

密碼學標準

  1. Bertoni, G., Daemen, J., Peeters, M., & Van Assche, G. (2011). The Keccak SHA-3 submission. NIST. https://keccak.team/
  1. Johnson, D., Menezes, A., & Vanstone, S. (2001). The elliptic curve digital signature algorithm (ECDSA). International Journal of Information Security. https://doi.org/10.1007/s102070100002
  1. Hankerson, D., Menezes, A., & Vanstone, S. (2006). Guide to Elliptic Curve Cryptography. Springer. ISBN: 978-0387227546

EIP 規格

  1. Buterin, V. (2019). EIP-1559: Fee market change for ETH 1.0 chain. https://eips.ethereum.org/EIPS/eip-1559
  1. Weisse, O. (2020). EIP-2929: Gas cost increases for state access opcodes. https://eips.ethereum.org/EIPS/eip-2929
  1. Awalas, S. (2023). EIP-3855: PUSH0 instruction. https://eips.ethereum.org/EIPS/eip-3855

技術規格

  1. Ethereum Foundation. (2024). Ethereum Yellow Paper. https://ethereum.github.io/yellowpaper/paper.pdf
  1. Ethereum Foundation. (2024). EVM Illustrated. https://ethereum.org/developers/docs/evm
  1. Solidity Documentation. (2024). Layout of a Contract. https://docs.soliditylang.org/

標籤

ethereum, evm, go-ethereum, source-code, cryptography, keccak, ecdsa, secp256k1, bn256, gas, eip-1559, smart-contract, security, mathematical-derivation, stack, memory, call, create

難度

advanced

相關文章

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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