以太坊 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)。
海綿結構定義:
海綿結構包含兩個階段:
- 吸收階段(Absorbing):輸入被分成固定大小的塊,與狀態進行 XOR 運算
- 擠出階段(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$$
曲線參數:
- 基點 $G$:生成元的座標
- $n$:基點的階(Curve order)
- $h$:餘因子(Cofactor)= 1
橢圓曲線加法:
曲線上兩點相加的幾何定義:
- 連接 $P$ 和 $Q$ 的直線
- 找到與曲線的第三個交點 $R$
- $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 簽章與驗證的數學推導
簽章生成:
- 選擇隨機數 $k$,$1 < k < n-1$
- 計算 $R = k \cdot G = (x1, y1)$
- 計算 $r = x_1 \pmod{n}$
- 計算 $s = k^{-1}(z + r \cdot d_A) \pmod{n}$
其中:
- $d_A$:私鑰
- $z$:消息哈希的前 $n$ 位
- $k^{-1}$:$k$ 模 $n$ 的乘法逆元
簽章驗證:
給定公鑰 $Q_A$、消息哈希 $z$、簽章 $(r, s)$,驗證過程:
- 驗證 $r, s$ 在 $[1, n-1]$ 範圍內
- 計算 $w = s^{-1} \pmod{n}$
- 計算 $u_1 = z \cdot w \pmod{n}$
- 計算 $u_2 = r \cdot w \pmod{n}$
- 計算 $P = u1 \cdot G + u2 \cdot QA = (x1, y_1)$
- 驗證 $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 成本 |
|---|---|---|
| 0x01 | ecrecover | 3,000 |
| 0x02 | sha256 | 60 + 12 × words |
| 0x03 | ripemd160 | 600 + 120 × words |
| 0x04 | dataCopy | 15 + 3 × words |
| 0x05 | BigModExp | 根據輸入大小計算 |
| 0x06 | bn256Add | 500 |
| 0x07 | bn256ScalarMul | 40000 |
| 0x08 | bn256Pairing | 100000 + 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:]$$
數學推導:
- 設 $I = initCode$
- 計算 $K = keccak256(I)$
- 計算 $A = keccak256(0xff || D || s || K)$
- 返回 $A[12:32]$(取後 20 位元組)
這個公式的密碼學安全性基於:
- Keccak-256 的抗碰撞性
- 攻擊者無法預測 $A$ 而不枚舉 $s$
五、安全性分析與最佳實踐
5.1 常見漏洞的數學理解
重入攻擊:
重入漏洞的數學本質是狀態更新順序的不一致。假設合約狀態轉換函數為 $S{new} = f(S{old}, T)$。
正常流程:
- $S1 = f{internal}(S_0, T)$ (先更新內部狀態)
- $S2 = f{external}(S_1, T)$ (後執行外部呼叫)
攻擊流程:
- $S1 = f{external}(S_0, T)$ (先執行外部呼叫)
- 在外部呼叫期間,合約狀態未更新
- 攻擊合約利用未更新狀態再次呼叫
- $S2 = f{internal}(S_0, T)$ (最終狀態不一致)
防範措施(Checks-Effects-Interactions):
- 先執行所有 Checks
- 更新所有 Effects(狀態)
- 最後執行 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)$$
潛在攻擊向量:
- 礦工操縱:若 $R$ 用於資金分配,礦工可通過選擇區塊內容影響 $R$
- 驗證者操縱(PoS):驗證者可以:
- 選擇不提議包含特定交易的區塊
- 影響區塊時間戳(±15 秒)
防範措施:使用 commit-reveal 方案或信標鏈隨機數。
六、結論
本文從 Go-Ethereum 原始碼分析與密碼學數學推導兩個維度,全面剖析了 EVM 的執行引擎架構與密碼學基礎。
主要內容包括:
- EVM 執行引擎:分析了核心資料結構、堆疊管理、記憶體管理與指令執行循環的原始碼實作。
- 密碼學原語:推導了 Keccak-256 海綿結構、secp256k1 橢圓曲線運算與 ECDSA 簽章驗證的數學基礎。
- Gas 機制:分析了 EIP-1559 費用計算、Base Fee 動態調整與區塊 Gas 限制的原始碼實作。
- 合約呼叫框架:深入分析了 CALL、DELEGATECALL、CREATE、CREATE2 等操作碼的原始碼實作。
- 安全性分析:從數學角度分析了常見漏洞的原理與防範措施。
這些深入的理解對於智慧合約開發者編寫更安全的合約、安全審計人員識別漏洞、以及研究者理解 EVM 的設計決策都具有重要價值。EVM 作為以太坊生態系統的核心組件,其設計體現了密碼學、分散式系統與軟體工程的巧妙結合。
參考來源
Go-Ethereum 原始碼
- go-ethereum Authors. (2024). EVM Interpreter Implementation. GitHub Repository. https://github.com/ethereum/go-ethereum/blob/master/core/vm/
- go-ethereum Authors. (2024). EVM Opcodes Implementation. GitHub Repository. https://github.com/ethereum/go-ethereum/blob/master/core/vm/instructions.go
- go-ethereum Authors. (2024). Elliptic Curve Cryptography. GitHub Repository. https://github.com/ethereum/go-ethereum/tree/master/crypto/secp256k1
密碼學標準
- Bertoni, G., Daemen, J., Peeters, M., & Van Assche, G. (2011). The Keccak SHA-3 submission. NIST. https://keccak.team/
- 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
- Hankerson, D., Menezes, A., & Vanstone, S. (2006). Guide to Elliptic Curve Cryptography. Springer. ISBN: 978-0387227546
EIP 規格
- Buterin, V. (2019). EIP-1559: Fee market change for ETH 1.0 chain. https://eips.ethereum.org/EIPS/eip-1559
- Weisse, O. (2020). EIP-2929: Gas cost increases for state access opcodes. https://eips.ethereum.org/EIPS/eip-2929
- Awalas, S. (2023). EIP-3855: PUSH0 instruction. https://eips.ethereum.org/EIPS/eip-3855
技術規格
- Ethereum Foundation. (2024). Ethereum Yellow Paper. https://ethereum.github.io/yellowpaper/paper.pdf
- Ethereum Foundation. (2024). EVM Illustrated. https://ethereum.org/developers/docs/evm
- 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
相關文章
- 以太坊 EVM 執行模型深度技術分析
- 以太坊核心協議完整深度分析
- 以太坊 Gas 費用歷史與市場分析
- 以太坊狀態管理與資料結構完整技術分析
相關文章
- 以太坊 EVM 執行模型深度技術分析:從位元組碼到共識層的完整解析 — 本文從底層架構視角深入剖析 EVM 的執行模型,涵蓋 opcode 指令集深度分析、記憶體隔離模型、Gas 消耗機制、呼叫框架、Casper FFG 數學推導、以及 EVM 版本演進與未來發展。我們提供完整的技術細節、位元組碼範例、效能瓶頸定量評估,幫助智慧合約開發者與區塊鏈研究者建立對 EVM 的系統性理解。
- 橢圓曲線離散對數問題:從代數幾何到密碼學安全的直覺解釋 — 橢圓曲線離散對數問題(ECDLP)是以太坊密碼學安全的數學基石。本文從直覺出發,逐步建立對ECDLP的完整理解,涵蓋群論基礎、橢圓曲線幾何、離散對數問題的定義與困難性、以及在以太坊中的實際應用場景。我們將深入分析為何256位金鑰能提供與4096位RSA相當的安全性,並探討量子計算對現有密碼系統的潛在威脅。這是理解以太坊底層密碼學安全性的必讀文章。
- 以太坊核心協議完整技術分析:從共識機制到狀態管理 — 本文提供一份全面且深入的以太坊核心協議技術分析,涵蓋共識機制、Casper FFG、LMD Ghost、EVM 架構、Gas 計算、狀態管理等技術層面。我們從密碼學基礎出發,逐步構建對以太坊整體架構的系統性理解,提供關鍵計算公式與數值推導,並深入分析 Layer 2 擴展方案和 MEV 基礎設施。截至 2026 年第一季度,以太坊網路質押總量超過 3,400 萬 ETH,驗證者數量突破 100 萬,本技術分析將幫助讀者理解這些數據背後的工程原理。
- 以太坊虛擬機(EVM)完整技術指南:從執行模型到狀態管理的系統性解析 — 本文提供 EVM 的系統性完整解析,涵蓋執行模型、指令集架構、記憶體管理、狀態儲存機制、Gas 計算模型,以及 2025-2026 年的最新升級動態。深入分析 EVM 的確定性執行原則、執行上下文結構、交易執行生命週期,並探討 EOF 和 Verkle Tree 等未來演進方向。
- 以太坊 Gas 市場機制深度分析:從 EIP-1559 經濟學到 MEV 影響的系統性研究 — 本文深入分析以太坊 Gas 市場的運作原理、價格發現機制、MEV 的影響、以及 2025-2026 年的市場演進趨勢。涵蓋 EIP-1559 費用市場結構、Base Fee 調整演算法、費用估算模型、Flashbots 拍賣機制,以及 Layer2 對主網 Gas 市場的影響。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案完整列表
- Solidity 文檔 智慧合約程式語言官方規格
- EVM 代碼庫 EVM 實作的核心參考
- Alethio EVM 分析 EVM 行為的正規驗證
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!