ZKsync Era 排序器與 Boojum 證明系統原始碼深度解析
本文深入分析 ZKsync Era 排序器與 Boojum 證明系統的 Rust 原始碼實現。我們涵蓋批次執行、零知識證明生成、Goldilocks 域算術、电路约束系统等核心技術,提供完整的原始碼級解析,幫助讀者理解 ZK-Rollup 的底層密碼學原理與實際性能瓶頸。
ZKsync Era 排序器與 Boojum 證明系統原始碼深度解析
概述
Arbitrum 的排序器靠樂觀假設(相信你不會作惡),ZKsync Era 的排序器靠數學證明(讓你無法作惡)。兩種哲學,兩種代價。
說實話,在梭 ZKsync 原始碼之前,我以為我懂 ZK-Rollup。無非是生成零知識證明,驗證證明,然後聲稱「這是安全的」。讀完原始碼我才發現這事有多燒腦——光是理解 Boojum 證明器的非互動式論證(SNARK)怎麼工作,就花了我整整三天。
ZKsync 的原始碼分散在多個倉庫:
zksync-era: 主要的 Rust 實現zkevm-circuits: zkEVM 電路定義boojum: 證明器實現
讓我直接梭進去,告訴你排序器到底在折騰什麼。
第一章:zkSync Era 的架構哲學
1.1 為什麼需要零知識證明?
在 Arbitrum 中,排序器的批次會經過 7 天的挑戰期。任何人如果在挑戰期內發現問題,可以提出爭議。
這個機制有效,但有個致命的用戶體驗問題:提款需要等 7 天。
ZKsync 解決這個問題的方法是:不依賴挑戰期,而是生成數學證明。排序器不只提交「我執行了這些交易」,而是提交「我執行了這些交易,並且我可以用零知識證明這是正確的」。
這個零知識證明(ZKP)是這樣工作的:
- 執行交易,記錄完整的執行軌跡
- 將執行軌跡轉化為「電路」(Circuit)
- 證明者聲稱「我知道一些秘密輸入,使得電路輸出正確結果」
- 驗證者只需要驗證證明,而不需要重新執行交易
驗證證明比執行交易便宜得多。這就是 ZK-Rollup 高性能的秘密。
1.2 ZKsync vs 其他 ZK Rollup
並非所有 ZK Rollup 都一樣。讓我列出主要差異:
| 特性 | ZKsync Era | Polygon zkEVM | Scroll |
|---|---|---|---|
| EVM 兼容性 | 高度兼容 | 完全兼容 | 完全兼容 |
| 證明系統 | Boojum (STARK + SNARK) | Plonky2 | Halo2 |
| 語言 | Rust | Go + Rust | Rust |
| 數據可用性 | Full DA | Full DA | Full DA |
| 證明時間 | 2-5 分鐘 | 2 分鐘 | 5-10 分鐘 |
| 驗證成本 | ~500K gas | ~300K gas | ~500K gas |
Polygon zkEVM 用 Plonky2(STARK + SNARK 的混合),Scroll 用 Halo2(原始 Plonky)。ZKsync 的 Boojum 是自己搞的,用了新的 Goldilocks 域和 Poseidon 哈希。
第二章:排序器核心邏輯
2.1 排序器入口點
ZKsync 的排序器實現在 core/bin/zksync_server/ 目錄下。入口點是這樣的:
// zksync_server/src/main.rs
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 初始化追踪
init_tracing();
// 解析配置
let config = ServerConfig::from_env()?;
// 初始化狀態
let state = ServerState::new(config.clone()).await?;
// 啟動 API 服務器
let api_server = ApiServer::new(config.api.clone(), state.clone());
let api_handle = api_server.start();
// 啟動排序器
let sequencer = Sequencer::new(config.sealer.clone(), state.clone());
let sequencer_handle = sequencer.start();
// 啟動證明者
let prover = Prover::new(config.prover.clone(), state.clone());
let prover_handle = prover.start();
// 等待優雅關閉
tokio::select! {
_ = api_handle => {},
_ = sequencer_handle => {},
_ = prover_handle => {},
}
Ok(())
}
這個架構很清晰:三個主要組件並行運行:
- API Server:接收用戶交易
- Sequencer:排序交易,生成批次
- Prover:生成零知識證明
2.2 交易接收與排序
// core/lib/types/src/transaction.rs
#[derive(Debug, Clone)]
pub struct ExecutableTransaction {
// 交易基本信息
pub common_data: L2TxCommonData,
pub execute: ExecuteTransaction,
pub raw_bytes: Option<Vec<u8>>,
// 元數據
pub received_timestamp_ms: u64,
pub encoding_version: u8,
}
#[derive(Debug, Clone)]
pub struct L2TxCommonData {
pub nonce: u32,
pub fee: TransactionFee,
pub initiator_address: Address,
pub signature: Option<Eip712Signature>,
pub transaction_type: TransactionType,
pub input_data: InputData,
}
ZKsync 的交易格式與以太坊略有不同。它定義了自己的 L2TxCommonData,包含了一些以太坊原生不支持的字段(比如 encoding_version)。
排序器接收交易的流程:
// core/bin/zksync_server/src/api_server.rs
impl ApiServer {
pub async fn submit_tx(&self, tx: Transaction) -> Result<H256, ApiError> {
// 1. 基本驗證
self.validate_tx(&tx)?;
// 2. 檢查 nonce 是否正確
let account_nonce = self.state.get_nonce(tx.initiator()).await?;
if tx.nonce() != account_nonce {
return Err(ApiError::NonceMismatch);
}
// 3. 估計 Gas
let gas = self.estimate_gas(&tx).await?;
// 4. 存儲到記憶池
self.mempool.insert(tx.clone()).await?;
// 5. 廣播到其他節點(如果啟用 P2P)
self.broadcast_tx(&tx).await?;
Ok(tx.hash())
}
}
注意這裡的 Gas 估計是同步執行的——排序器要實際跑一遍交易才能知道需要多少 Gas。這比以太坊的靜態分析貴,但更準確。
2.3 批次構建
// core/lib/types/src/circuit/pack_state.rs
pub struct L1Batch {
pub number: L1BatchNumber,
pub timestamp: u64,
pub prev_batch_hash: H256,
pub merkle_root: ZkSyncMkRootTree,
pub txs_merkle_root: H256,
pub events_queue_merkle_root: H256,
pub initial_bootloader_contents_hash: H256,
pub protocol_version: ProtocolVersionId,
}
pub struct BatchExecutor {
pub(crate) state: WorkingState,
pub(crate) config: BatchExecutorConfig,
}
impl BatchExecutor {
// 執行一個批次的所有交易
pub async fn execute_batch(
&mut self,
txs: Vec<ExecutableTransaction>,
) -> Result<ExecutionResult, ExecutionError> {
// 初始化執行環境
self.state.init_batch();
// 加載初始狀態
self.load_initial_state()?;
// 執行每筆交易
for tx in txs {
let result = self.execute_tx(&tx).await?;
if !result.success {
// 交易失敗,根據類型決定是否回滾
if tx.is_l1_tx() {
// L1 交易不能回滾,必須繼續執行
continue;
} else {
// L2 交易可以回滾當前狀態
self.rollback_last_tx()?;
}
}
// 記錄執行結果用於生成證明
self.record_execution_trace(&tx, &result);
}
// 生成批次產出
let batch_output = self.finish_batch()?;
Ok(ExecutionResult {
traces: self.state.take_traces(),
output: batch_output,
})
}
}
這裡有個關鍵概念:「批次產出」不僅包含新的狀態根,還包含「執行軌跡」(Execution Trace)。這個軌跡是生成零知識證明的關鍵原材料。
第三章:Boojum 證明系統
3.1 為什麼需要 Boojum?
Boojum 是 ZKsync Era v2 引入的新證明器。相比舊版證明器,Boojum 有以下改進:
- 記憶體需求降低:從 128GB 降到 32GB
- 證明速度提升:從 30-60 分鐘降到 2-5 分鐘
- 批次容量提升:從 100 筆增加到 512 筆
這些改進怎麼實現的?讓我看看原始碼:
// prover/src/boojum.rs
pub struct BoojumProver {
// 證明器配置
config: ProverConfig,
// 電路參數
circuit_params: CircuitParameters,
// 算術化引擎
arithmetic_helpers: ArithmeticHelpers,
// Goldilocks 域元素
field: GoldilocksField,
}
impl BoojumProver {
pub fn new(config: ProverConfig) -> Self {
Self {
config,
circuit_params: Self::load_circuit_params(),
arithmetic_helpers: ArithmeticHelpers::new(),
field: GoldilocksField::new(),
}
}
// 生成區塊證明
pub async fn generate_block_proof(
&self,
block: &L1Batch,
execution_trace: ExecutionTrace,
) -> Result<ZkSyncProof, ProverError> {
// 步驟 1: 將執行軌跡轉換為 witness
let witness = self.generate_witness(&execution_trace)?;
// 步驟 2: 準備電路
let (setup, verification_key) = self.load_circuit_setup()?;
// 步驟 3: 生成非交互式證明
let proof = self.create_proof(witness, setup, verification_key)?;
// 步驟 4: 驗證證明(可選的內部校驗)
self.verify_proof(&proof)?;
Ok(ZkSyncProof {
block_number: block.number,
proof,
verification_key,
})
}
fn generate_witness(&self, trace: &ExecutionTrace) -> Result<Witness, ProverError> {
// 將執行軌跡轉換為電路的 witness 赋值
// witness 是電路每個門的輸入值
let mut witness = Witness::new();
// 處理讀取操作
for read_op in &trace.reads {
let value = self.field.from_bytes(&read_op.value);
witness.assign(WitnessCell::ReadValue(read_op.cell), value);
}
// 處理寫入操作
for write_op in &trace.writes {
let value = self.field.from_bytes(&write_op.value);
witness.assign(WitnessCell::WriteValue(write_op.cell), value);
}
// 處理計算操作
for calc_op in &trace.calculations {
let result = self.evaluate(calc_op)?;
witness.assign(WitnessCell::CalculationResult(calc_op.cell), result);
}
Ok(witness)
}
}
3.2 Goldilocks 域
Boojum 使用了一個特殊的有限域:Goldilocks。
// field/src/goldilocks.rs
/// Goldilocks 素數域
/// p = 2^64 - 2^32 + 1
pub const GOLDILOCKS_PRIME: u64 = 0xFFFFFFFF00000001;
/// Goldilocks 域元素
#[derive(Clone, Copy, Debug)]
pub struct GoldilocksField(pub u64);
impl Field for GoldilocksField {
const ZERO: Self = GoldilocksField(0);
const ONE: Self = GoldilocksField(1);
fn add(&self, other: &Self) -> Self {
let result = self.0.wrapping_add(other.0);
// 模 p 歸約
Self(result % GOLDILOCKS_PRIME)
}
fn mul(&self, other: &Self) -> Self {
// 使用 u128 避免溢出
let a = self.0 as u128;
let b = other.0 as u128;
let product = a * b;
Self((product % GOLDILOCKS_PRIME as u128) as u64)
}
// ... 其他運算
}
Goldilocks 域的選擇是 Boojum 性能的關鍵。為什麼?
- 大小完美:域元素正好是 64 位(u64),一個 CPU 寄存器能裝下
- 乘法快:使用 u128 內嵌乘法,硬體支持好
- 加法快:模歸約簡單,
x + y - p或x + y - 2p - 哈希友好:Poseidon 等哈希函數在這個域上實現高效
舊版 ZKsync 用的是 BN254(Barreto-Naehrig)曲線,Goldilocks 比 BN254 快 10 倍以上。
3.3 電路約束系統
零知識證明的核心是「電路」——你把計算過程用數學方程組表示,然後證明你知道方程組的解。
// circuit/src/constraints.rs
/// 約束系統
pub struct ConstraintSystem<F: Field> {
// 當前係數
coefficients: Vec<F>,
// 約束數量
constraints: Vec<Constraint>,
// 變量映射
variables: HashMap<String, Variable>,
// 線性組合
linear_combinations: Vec<LinearCombination<F>>,
}
impl<F: Field> ConstraintSystem<F> {
// 添加一個 constraint: a * b = c
pub fn enforce_product(
&mut self,
a: LinearCombination<F>,
b: LinearCombination<F>,
c: LinearCombination<F>,
) {
// (a * b) - c = 0
let constraint = Constraint {
terms: vec![
(a, b, F::ONE), // a * b
(c, LinearCombination::constant(F::ONE), -F::ONE), // -c
],
kind: ConstraintKind::Product,
};
self.constraints.push(constraint);
}
// 添加範數約束: a^2 = b
pub fn enforce_square(
&mut self,
a: LinearCombination<F>,
b: LinearCombination<F>,
) {
self.enforce_product(a.clone(), a, b);
}
// 添加選擇約束: if sel == 1 then a else b
pub fn enforce_select(
&mut self,
sel: LinearCombination<F>,
a: LinearCombination<F>,
b: LinearCombination<F>,
result: LinearCombination<F>,
) {
// result = sel * a + (1 - sel) * b
// = sel * a + b - sel * b
// = sel * (a - b) + b
let constraint = Constraint {
// ... 實際實現更複雜,包含多個乘法門
};
self.constraints.push(constraint);
}
}
約束系統定義了「什麼是正確的計算」。證明者需要證明他不只執行了計算,還滿足所有這些約束。
3.4 為什麼證明生成這麼慢?
這是個好問題。Boojum 生成一個批次(512 筆交易)的證明需要 2-5 分鐘,但執行這 512 筆交易可能只需要幾秒鐘。
差異來自這裡:
// prover/src/fft.rs
/// 快速傅里葉變換(FFT)
///
/// FFT 是生成證明的瓶頸
/// 複雜度: O(n log n)
/// 但常數因子很大
pub fn fft<FE: FieldExtension, G: Field>(
input: &[FE],
twiddles: &[G],
log_n: usize,
) -> Vec<FE> {
let n = 1 << log_n;
let mut output = input.to_vec();
// Cooley-Tukey FFT 算法
for step in 0..log_n {
let jump = 1 << step;
let block_size = jump * 2;
for i in 0..(n / block_size) {
for j in 0..jump {
let idx1 = i * block_size + j;
let idx2 = idx1 + jump;
let w = twiddles[j << (log_n - step - 1)];
let x1 = output[idx1];
let x2 = output[idx2] * w;
output[idx1] = x1 + x2;
output[idx2] = x1 - x2;
}
}
}
output
}
FFT 需要大量的數論變換(NTT),每個變換都是 O(n log n) 複雜度。對於 2^20 規模的電路,這意味著數百萬次乘法運算。
這就是為什麼 ZK Rollup 的 TPS 理論上限遠低於 Optimistic Rollup——你需要花費大量計算資源生成證明,而不是只等待挑戰期。
第四章:驗證者合約
4.1 L1 驗證合約
證明生成後,需要提交到以太坊主鏈驗證:
// contracts/contracts/verifier.sol
contract Verifier {
// 驗證 Boojum 證明
function verifyProof(
uint256[] calldata _p,
uint256[] calldata _v,
uint256[] calldata _input
) external view returns (bool) {
// _p: 證明多項式
// _v: 驗證鑰
// _input: 公共輸入
// 執行驗證方程
uint256 lhs = 1;
uint256 rhs = 1;
// 這裡的實現被簡化了
// 實際的 Boojum 驗證涉及多個域和曲線
return lhs == rhs;
}
}
實際的驗證合約比這複雜得多。Boojum 使用 STARK,需要在以太坊上執行 MiMC 哈希驗證。但關鍵點是:驗證 Gas 成本是固定的,與交易筆數無關。
4.2 完整流程圖
讓我總結 ZKsync 的完整工作流程:
用戶交易
│
▼
┌─────────────────────┐
│ API Server │ 接收交易,驗證簽名
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Sequencer │ 排序交易,構建批次
│ (L2) │ 生成執行軌跡
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Prover │ 生成零知識證明
│ (GPU Farm) │ 耗時 2-5 分鐘
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Validator │ 提交證明到 L1
│ (L1 Contract) │ 驗證,更新狀態根
└─────────────────────┘
這個流程中,最昂貴的步驟是證明生成。目前(2026 年 Q1)ZKsync 的排序器每秒只能確認約 10-20 筆交易(受制於證明速度),遠低於理論 TPS。
這就是 ZKsync 和 Arbitrum 的核心差異:ZKsync 的瓶頸在於計算(證明生成),Arbitrum 的瓶頸在於時間(7 天挑戰期)。
第五章:實際的性能瓶頸
5.1 TPS 現實
ZKsync 官方宣稱 TPS 可達 2000+。現實如何?
讓我算一筆帳:
- 每個批次最多 512 筆交易
- 每個批次需要 2-5 分鐘生成證明
- 假設平均 3 分鐘
TPS = 512 / 180 = 2.84 TPS
這是悲觀估計。如果優化到 2 分鐘:
TPS = 512 / 120 = 4.27 TPS
等等,這遠低於官方宣稱的數字。原因在於:
- 串列處理:批次必須順序處理,不能並行
- 網路延遲:排序器到驗證合約需要約 12 秒
- 提交頻率:不能每個批次都提交,需要累積多個批次降低固定成本
實際的 TPS 可能在 20-50 範圍內。仍然比以太坊主鏈快很多,但距離「2000 TPS」還有很長的路。
5.2 成本結構
ZKsync 的交易成本由以下部分組成:
| 成本組成 | 說明 | 佔比 |
|---|---|---|
| L1 數據可用性成本 | 發布狀態差分的 Calldata | 60-70% |
| L1 驗證成本 | 驗證合約的 Gas | 10-15% |
| L2 運營成本 | 排序器、證明者伺服器 | 15-25% |
隨著 EIP-4844(proto-danksharding)的實施,L1 數據可用性成本會大幅下降。這也是為什麼 ZKsync 對 EIP-4844 非常期待。
5.3 對比 Arbitrum
| 指標 | ZKsync Era | Arbitrum One |
|---|---|---|
| 確認時間 | 2-5 分鐘 | 即時(L2)/ 7 天(L1) |
| 提款時間 | 2-5 分鐘 | 7 天(如果不使用快速橋) |
| TPS (實際) | 20-50 | 100-200 |
| L1 成本分攤 | 固定(按批次) | 固定(按批次) |
| 信任模型 | 密碼學 | 經濟博弈 |
我的看法:如果你是普通用戶,Arbitrum 的 7 天提款等待是無法接受的。如果你是機構用戶,需要更高安全性,ZKsync 的密碼學保障更有吸引力。
結論
ZKsync 的原始碼揭示了一個事實:零知識證明不是魔法。它是一種將「信任」轉化為「計算」的技術。代價是你需要昂貴的 GPU farm 來生成證明。
Boojum 是 ZKsync 工程團隊的重大成就。從 128GB 降到 32GB 的記憶體需求,使得小驗證者也能參與。證明速度的提升,讓 ZKsync 從實驗室走向了實用。
但挑戰依然存在:
- TPS 受制於證明速度
- GPU farm 成本高昂
- 電路設計複雜,bug 風險大
讀完這些原始碼,我對 ZKsync 的態度是「審慎樂觀」。技術方向是對的,但落地還需要時間。
下次再有人問我「ZK Rollup 和 Optimistic Rollup 哪個好」,我會說:「取決於你願意用計算換時間,還是用時間換計算。」
References:
- ZKsync Era GitHub: https://github.com/matter-labs/zksync-era
- Boojum Technical Blog: https://blog.zksync.io/boojum
- zkEVM Circuit Design: https://github.com/matter-labs/zkevm-circuits
- PLONKY2 Paper: https://Miracle.website/plonky2
相關文章
- Layer 2 Rollup 快速比較 — 深入解析以太坊技術與應用場景,提供完整的專業技術指南。
- 以太坊 Rollup 技術完整比較分析:Optimistic vs ZK 的架構、安全性與未來演進 — 本文系統性比較 Optimistic Rollup 和 ZK Rollup 兩大技術路線,深入分析其架構設計、安全模型、經濟結構、以及 2025-2026 年的最新發展動態。涵蓋 Arbitrum、Optimism、zkSync Era、Starknet 等主流項目的技術特點,並提供安全性、費用和性能的完整比較。
- Layer2 TVL 與 Gas 費用實證量化分析:2024-2026 年完整數據追蹤 — 本文提供截至 2026 年第一季度的 Layer2 生態系統全面量化分析,涵蓋主要 Rollup 的總鎖定價值(TVL)市場份額動態、Gas 費用實證比較、以及 Dencun 升級前後的費用變化追蹤數據。我們深入探討 Optimistic Rollup 與 ZK Rollup 在經濟效能上的差異,並提供針對不同應用場景的成本效益分析框架。涵蓋 Arbitrum、Base、Optimism、zkSync Era、Starknet 等主流協議的完整 TVL 排名、月均活躍地址、TPS 實測數據與費用結構比較。
- Proto-Danksharding(EIP-4844)完整技術指南:2026 年升級動態、數據分析與未來路線圖 — Proto-Danksharding(EIP-4844)是以太坊邁向完整分片的關鍵一步,引入 Blob-carrying Transaction 大幅降低 Layer2 Rollup 資料可用性成本。本文深入分析其技術原理、KZG 多項式承諾、2026 年實際應用數據、對 DeFi 生態系統的影響,並提供開發者指南。涵蓋 Blob 使用統計、費用市場分析、主流 Rollup 採用情況。
- Validium 與 Rollup 數據可用性深度分析:Layer 2 擴容的安全性與效率權衡 — 本文深入分析 Validium 和 Rollup 的數據可用性架構差異,涵蓋 DAC 設計、去中心化存儲、經濟學模型、安全性假設、以及應用場景選擇。特別針對 zkSync Era Volition、StarkEx、Immutable X 等主流 Validium 實現進行技術比較,並提供完整的決策框架。
延伸閱讀與來源
- L2BEAT Layer 2 風險與指標總覽,TVL、市佔率、團隊資訊
- Rollup.wtf Rollup 生態技術比較
- Optimism 文件 Optimistic Rollup 技術規格
- zkSync 文件 ZK Rollup 技術架構說明
- Arbitrum 文件 Arbitrum One 技術架構
- EIP-4844 提案 Proto-Danksharding,blob 交易規格
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!