EVM Opcode 完整指南:從底層理解以太坊虛擬機
提供完整的以太坊虛擬機(EVM)操作碼參考手冊,涵蓋所有 Opcode 的功能、Gas 消耗計算、儲存與記憶體操作、以及Opcode 層級的安全漏洞分析,幫助開發者深入理解智慧合約底層運作。
EVM Opcode 完整指南:從底層理解以太坊虛擬機
概述
以太坊虛擬機(Ethereum Virtual Machine,EVM)是以太坊智慧合約的執行環境,其核心是一個基于堆疊的(stack-based)圖靈完整虛擬機。理解 EVM Opcode(操作碼)不僅是智慧合約優化的基礎,更是識別安全漏洞、診斷交易失敗原因的關鍵技能。本文提供完整的 Opcode 參考指南,涵蓋每個操作碼的功能、Gas 消耗計算、以及實際應用場景。
對於希望深入理解以太坊底層運作的開發者而言,掌握 Opcode 知識能夠顯著提升合約設計能力與安全審計水平。我們將從基礎概念出發,逐步深入到進階主題,包括Gas優化技巧與常見攻擊向量分析。
一、EVM 基礎架構
1.1 執行環境概述
EVM 是一個隔離的執行環境,每次智慧合約調用都在一個全新的虛擬機實例中進行。這種設計確保了確任性(determinism)——相同的輸入必然產生相同的輸出。
核心組件
- 堆疊(Stack):最大深度 1024 個 256 位元項目
- 記憶體(Memory):可位元組定址的暫時儲存空間
- 儲存(Storage):永久性的鍵值對資料庫
- 調用資料(Calldata):交易的輸入資料
執行模型
EVM 採用 Von Neumann 架構,程式碼與資料共存於同一記憶體空間。執行流程如下:
- 載入位元組碼(Bytecode)到記憶體
- 讀取並解碼當前 PC(Program Counter)指向的 Opcode
- 從堆疊彈出所需參數
- 執行對應操作
- 將結果推回堆疊
- PC 遞增或跳轉
1.2 位元組碼結構
智慧合約編譯後生成 EVM 位元組碼,其結構如下:
0x6080604052348015610010575b5b610100818163518c6001600160401b039290811682900b816020015b600080fd5b506004361003d557fe6
|_____| |_________| |______________________________________________________________|
PUSH1 操作碼 參數(緊跟在 PUSH 操作碼後)
實際範例:Simple Storage 合約
// SimpleStorage.sol
contract SimpleStorage {
uint256 public storedData;
function set(uint256 x) public {
storedData = x;
}
function get() public view returns (uint256) {
return storedData;
}
}
編譯後的位元組碼:
608060405234801561000f575b5b610100818163518c6001600160401b039290811682900b60205260205260406020a0600067
讓我們逐步分析這段位元組碼:
| 位址 | Opcode | 說明 |
|---|---|---|
| 0x00 | PUSH1 0x80 | 將 0x80 推入堆疊 |
| 0x02 | PUSH1 0x40 | 將 0x40 推入堆疊 |
| 0x04 | MSTORE | 將 0x80 存到 memory[0x40:0x60] |
| 0x05 | PUSH1 0x04 | 載入 jumpdest |
| ... | ... | 繼續解碼 |
1.3 堆疊操作模型
EVM 採用大端序(Big-Endian)表示法,最重要的位元組優先存放。堆疊操作遵循 LIFO(Last In, First Out)原則:
堆疊指令示例
// 堆疊狀態追蹤
PUSH1 0x20 // 堆疊: [0x20]
PUSH1 0x10 // 堆疊: [0x20, 0x10]
ADD // 彈出 0x10, 0x20; 推入 0x30 // 堆疊: [0x30]
PUSH1 0x05 // 堆疊: [0x30, 0x05]
MUL // 彈出 0x05, 0x30; 推入 0xF0 // 堆疊: [0xF0]
二、完整 Opcode 參考手冊
2.1 停止與運算操作碼(0x00-0x0F)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 |
|---|---|---|---|---|---|
| 0x00 | STOP | - | - | 0 | 停止執行,不消耗 Gas |
| 0x01 | ADD | a, b | a + b | 3 | 整數加法,溢位則回捲 |
| 0x02 | MUL | a, b | a × b | 5 | 整數乘法,溢位則回捲 |
| 0x03 | SUB | a, b | a - b | 3 | 整數減法 |
| 0x04 | DIV | a, b | a / b | 5 | 整數除法,除以 0 回 0 |
| 0x05 | SDIV | a, b | a / b | 5 | 有符號除法 |
| 0x06 | MOD | a, b | a % b | 5 | 取餘運算 |
| 0x07 | SMOD | a, b | a % b | 5 | 有符號取餘 |
| 0x08 | ADDMOD | a, b, n | (a + b) % n | 8 | 先加後取餘 |
| 0x09 | MULMOD | a, b, n | (a × b) % n | 8 | 先乘後取餘 |
| 0x0A | EXP | a, b | aᵇ | 10* | 指數運算 |
| 0x0B | SIGNEXTEND | b, x | 符號擴展 | 5 | 擴展位元組長度 |
EXP 操作碼 Gas 計算
EXP 的動態 Gas 較為複雜:
Gas = 10 + (exponent_byte_size × gas_per_byte)
其中 exponent_byte_size = ceil(log2(exponent) / 8)
範例:
// EXP Gas 計算
2²⁵⁵ // 指數位元組大小 = 32, Gas = 10 + 32 × 50 = 1610
2¹ // 指數位元組大小 = 1, Gas = 10 + 1 × 50 = 60
2.2 比較與分支操作碼(0x10-0x1F)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 | |
|---|---|---|---|---|---|---|
| 0x10 | LT | a, b | 1 if a < b else 0 | 3 | 無符號小於 | |
| 0x11 | GT | a, b | 1 if a > b else 0 | 3 | 無符號大於 | |
| 0x12 | SLT | a, b | 1 if a < b else 0 | 3 | 有符號小於 | |
| 0x13 | SGT | a, b | 1 if a > b else 0 | 3 | 有符號大於 | |
| 0x14 | EQ | a, b | 1 if a == b else 0 | 3 | 相等比較 | |
| 0x15 | ISZERO | a | 1 if a == 0 else 0 | 3 | 邏輯非 | |
| 0x16 | AND | a, b | a & b | 3 | 位元 AND | |
| 0x17 | OR | a, b | a | b | 3 | 位元 OR |
| 0x18 | XOR | a, b | a ^ b | 3 | 位元 XOR | |
| 0x19 | NOT | a | ~a | 3 | 位元 NOT | |
| 0x1A | BYTE | i, x | x[i] | 3 | 獲取位元組 | |
| 0x1B | SHL | shift, value | value << shift | 3 | 左移(Constantinople) | |
| 0x1C | SHR | shift, value | value >> shift | 3 | 右移(邏輯) | |
| 0x1D | SAR | shift, value | value >> shift | 3 | 右移(算術) |
有符號 vs 無符號比較
// 範例:有符號與無符號的差異
uint256 a = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
uint256 b = 1;
// 無符號比較:a > b(因為 a = 2^256 - 1)
// 有符號比較:a < b(因為 a = -1 in two's complement)
2.3 密碼學操作碼(0x20-0x2F)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 |
|---|---|---|---|---|---|
| 0x20 | SHA3 | offset, length | keccak256(memory[offset:offset+length]) | 30 + 6 × words | Keccak-256 雜湊 |
| 0x21 | SHA256 | offset, length | sha256(...) | 60 + 12 × words | SHA-256(未啟用) |
| 0x22 | RIPEMD160 | offset, length | ripemd160(...) | 600 + 120 × words | RIPEMD-160(未啟用) |
| 0x23 | ECRECOVER | v, r, s, hash | 回復公鑰位址或 0 | 3000 | 橢圓曲線恢復 |
SHA3(Keccak-256)Gas 計算
Gas = 30 + 6 × ceil(length / 32)
範例:
// "Hello" 的長度為 5 位元組
Gas = 30 + 6 × ceil(5/32) = 30 + 6 × 1 = 36
// "Hello World" 的長度為 11 位元組
Gas = 30 + 6 × ceil(11/32) = 30 + 6 × 1 = 36
2.4 環境資訊操作碼(0x30-0x3F)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 |
|---|---|---|---|---|---|
| 0x30 | ADDRESS | - | 合約位址 | 2 | 當前合約位址 |
| 0x31 | BALANCE | address | address.balance | 2600* | 帳戶餘額 |
| 0x32 | ORIGIN | - | 交易發起者 | 2 | EOA 位址 |
| 0x33 | CALLER | - | msg.sender | 2 | 調用者位址 |
| 0x34 | CALLVALUE | - | msg.value | 2 | 交易金額(wei) |
| 0x35 | CALLDATALOAD | i | msg.data[i:i+32] | 3 | 載入 calldata |
| 0x36 | CALLDATASIZE | - | msg.data.length | 2 | calldata 長度 |
| 0x37 | CALLDATACOPY | dest, offset, length | - | 3 + 3 × words | 複製 calldata |
| 0x38 | CODESIZE | - | 當前合約代碼長度 | 2 | - |
| 0x39 | CODECOPY | dest, offset, length | - | 3 + 3 × words | 複製合約代碼 |
| 0x3A | GASPRICE | - | tx.gasprice | 2 | 交易 Gas 價格 |
| 0x3B | EXTCODESIZE | address | 合約代碼長度 | 2600* | - |
| 0x3C | EXTCODECOPY | addr,dest,o,l | - | 2600* + 3 × words | - |
| 0x3D | RETURNDATASIZE | - | 上次調用的回傳資料長度 | 2 | - |
| 0x3E | RETURNDATACOPY | d,o,l | - | 3 + 3 × words | - |
| 0x3F | EXTCODEHASH | address | keccak256(code) | 2600* | - |
- 這些 Opcode 在 cold access 時消耗 2600 Gas,warm access 時消耗 100 Gas。
2.5 區塊資訊操作碼(0x40-0x4F)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 |
|---|---|---|---|---|---|
| 0x40 | BLOCKHASH | blockNumber | block.blockhash(n) | 20* | 區塊雜湊 |
| 0x41 | COINBASE | - | block.coinbase | 2 | 礦工位址 |
| 0x42 | TIMESTAMP | - | block.timestamp | 2 | 區塊時間戳 |
| 0x43 | NUMBER | - | block.number | 2 | 區塊編號 |
| 0x44 | DIFFICULTY | - | block.difficulty | 2 | 區塊難度 |
| 0x45 | GASLIMIT | - | block.gaslimit | 2 | Gas 上限 |
| 0x46 | CHAINID | - | chain.id | 2 | 鏈 ID |
| 0x47 | SELFBALANCE | - | address(this).balance | 5 | 合約餘額(更便宜) |
- BLOCKHASH 快取命中率會影響實際 Gas。
2.6 堆疊、記憶體、儲存操作碼(0x50-0x5F)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 |
|---|---|---|---|---|---|
| 0x50 | POP | a | - | 2 | 彈出堆疊頂 |
| 0x51 | MLOAD | offset | memory[offset:offset+32] | 3 + 3 × words | 載入記憶體 |
| 0x52 | MSTORE | offset, value | - | 3 + 3 × words | 儲存記憶體 |
| 0x53 | MSTORE8 | offset, value | - | 3 | 儲存單一位元組 |
| 0x54 | SLOAD | key | storage[key] | 2100* | 載入儲存 |
| 0x55 | SSTORE | key, value | - | 2900 / 20000 | 儲存值 |
| 0x56 | JUMP | dest | - | 8 | 無條件跳轉 |
| 0x57 | JUMPI | dest, cond | - | 10 | 條件跳轉 |
| 0x58 | PC | - | pc | 2 | 程式計數器 |
| 0x59 | MSIZE | - | 記憶體大小 | 2 | 已使用記憶體 |
| 0x5A | GAS | - | remaining gas | 2 | 剩餘 Gas |
| 0x5B | JUMPDEST | - | - | 1 | 跳轉目標標記 |
- SLOAD/SSTORE 的 Cold vs Warm 訪問消耗不同。
2.7 調用操作碼(0xF0-0xFF)
| Opcode | 名稱 | 堆疊輸入 | 堆疊輸出 | Gas | 說明 |
|---|---|---|---|---|---|
| 0xF0 | CREATE | value, offset, length | newAddress | 32000 | 部署新合約 |
| 0xF1 | CALL | gas, addr, value, argsOffset, argsLen, retOffset, retLen | success | 100* + 動態 | 調用其他合約 |
| 0xF2 | CALLCODE | gas, addr, value, argsOffset, argsLen, retOffset, retLen | success | 100* + 動態 | 同上下文調用 |
| 0xF3 | RETURN | offset, length | - | 0 | 返回資料 |
| 0xF4 | DELEGATECALL | gas, addr, argsOffset, argsLen, retOffset, retLen | success | 100* + 動態 | 委託調用 |
| 0xFA | STATICCALL | gas, addr, argsOffset, argsLen, retOffset, retLen | success | 100* + 靜態 | 靜態調用 |
| 0xFD | REVERT | offset, length | - | 0 | 回滾交易 |
| 0xFE | INVALID | - | - | 0 | 無效指令 |
| 0xFF | SELFDESTRUCT | beneficiary | - | 30000 | 自毀合約 |
三、Gas 消耗深度分析
3.1 靜態 vs 動態 Gas
EVM 的 Gas 消耗分為兩大類:
靜態 Gas(固定)
這些操作的 Gas 消耗是固定的,與執行環境無關:
| 操作類型 | Gas 消耗 |
|---|---|
| 基本算術(ADD, SUB, etc.) | 3-5 |
| 堆疊操作(PUSH, POP, DUP, SWAP) | 3 |
| 邏輯運算(AND, OR, XOR) | 3 |
| 環境查詢(ADDRESS, CALLER) | 2 |
動態 Gas(變動)
這些操作的 Gas 消耗取決於執行時的狀態:
總 Gas = 靜態 Gas + (記憶體words × memoryGas) + 額外操作 Gas
3.2 記憶體 Gas 模型
記憶體擴展採用線性定價,遵循以下規則:
記憶體擴展成本
memoryGas = ceil(newSize / 32) - ceil(oldSize / 32)
cost = memoryGas × memoryGas / 512 + (3 × memoryGas)
簡化理解
| 記憶體範圍 | 額外 Gas |
|---|---|
| 0-0.2 KB(0-64 bytes) | ~3 |
| 0.2-1 KB(64-352 bytes) | ~24 |
| 1-2 KB | ~90 |
| 每增加 1 倍 | 成本增加 ~3 倍 |
實際範例
// 場景 1:簡單函數
function simple() external {
uint256 a = 1; // 記憶體不擴展
}
// Gas ≈ 21000 (交易成本)
// 場景 2:記憶體陣列
function withArray(uint256 n) external {
uint256[] memory arr = new uint256[](n); // 擴展記憶體
arr[0] = 42;
}
// 當 n=10: memoryGas = ceil(320/32) - ceil(0/32) = 10
// memoryCost = 10×10/512 + 3×10 ≈ 30
// 總成本大幅增加
3.3 儲存操作 Gas 詳解
儲存(Storage)是 EVM 中最昂貴的操作。
Gas 計算規則
| 操作 | Cold Access | Warm Access |
|---|---|---|
| SLOAD | 2100 | 100 |
| SSTORE (set 0→非0) | 20000 | 2900 |
| SSTORE (非0→非0) | 2900 | 2900 |
| SSTORE (非0→0) | 5000 | 5000 |
冷存取定義
「Cold Access」指的是在當前交易中首次訪問某個存儲槽。Warm Access 指的是在當前交易中已訪問過的存儲槽。
實務建議
- 批量更新:減少 SSTORE 次數
// 不佳做法:多次 SSTORE
function updateMultipleBad(uint256[] calldata values) external {
for (uint256 i = 0; i < values.length; i++) {
data[i] = values[i]; // 每次都觸發 SSTORE
}
}
// 較佳做法:使用 scratch space
function updateMultipleBetter(uint256[] calldata values) external {
assembly {
// 在 memory 中組裝資料,一次性寫入
}
}
- 刪除恢復:使用 DELETE 而非設為 0
// DELETE 實際上會退還 Gas
function cleanup() external {
delete data; // 退還約 15000 Gas(如果原本非0)
}
3.4 調用操作 Gas 計算
CALL 系列操作的 Gas 消耗最為複雜:
CALL Gas = 100 (cold) / 100 (warm) + 子調用消耗 + 額外
子調用 Gas 轉遞
// 完整 Gas 轉遞語法
success := call(gas, addr, value, argsOffset, argsLen, retOffset, retLen)
// 限制子調用 Gas
success := call(gas / 2, addr, value, argsOffset, argsLen, retOffset, retLen)
DELEGATECALL 風險
// 典型的代理模式漏洞
function proxyCall(address impl, bytes calldata data) external {
// 攻擊者可能設置極低的 gas 導致目標合約失敗但代理合約成功
(bool success, ) = impl.delegatecall(data);
require(success);
}
3.5 Gas 優化實務技巧
1. 短路運算
// 不佳:總是執行兩個條件
if (condition1 && condition2) { ... }
// 較佳:短路運算
if (condition1) {
if (condition2) { ... }
}
2. 避免不必要狀態變數
// 浪費 Gas
function computeAndStore(uint256 a, uint256 b) external returns (uint256) {
result = a * b; // 寫入存儲
return result;
}
// 較佳:記憶體計算
function computeOnly(uint256 a, uint256 b) external pure returns (uint256) {
return a * b; // 記憶體中計算
}
3. 變數打包
// 未優化:兩個存儲槽
struct Unpacked {
uint128 a; // 存儲槽 0
uint128 b; // 存儲槽 1
}
// 優化後:一個存儲槽
struct Packed {
uint128 a;
uint128 b;
}
4. 使用 Custom Error
// 較舊的做法:require 字串
require(owner == msg.sender, "Not authorized"); // 字串儲存在 bytecode 中
// 較新的做法:custom error
error NotAuthorized();
// Error selector: 0x08214c63
require(owner == msg.sender, NotAuthorized.selector);
四、Opcode 層級攻擊向量分析
4.1 整數溢位攻擊
經典案例:The DAO 事件
// 有漏洞的代碼(2016)
function splitDAO(
DAOInterface dao,
uint256 _proposalID,
address _newCurator
) noEther {
// 計算回報
uint256 rewardAccount = balanceOf[msg.sender]; // 可能溢位
// ...
balanceOf[msg.sender] = 0; // 沒有檢查
}
防禦方法
// SafeMath 庫
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
return a - b;
}
// Solidity 0.8+ 內建檢查
uint256 result = a - b; // 自動 revert if b > a
4.2 重入攻擊
攻擊模式
// 有漏洞的合約
mapping(address => uint256) public balances;
function withdraw() external {
uint256 balance = balances[msg.sender];
require(balance > 0);
// 轉帳在狀態更新前
(bool success, ) = msg.sender.call{value: balance}("");
require(success);
balances[msg.sender] = 0; // 太晚了!
}
// 攻擊合約
function receive() external payable {
if (address(victim).balance > 0) {
victim.withdraw(); // 遞迴調用
}
}
防禦模式
// 檢查-生效-互動模式
function withdraw() external {
uint256 balance = balances[msg.sender];
require(balance > 0, "No balance");
// 1. 狀態更新先於轉帳
balances[msg.sender] = 0;
// 2. 轉帳在最後
(bool success, ) = msg.sender.call{value: balance}("");
require(success);
}
// 或使用 ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
4.3 操縱存儲槽攻擊
Slot 讀取順序問題
// 假設攻擊者可控制地址
mapping(address => uint256) public userBalances;
function getBalance() external view returns (uint256) {
address user = msg.sender;
return userBalances[user]; // 如果 user = 存儲槽位置?
}
// 攻擊:部署合約使 slot 0 映射到 userBalances
防禦
// 明確指定存儲位置
mapping(address => uint256) public userBalances;
// 使用 assembly 避免編譯器優化干擾
4.4 操縱 Block 參數
Timestamp 操控
// 典型漏洞
function distributeRewards() external {
require(block.timestamp - lastDistribution >= 1 days);
// 獎勵發放
}
// 攻擊:礦工可以選擇 block.timestamp(在一定範圍內)
防禦
// 使用多個區塊確認
function distributeRewards() external {
require(block.timestamp - lastDistribution >= 1 days);
require(blockhash(block.number - 1) != bytes32(0)); // 確認區塊已最終確認
}
五、Debug 工具與實務應用
5.1 使用 evm-dafny 追蹤執行
指令追蹤範例
原始交易:
to: 0xABC...DEF
data: 0xa9059cbb000000000000000000000000[dest]0000000000000000000000000000000000000a
解碼:
0xa9059cbb = transfer(address,uint256)
0x0000...00dest = 目標地址
0x0000...000a = 數量 (10)
5.2 常見交易失敗診斷
Out of Gas
Gas 消耗分析:
CALL opcode: 21000 (base) + 70000 (contract execution) = 91000
可用 Gas: 80000
結果: REVERT - Out of Gas
Stack Underflow
錯誤代碼: 0x11 (JUMP 目標不是 JUMPDEST)
原因: 嘗試跳轉到資料區域
5.3 Gas 優化實戰
範例:優化的 ERC-20 Transfer
// 未優化版本
function transfer(address to, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
// 優化版本
function transfer(address to, uint256 amount) public returns (bool) {
assembly {
// 單一 SLOAD, 單一 SSTORE
let slot := balances.slot
let senderSlot := keccak256(abi.encode(msg.sender, slot))
let balance := sload(senderSlot)
if lt(balance, amount) {
mstore(0x00, 0xcdctedb3) // Error: InsufficientBalance
revert(0x00, 0x04)
}
sstore(senderSlot, sub(balance, amount))
}
// ... emit event
}
六、Opcode 與合約安全審計清單
6.1 必檢查項目
- 重入防護:所有資金相關函數
- 整數溢位:所有算術運算
- 存取控制:所有權限函數
- 輸入驗證:所有外部輸入
6.2 Opcode 級別審計要點
- [ ] 避免 SELFDESTRUCT 在代理模式中
- [ ] STATICCALL 正確使用
- [ ] DELEGATECALL 目標合約信任假設
- [ ] CALL Gas 傳遞安全邊際
- [ ] EXTCODESIZE 檢查目標為合約
6.3 Gas 極限建議
| 操作類型 | 建議 Gas 限制 |
|---|---|
| 簡單轉帳 | 21000 |
| ERC-20 transfer | 50000 |
| 標準合約調用 | 100000 |
| 複雜 DeFi 操作 | 200000+ |
結論
理解 EVM Opcode 是智慧合約開發的進階技能。透過掌握這些底層操作碼,開發者能夠:
- 編寫更高效的合約代碼
- 識別並防止潛在安全漏洞
- 診斷交易失敗的根本原因
- 進行更深度的安全審計
建議讀者在本地測試環境中使用 Remix 或 Hardhat 的除錯功能,逐步追蹤每個 Opcode 的執行,理解堆疊、記憶體、儲存的狀態變化。這種「透視」能力將顯著提升智慧合約開發的專業水平。
延伸閱讀
相關文章
- 以太幣手續費市場基礎 — 理解 gas、priority fee 與交易打包行為。
- OpenZeppelin 智慧合約庫使用完整指南 — 詳細介紹 OpenZeppelin Contracts 的 ERC 代幣標準、存取控制與安全工具。
- 智慧合約測試方法論完整指南 — 系統介紹單元測試、整合測試、模糊測試與形式化驗證的智慧合約測試策略。
- EIP-1559 深度解析:以太坊費用市場的範式轉移 — 深入解析 EIP-1559 的費用結構、ETH 燃燒機制、經濟學意涵,以及對用戶、驗證者和生態系統的影響。
- Tornado Cash 事件分析與隱私協議教訓 — 深入分析 2022 年 OFAC 制裁事件、技術機制與對加密隱私領域的深遠影響。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!