HALO2 與 PLONK 家族零知識證明系統深度工程實作指南:折疊方案、遞迴證明與電路設計實務
HALO2 由 Electric Coin Company(Zcash 團隊)開發,其創新性地採用了「折疊方案」(Folding Scheme)來實現無需可信設置的遞迴證明組合。PLONK 及其衍生協議(UltraPLONK、TurboPLONK、Plonky2)則以其通用性和效率聞名。本文深入分析 HALO2 協議的核心原理、PLONK 家族的數學推導、以及實際電路設計的工程細節。涵蓋 IVC 增量可驗證計算、PLONK 置換論證與門約束系統、折疊方案數學推導、IPA 內積論證、HALO2 電路結構與約束定義、Rust HALO2 電路開發實務、PLONK 變體分析(UltraPLONK 查找表、Plonky2 Goldilocks 域優化)、以及 zkSync、Aztec 等項目的實際應用案例。同時探討電路安全性陷阱、驗證器安全最佳實踐與形式化驗證方法。
HALO2 與 PLONK 家族零知識證明系統深度工程實作指南:折疊方案、遞迴證明與電路設計實務
概述
零知識證明技術在區塊鏈領域的應用已經從理論走向大規模實際部署。在諸多零知識證明系統中,HALO2 和 PLONK 家族協議因其獨特的設計理念和優異的性能表現,正在成為 ZK-Rollup 和隱私計算的基石技術。
本文深入分析 HALO2 協議的核心原理、PLONK 家族的數學推導、以及實際電路設計的工程細節。HALO2 由 Electric Coin Company(Zcash 團隊)開發,其創新性地採用了「折疊方案」(Folding Scheme)來實現無需可信設置的遞迴證明組合。PLONK 及其衍生協議(UltraPLONK、TurboPLONK、Plonky2)則以其通用性和效率聞名。
截至 2026 年第一季度,HALO2 已被多個主流 ZK 項目採用,包括 Zcash 的 Orchard 方案、Polygon zkEVM 的部分實現、以及多個新興的隱私計算協議。PLONK 家族的變體則被 Aztec、zkSync 等項目廣泛使用。理解這些協議的底層原理和工程實現細節,對於開發高效、安全的零知識應用至關重要。
第一章:零知識證明基礎回顧與發展脈絡
1.1 從 NP 到零知識:計算複雜性視角
零知識證明的安全性根基於計算複雜性理論。要理解為何某些問題適合用零知識證明表述,首先需要回顧 NP 類語言的特性。
定義:語言 L 屬於 NP 類,當且僅當存在多項式時間的驗證器 V 和多項式 p,使得:
x ∈ L ⟺ ∃w (|w| ≤ p(|x|) ∧ V(x, w) = 1)
其中 w 稱為「見證」(Witness),V(x, w) 為驗證器。
零知識證明的核心思想是:證明者 P 可以讓驗證者 V 確信「存在這樣一個見證」,而無需透露見證本身的內容。
電路可滿足性(Circuit Satisfiability):
令 C 為一個布爾電路或算術電路。語言 SAT_C 定義為:
SAT_C = {⟨C, x⟩ : ∃w 使得 C(x, w) = 1}
任何 NP 語言都可以通過電路可滿足性來表述,這是大多數 ZK 系統的出發點。
1.2 零知識證明的分類
零知識證明系統分類:
┌─────────────────────────────────────────────────────────────────┐
│ 互動式 vs 非互動式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 互動式零知識證明 (IZK): │
│ • 需要多輪通信 │
│ • 可透過 Fiat-Shamir 啟發式轉換為非互動式 │
│ • 安全性分析較複雜 │
│ │
│ 非互動式零知識證明 (NIZK): │
│ • 只需單輪通信 │
│ • 可在區塊鏈上使用 │
│ • 需要隨機預言機或 CRS │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 論證系統特性 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ SNARK: │
│ • Succinct(簡潔):證明大小和驗證時間 sublinear │
│ • Non-interactive │
│ • ARgument:計算 Soundness(非完美 Soundness) │
│ • of Knowledge:知識 Soundness │
│ │
│ STARK: │
│ • Scalable:驗證時間對數級別 │
│ • Transparent:無需可信設置 │
│ • ARgument │
│ • of Knowledge │
│ │
│ Bulletproof: │
│ • 短證明:對數大小 │
│ • 無需可信設置 │
│ • 範圍證明友好 │
│ │
└─────────────────────────────────────────────────────────────────┘
1.3 以太坊生態中的 ZK 應用全景
以太坊 ZK 應用生態地圖:
┌─────────────────────────────────────────────────────────────────┐
│ Layer 2 ZK-Rollups │
├─────────────────────────────────────────────────────────────────┤
│ │
│ zkSync Era:Plonky2 (PLONK 變體) │
│ ├── zkEVM:支援 EVM 等效指令集 │
│ └── Boojum:新一代證明系統 │
│ │
│ Starknet:ZK-STARKs │
│ ├── Cairo:專用智慧合約語言 │
│ └── Stone Prover:高效證明生成 │
│ │
│ Polygon zkEVM: │
│ ├── Groth16 + 自定義優化 │
│ └── 多種證明系統並行 │
│ │
│ Scroll:Groth16 │
│ └── 與以太坊 EVM 高度相容 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 隱私保護協議 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Aztec:PLONK(Noir 語言) │
│ ├── 交易隱私 │
│ └── DeFi 隱私整合 │
│ │
│ Zcash:HALO2 + Orchard │
│ ├── 增量更新 │
│ └── 遞迴組合 │
│ │
└─────────────────────────────────────────────────────────────────┘
第二章:PLONK 協議深度數學推導
2.1 PLONK 的設計目標
PLONK(Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge)於 2019 年提出,其核心創新在於實現了「通用且可更新的信任設置」(Universal and Updatable Trusted Setup)。
PLONK 的三大特性:
- 通用性(Universality):只需一次可信設置,即可支持任意大小(上限內)的電路
- 可更新性(Updatability):任何人都可以參與更新儀式,降低單點信任風險
- 簡潔性(Succinctness):證明大小恆定,與電路規模無關
2.2 電路表示:QAP 到 PLONK 約束
PLONK 將計算表示為一組「門約束」和「連線約束」。
門約束(Gate Constraints):
PLONK 使用五元選擇器多項式來定義門類型。令 $qL, qR, qO, qM, q_C$ 為選擇器多項式,定義門約束:
q_L(x) · a(x) + q_R(x) · b(x) + q_O(x) · c(x) + q_M(x) · a(x) · b(x) + q_C(x) = 0
其中:
- $q_L$: 左輸入選擇器
- $q_R$: 右輸入選擇器
- $q_O$: 輸出選擇器
- $q_M$: 乘法選擇器
- $q_C$: 常數選擇器
選擇器編碼示例:
加法門(Addition Gate):
q_L = 1, q_R = 1, q_O = -1, q_M = 0, q_C = 0
約束:a + b - c = 0
乘法門(Multiplication Gate):
q_L = 1, q_R = 1, q_O = -1, q_M = 1, q_C = 0
約束:a · b - c = 0
常數門(Constant Gate):
q_L = 1, q_R = 0, q_O = -1, q_M = 0, q_C = -k
約束:k + a - c = 0
等於門(Equality Gate):
q_L = 1, q_R = 0, q_O = -1, q_M = 0, q_C = 0
約束:a - c = 0
PLONK 門電路表示:
┌─────────────────────────────────────────────────────────────────┐
│ PLONK 電路結構 │
│ │
│ 門(Gate): │
│ ┌─────────┐ │
│ │ • │──▶ c │
│ │ a b │ │
│ └─────────┘ │
│ │
│ 連線(Wiring): │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Wire: w₀ ──────▶ w₁ ──────▶ w₂ ──────▶ ... │ │
│ │ Gate₁ Gate₂ Gate₃ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 置換約束(Permutation): │
│ • 指定哪些 wire 應該相等 │
│ • 透過置換論證驗證 │
│ │
└─────────────────────────────────────────────────────────────────┘
2.3 置換論證(Permutation Argument)
置換論證是 PLONK 區別於傳統 R1CS 的關鍵創新。它允許任意連線模式,無需將電路表示為「標準形式」。
問題陳述:
令 $\mathbf{a}, \mathbf{b}$ 為兩個長度為 $n$ 的向量。證明者聲稱存在一個置換 $\sigma$,使得 $bi = a{\sigma(i)}$。
PLONK 使用「Grand Product」論證來高效地驗證置換關係:
數學推導:
定義多項式 $Z(x)$ 為:
$$Z(x) = \prod{i=1}^{n} (x - \omega^i) \cdot \prod{i=1}^{n} \frac{x - \beta \omega^i}{x - \gamma \omega^i}$$
其中 $\omega$ 為 $n$ 次單位根,$\beta, \gamma$ 為驗證者選擇的隨機挑戰。
展開後,$Z(\omega)$ 和 $Z(1)$ 的關係蘊含了置換關係。
Grand Product 約束:
定義
$$Z_0 = 1$$
$$Z{i+1} = Zi \cdot \frac{1 + \beta \cdot Si + \gamma}{1 + \beta \cdot Wi + \gamma}$$
其中 $Si$ 是 wire 值的某種編碼,$Wi$ 是目標 wire 值的編碼。
最終約束:
$$Z_n = 1$$
這個約束的成立當且僅當 $\{Wi\}$ 是 $\{Si\}$ 的置換。
2.4 完整協議流程
設置階段(Setup):
可信設置(KZG-style):
1. 選擇安全參數 κ 和最大電路規模 n
2. 選擇隨機值 τ(有毒廢料,必須銷毀)
3. 計算 Powers of Tau:
[τ⁰]G₁, [τ¹]G₁, ..., [τⁿ]G₁
[τ⁰]G₂, [τ¹]G₂, ..., [τᵏ]G₂
4. 發布 CRS(Common Reference String):
CRS = ( [τⁱ]G₁, [τⁱ]G₂ ) for i ∈ [0, n]
證明生成(Proof Generation):
# PLONK 證明生成偽代碼
def plonk_prove(crs, circuit, public_input, witness):
"""
輸入:
- crs: 公共參考字符串
- circuit: 電路定義(門約束 + 置換約束)
- public_input: 公開輸入
- witness: 私密見證
輸出:PLONK 證明
"""
n = circuit.num_wires
# 步驟 1:計算 witness 分配
assignments = compute_witness_assignments(
circuit, public_input, witness
)
# 將 wire 值表示為拉格朗日插值多項式
a_poly = interpolate(assignments.a_wires)
b_poly = interpolate(assignments.b_wires)
c_poly = interpolate(assignments.c_wires)
# 步驟 2:承諾 wire 多項式
A_commit = commit(crs, a_poly)
B_commit = commit(crs, b_poly)
C_commit = commit(crs, c_poly)
# 步驟 3:驗證者挑戰
beta = hash(A_commit || B_commit || C_commit)
gamma = hash(beta || ...)
# 步驟 4:置換論證
permutation_quotient = compute_permutation_poly(
assignments, circuit.permutation, beta, gamma
)
Z_commit = commit(crs, permutation_quotient)
# 步驟 5:計算商多項式
quotient_poly = compute_quotient_poly(
circuit.gates,
a_poly, b_poly, c_poly,
permutation_quotient,
beta, gamma
)
QT_commit = commit(crs, quotient_poly)
# 步驟 6:隨機 opening
zeta = hash(QT_commit || ...)
# 步驟 7:批量 opening 證明
openings = batch_open(
crs,
[a_poly, b_poly, c_poly, permutation_quotient, quotient_poly],
zeta
)
return PLONKProof(
A=A_commit,
B=B_commit,
C=C_commit,
Z=Z_commit,
QT=QT_commit,
openings=openings,
zeta=zeta
)
驗證過程(Verification):
def plonk_verify(crs, proof, public_input):
"""
PLONK 驗證
時間複雜度:O(log n) 用於配對檢查
空間複雜度:O(1) 證明大小
"""
# 1. 重新計算挑戰
beta = hash(proof.A || proof.B || proof.C)
gamma = hash(beta || ...)
alpha = hash(beta || gamma || ...)
zeta = hash(proof.QT || ...)
# 2. 驗證 Opening 承諾
batch_openings = verify_batch_openings(
crs, proof.openings, zeta
)
assert batch_openings == VALID
# 3. 驗證門約束
gate_check = verify_gate_constraints(
crs, proof, zeta, alpha
)
assert gate_check == VALID
# 4. 驗證置換約束
perm_check = verify_permutation(
crs, proof, zeta, beta, gamma
)
assert perm_check == VALID
# 5. 最終配對檢查
final_pairing_check(crs, proof, zeta, alpha, beta, gamma)
return ACCEPT
第三章:HALO2 協議核心原理
3.1 HALO2 的設計動機
HALO2 的設計動機源於對「遞迴證明組合」的追求。在 Zcash 的 Orchard 方案中,需要將多個零知識證明組合成一個,以實現增量更新和高效驗證。傳統方案(如 Groth16)需要漫長的可信設置儀式,且不支援真正的遞迴組合。
核心創新:HALO2 採用了「折疊方案」(Folding Scheme),允許將兩個「實例」(Instance)折疊成一個,無需產生新的證明。這使得遞迴證明組合可以在常數空間內完成。
3.2 IVC 增量可驗證計算
HALO2 基於 IVC(Incremental Verifiable Computation,增量可驗證計算)框架。
IVC 定義:
令 $F$ 為一個函數,$Z_i$ 為第 $i$ 步的輸出狀態。IVC 要求:
Z₀ = 輸入
Zᵢ₊₁ = F(Zᵢ, wᵢ)
存在 NPC(增量證明者)使得:
- NPC(CRS, (F, Zᵢ), πᵢ₋₁) → πᵢ
- NV(CRS, (F, Zᵢ), πᵢ) → {ACCEPT, REJECT}
其中 $\pii$ 是在第 $i$ 步的證明,聲明「存在序列 $w0, w1, ..., wi$ 使得狀態演化到 $Z_i$」。
IVC 的關鍵特性:
- 增量性:每一步都可以生成新證明
- 可驗證性:每個證明都可以被獨立驗證
- 遞迴性:可以在單次證明中驗證多個先前步驟
3.3 折疊方案數學推導
折疊的基本思想:
給定兩個「實例」(Instance),每個實例包含:
- 公開的電路約束
- 公開的實例變量(public IO)
- 私有的 witness 變量
折疊方案的目標是構造一個新的「折疊實例」,使得:
折疊實例可滿足 ⟺ 存在原實例₁ 和原實例₂ 的 witness
正式的折疊定義:
令 $R$ 為一個二元關係(電路可滿足性)。給定 $(u1, w1), (u2, w2) \in R$,折疊算法計算:
$$\text{Fold}(crs, (u1, w1), (u2, w2)) \rightarrow (u{fold}, \text{cm}1, \text{cm}_2)$$
使得:
- $u_{fold}$ 是新的實例變量
- $\text{cm}1, \text{vm}2$ 是對 $w1, w2$ 的承諾
- 驗證 $u_{fold}$ 的成本遠低於驗證原兩個實例
HALO2 的特殊折疊結構:
HALO2 採用了一種特殊的「雙曲線排列」(Elliptic Curve Pairing)結構,稱為「長承諾」(Long Commitment)和「短承諾」:
┌─────────────────────────────────────────────────────────────────┐
│ HALO2 折疊結構 │
│ │
│ 實例結構: │
│ Instance = (W₁, W₂, W₃, W₄, Q, C) │
│ │
│ 其中: │
│ • W₁, W₂, W₃, W₄:四個 witness 線 │
│ • Q:選擇器多項式 │
│ • C:常數 │
│ │
│ 約束: │
│ W₁ · W₂ · Q = W₃ + C │
│ │
│ 折疊約束: │
│ u₁ + α·u₂ = Fold₁ │
│ cm₁ + α·cm₂ = Fold_cm │
│ │
│ 其中 α 為隨機挑戰,u 為實例承諾 │
│ │
└─────────────────────────────────────────────────────────────────┘
3.4 折疊協議詳細流程
協議初始化:
HALO2 設置:
1. 選擇橢圓曲線 E(𝔽_p) 和標量域 𝔽_r
2. 選擇約束系統參數(電路定義)
3. 生成公開參數:
- 基點 G, H ∈ E(𝔽_p)
- 多項式承諾鑰匙(powers of tau)
4. 發布 (G, H, crs)
單輪折疊:
# HALO2 折疊算法
def halo2_fold(crs, instance1, instance2):
"""
HALO2 折疊方案
輸入:
- instance1 = (W₁¹, W₂¹, W₃¹, W₄¹, Q¹, C¹)
- instance2 = (W₁², W₂², W₃², W₄², Q², C²)
輸出:
- folded_instance
- commitments to original instances
"""
# 步驟 1:承諾原實例的 witness
T1_commit = PedersenCommit(W₁¹)
T2_commit = PedersenCommit(W₁²)
# 步驟 2:驗證者選擇隨機挑戰
r = hash(T1_commit || T2_commit)
# 步驟 3:計算折疊實例
W₁_fold = W₁¹ + r * W₁²
W₂_fold = W₂¹ + r * W₂²
W₃_fold = W₃¹ + r * W₃²
W₄_fold = W₄¹ + r * W₄²
# 步驟 4:折疊選擇器(加權平均)
Q_fold = Q¹ + r * Q²
C_fold = C¹ + r * C²
# 步驟 5:構造折疊實例承諾
u_fold = hash(W₁_fold || W₂_fold || W₃_fold || W₄_fold || Q_fold || C_fold)
return FoldedInstance(
W=(W₁_fold, W₂_fold, W₃_fold, W₄_fold),
Q=Q_fold,
C=C_fold,
u=u_fold,
T1=T1_commit,
T2=T2_commit,
r=r
)
遞迴驗證:
def halo2_recursive_prove(crs, steps, initial_state):
"""
HALO2 遞迴證明生成
使用折疊方案將多個計算步驟組合成單一證明
"""
current_instance = initial_state
for step in steps:
# 為當前步驟構造 witness
witness = compute_witness(step, current_instance)
# 構造這一步的「證明實例」
proof_instance = Instance(
W=(witness.W1, witness.W2, witness.W3, witness.W4),
Q=step.selectors,
C=step.constants
)
# 將當前實例和新實例折疊
current_instance = halo2_fold(
crs,
current_instance, # 前一步的「虛擬」實例
proof_instance # 當前步驟
)
# 最後一步:生成 SNARK 證明
final_proof = snark_prove(crs, current_instance)
return RecursiveProof(
folded_instances=..., # 折疊軌跡
final_proof=final_proof
)
3.5 為何 HALO2 不需要可信設置
這是 HALO2 最具創新性的特點。傳統 PLONK 需要 KZG 承諾的「powers of tau」可信設置,但 HALO2 使用了不同的承諾方案。
Inner Product Argument (IPA):
HALO2 使用基於內積論證的承諾方案,該方案:
- 僅需公共隨機字串(不需要信任任何單一參與者)
- 承諾大小為 O(log n)
- 驗證需要 O(log n) 次群運算
# IPA 承諾方案
def ipa_commit(gens, polynomial_coeffs):
"""
Inner Product Argument 承諾
承諾 C = <a, G>,其中:
- a 是多項式係數向量
- G 是生成元向量
"""
return inner_product(a, G)
def ipa_open(gens, a, point):
"""
證明多項式在點 point 的取值
"""
# 構造商多項式
a_shifted = shift_polynomial(a, point)
# 遞迴內積證明
proof = []
G_current = gens.G
a_current = a_shifted
while len(a_current) > 1:
# 隨機挑戰
r = hash(G_current || a_current)
# 折疊
G_left, G_right = split(G_current)
a_left, a_right = split(a_current)
# 構造子問題
proof.append((r, inner_product(a_left, G_right)))
a_current = a_left + r * a_right
G_current = G_left + r * G_right
return IPAProof(proof=proof, final_value=a_current[0])
第四章:PLONK 變體深度分析
4.1 UltraPLONK:通用化擴展
UltraPLONK 是 PLONK 的擴展版本,引入了更多「定制化門」(Custom Gates),大幅提升了特定運算的效率。
UltraPLONK 的額外門:
UltraPLONK 門集合:
1. 標準算術門(與 PLONK 相同)
q_L·a + q_R·b + q_O·c + q_M·a·b + q_C = 0
2. 查詢門(Lookup Gate)
支援電路內建的查找表
3. 旋轉門(Rotation Gate)
支援向量旋轉操作
4. 面積門(Area Gate)
支援區域計算
5. 橢圓曲線運算門
優化的 ECDSA/BN254 運算
查找表(Lookup Table):
UltraPLONK 最重要的創新之一是內建了查找表支援。查找表可以預計算某些昂貴運算的結果,在電路中直接查表使用。
// UltraPLONK 查找表示例:XOR 運算
pragma circom 2.0.0;
template XorTable() {
// 定義 8 位 XOR 表
signal input a[8];
signal input b[8];
signal output c[8];
// 約束 a 和 b 為二進制
for (var i = 0; i < 8; i++) {
a[i] * (1 - a[i]) === 0;
b[i] * (1 - b[i]) === 0;
}
// 使用查找表約束
// lookup[0] = a⊕b when a=0,b=0
// lookup[1] = a⊕b when a=0,b=1
// ...
// lookup[255] = a⊕b when a=1,b=1
var xor_table[256];
for (var i = 0; i < 256; i++) {
xor_table[i] = (i ^ (i >> 1)) & 1; // 簡化示例
}
// 約束查詢
component lookup = MultiTable(8, 8);
lookup.enable[0] <== 1; // 啟用第一個表
// 連接輸入
for (var i = 0; i < 8; i++) {
lookup.in_a[i] <== a[i];
lookup.in_b[i] <== b[i];
}
// 獲取輸出
for (var i = 0; i < 8; i++) {
c[i] <== lookup.out[i];
}
}
4.2 Plonky2:Goldilocks 域優化
Plonky2 是由 Polygon Zero 開發的 PLONK 變體,專門針對 Goldilocks 域($\mathbb{F}_{2^{64}-2^{32}+1}$)進行了優化。
Goldilocks 域的特性:
Goldilocks 素數:p = 2^64 - 2^32 + 1
優點:
• 64 位算術運算在現代 CPU 上高效
• FFT 運算無需模運算溢出處理
• 非常適合 GPU 並行化
缺點:
• 嵌入度低,不支持高效配對
• 需要額外處理跨域運算
Plonky2 的創新:
- 可變radix 表示:支援多個小域的組合
- 遞迴友好:優化的 Pedersen 承諾
- FRI 整合:結合 STARK 的透明性
4.3 TurboPLONK:電路優化
TurboPLONK 專注於減少電路約束數量,提供更高效的電路設計。
TurboPLONK 的特殊門:
// TurboPLONK 示例: Poseidon 雜湊
pragma circom 2.0.0;
include "../node_modules/circomlib/circuits/poseidon.circom";
// TurboPLONK 的 Poseidon 約束優化
template TurboPoseidon(n) {
signal input inputs[n];
signal output out;
// Poseidon 是一種 ZK-友好的雜湊函數
// TurboPLONK 優化了其約束數量
component poseidon = Poseidon(n);
for (var i = 0; i < n; i++) {
poseidon.inputs[i] <== inputs[i];
}
out <== poseidon.out;
}
// 使用範例:驗證雜湊鏈
template HashChain(length) {
signal input first;
signal input elements[length];
signal output root;
component hashers[length + 1];
hashers[0] = TurboPoseidon(1);
hashers[0].inputs[0] <== first;
for (var i = 0; i < length; i++) {
hashers[i + 1] = TurboPoseidon(2);
hashers[i + 1].inputs[0] <== hashers[i].out;
hashers[i + 1].inputs[1] <== elements[i];
}
root <== hashers[length].out;
}
第五章:HALO2 電路設計深度實務
5.1 HALO2 電路結構
HALO2 採用了一種獨特的「區域」(Region)概念來組織電路佈局。
HALO2 電路架構:
┌─────────────────────────────────────────────────────────────────┐
│ Advice Column │
│ 儲存 witness 值 │
│ ┌────────┬────────┬────────┬────────┐ │
│ │ Cell │ Cell │ Cell │ Cell │ ... │
│ │ A[0,0] │ A[0,1] │ A[0,2] │ A[0,3] │ │
│ ├────────┼────────┼────────┼────────┤ │
│ │ Cell │ Cell │ Cell │ Cell │ │
│ │ A[1,0] │ A[1,1] │ A[1,2] │ A[1,3] │ │
│ └────────┴────────┴────────┴────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Fixed Column │
│ 儲存選擇器和常數 │
│ ┌────────┬────────┬────────┬────────┐ │
│ │ q_L │ q_R │ q_O │ q_M │ ... │
│ └────────┴────────┴────────┴────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Instance Column │
│ 儲存公開輸入 │
│ ┌────────┬────────┬────────┐ │
│ │ IO[0] │ IO[1] │ IO[2] │ ... │
│ └────────┴────────┴────────┘ │
└─────────────────────────────────────────────────────────────────┘
5.2 HALO2 約束系統定義
HALO2 的約束系統由三部分組成:
// HALO2 約束系統核心結構
pub struct ConstraintSystem<F: Field> {
/// advice 列數
num_advice: usize,
/// 實例列數
num_instance: usize,
/// 固定列數
num_fixed: usize,
/// 查找表配置
lookup_config: Option<LookupConfig>,
/// 排列配置
permutation_config: PermutationConfig,
/// 門定義
gates: Vec<Box<dyn Gate<F>>>,
}
門定義示例:
// 定義一個簡單的乘法門
use halo2_proofs::{
circuit::{Region, Value},
plonk::{Advice, Column, ConstraintSystem, Expression, VirtualCells},
poly::Rotation,
transcript::EncodedChallenge,
arithmetic::FieldExt,
};
#[derive(Clone, Debug)]
pub struct MulGate<F: Field> {
pub q_mul: Column<Fixed>,
pub q_add: Column<Fixed>,
pub q_const: Column<Fixed>,
}
impl<F: Field> Gate<F> for MulGate<F> {
type Point = Option<Point<F>>;
fn name(&self) -> &'static str {
"mul_gate"
}
fn constraints(
&self,
meta: &mut VirtualCells<'_, F>,
layout: &mut Option<Self::Point>,
) -> Expression<F> {
let lhs = meta.query_advice(self.column, Rotation::cur());
let rhs = meta.query_advice(self.column, Rotation::next());
let out = meta.query_advice(self.column, Rotation(2));
let q_mul = meta.query_fixed(self.q_mul, Rotation::cur());
let q_add = meta.query_fixed(self.q_add, Rotation::cur());
let q_const = meta.query_fixed(self.q_const, Rotation::cur());
// 約束:q_mul * lhs * rhs + q_add * lhs + q_add * rhs + q_const = out
q_mul * lhs.clone() * rhs.clone()
+ q_add * lhs
+ q_add * rhs
+ q_const
- out
}
}
5.3 實際電路開發流程
步驟 1:定義電路結構
// ZK-Merkle Tree 驗證電路
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner, Value},
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector},
arithmetic::FieldExt,
commitments::bn256:: Commitment,
};
#[derive(Clone)]
struct MerkleConfig {
advice: [Column<Advice>; 5],
instance: Column<Instance>,
selector: Selector,
constants: Column<Fixed>,
}
struct MerkleTreeCircuit<F: FieldExt> {
leaf: Option<u64>,
root: Option<u64>,
path: Vec<(u64, bool)>, // (hash, is_left_child)
}
impl<F: FieldExt> Circuit<F> for MerkleTreeCircuit<F> {
type Config = MerkleConfig;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self {
leaf: None,
root: None,
path: vec![],
}
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advice = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let instance = meta.instance_column();
let selector = meta.selector();
let constants = meta.fixed_column();
// 啟用排列約束
meta.enable_equality(instance);
for col in &advice {
meta.enable_equality(*col);
}
// 定義約束
meta.create_gate("merkle_proof", |meta| {
let s = meta.query_selector(selector);
// 獲取 cell 值
let leaf = meta.query_advice(advice[0], Rotation::cur());
let sibling = meta.query_advice(advice[1], Rotation::cur());
let is_left = meta.query_advice(advice[2], Rotation::cur());
let hash_out = meta.query_advice(advice[3], Rotation::cur());
// 約束:根據 is_left 選擇拼接順序
let left = is_left.clone() * leaf + (Expression::Constant(F::one()) - is_left.clone()) * sibling.clone();
let right = (Expression::Constant(F::one()) - is_left.clone()) * leaf + is_left.clone() * sibling;
// 約束:hash_out = Hash(left || right)
// 簡化示例:hash_out = left + right
let computed = left + right;
vec![s * (computed - hash_out)]
});
MerkleConfig {
advice,
instance,
selector,
constants,
}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
layouter.assign_region(
|| "merkle_proof",
|mut region| {
// 賦值 leaf
region.assign_advice(
|| "leaf",
config.advice[0],
0,
|| Value::known(F::from(self.leaf.unwrap_or(0))),
)?;
// 驗證路徑
let mut current_hash = self.leaf.map(|v| F::from(v));
for (i, (sibling_hash, is_left)) in self.path.iter().enumerate() {
// 啟用選擇器
config.selector.enable(&mut region, i)?;
// 賦值兄弟節點
region.assign_advice(
|| "sibling",
config.advice[1],
i,
|| Value::known(F::from(*sibling_hash)),
)?;
// 賦值左右標記
region.assign_advice(
|| "is_left",
config.advice[2],
i,
|| Value::known(F::from(*is_left as u64)),
)?;
// 計算新哈希
let left = if *is_left {
current_hash.unwrap()
} else {
F::from(*sibling_hash)
};
let right = if *is_left {
F::from(*sibling_hash)
} else {
current_hash.unwrap()
};
let new_hash = left + right;
// 賦值輸出
region.assign_advice(
|| "hash_out",
config.advice[3],
i,
|| Value::known(new_hash),
)?;
current_hash = Some(new_hash);
}
// 約束最終哈希等於 root
region.constrain_equal(
config.instance,
config.advice[4],
0,
)?;
Ok(())
},
)
}
}
步驟 2:生成證明
use halo2_proofs::{
dev::MockProver,
halo2curves::bn256::{Bn256, Fr, G1Affine},
transcript::Blake2bRead,
commitments::bn256:: CommitmentScheme,
};
fn main() {
// 定義電路實例
let circuit = MerkleTreeCircuit::<Fr> {
leaf: Some(42),
root: Some(42 + 100), // 假設路徑只有一個節點
path: vec![(100u64, true)], // 兄弟節點
};
// 創建 MockProver(用於本地測試)
let prover = MockProver::run(4, &circuit, vec![vec![Fr::from(142u64)]]).unwrap();
// 驗證
prover.verify().unwrap();
println!("電路驗證成功!");
// 實際部署時使用:
// let pk = generate_randomness(...);
// let proof = create_proof(...);
}
5.4 性能優化技巧
1. 減少約束數量:
// 優化示例:合併多個約束為一個
// 之前:多個獨立約束
for i in 0..n {
gate[i].enable(&mut region, i)?;
region.assign_advice(|| "", advice[0], i, || value_a[i])?;
region.assign_advice(|| "", advice[1], i, || value_b[i])?;
region.assign_advice(|| "", advice[2], i, || value_a[i] * value_b[i])?;
}
// 之後:使用佈局優化
// 將多個相同類型的操作排列在同一列
region.assign_table(|| "", |mut row| {
for i in 0..n {
// 合併多個操作
let combined_value = value_a[i] + value_b[i] * 2 + value_c[i] * 4;
row.next()?.assign_advice(|| "", advice[0], || combined_value)?;
}
Ok(())
})?;
2. 使用查找表:
// 定義查找表
impl<F: FieldExt> Circuit<F> for LookupCircuit<F> {
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advice = meta.advice_column();
let table = meta.advice_column();
let selector = meta.selector();
// 定義查找表
meta.lookup("xnor_table", |meta| {
let input = meta.query_advice(advice, Rotation::cur());
let output = meta.query_advice(advice, Rotation::next());
// 表列
let table_input = meta.query_advice(table, Rotation::cur());
let table_output = meta.query_advice(table, Rotation::next());
vec![input, output]
}, vec![
table_input,
table_output,
]);
// ...
}
}
第六章:PLONK 與 HALO2 在 ZK-Rollup 中的應用
6.1 zkSync Era 的 PLONK 實現
zkSync Era 使用了 Plonky2 作為其核心證明系統,結合了 PLONK 和 FRI 的優勢。
zkSync 證明架構:
┌─────────────────────────────────────────────────────────────────┐
│ 交易執行層 │
│ │
│ 1. VM 執行交易 │
│ 2. 生成執行蹟跡(Execution Trace) │
│ 3. 將蹟跡轉換為約束 │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 約束生成層 │
│ │
│ • 算術約束:普通 PLONK 門 │
│ • 查詢約束:記憶體讀寫、查找表 │
│ • Keccak 約束:使用專用電路 │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 證明生成層 │
│ │
│ • Plonky2 約束系統 │
│ • FRI 承諾(透明設置) │
│ • 遞迴組合 │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 驗證層 │
│ │
│ • 單一配對檢查 │
│ • Gas 成本:~500k │
│ │
└─────────────────────────────────────────────────────────────────┘
6.2 Zcash Orchard 的 HALO2 應用
Zcash Orchard 是首個大規模部署 HALO2 的實際應用。
Orchard 電路結構:
┌─────────────────────────────────────────────────────────────────┐
│ Action Circuit │
│ │
│ 輸入: │
│ • 舊 notes 的 commitment │
│ • 舊 notes 的 nullifier │
│ • 新 notes 的值和 recipient │
│ │
│ 約束: │
│ • Pedersen 承諾正確性 │
│ • 範圍證明 │
│ • Merkle 樹驗證 │
│ • 簽章驗證(Spend Authority) │
│ │
│ 輸出: │
│ • 新 notes 的 commitment │
│ • 新 notes 的 nullifier │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Transfer Circuit │
│ │
│ 約束: │
│ • Action 電路的正確執行 │
│ • 增量 Merkle 更新 │
│ • 電路間的一致性 │
│ │
└─────────────────────────────────────────────────────────────────┘
6.3 Aztec 的 PLONK 應用
Aztec Network 使用 PLONK 實現完全私密的 DeFi 交互。
Aztec 隱私架構:
┌─────────────────────────────────────────────────────────────────┐
│ 隱私帳戶模型 │
│ │
│ Account Note = Hash(secret, owner_pubkey, nonce) │
│ │
│ • 餘額對外隱藏 │
│ • 交易金額對外隱藏 │
│ • 交易對象對外隱藏 │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PLONK 電路 │
│ │
│ • 輸入輸出電路:驗證隱私轉帳 │
│ • 側鏈合約電路:驗證 DeFi 交互 │
│ • 合併電路:批量處理多筆交易 │
│ │
└─────────────────────────────────────────────────────────────────┘
第七章:安全考量與最佳實踐
7.1 電路安全性常見陷阱
ZK 電路安全問題清單:
┌─────────────────────────────────────────────────────────────────┐
│ 約束不足 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 問題:某些約束未被正確施加 │
│ │
│ 示例: │
│ // 錯誤:未約束輸入為二進制 │
│ signal input x; │
│ out <== x * (x - 1); // 應該是 x * (1 - x) │
│ │
│ // 正確: │
│ x * (1 - x) === 0; │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 溢出處理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 問題:算術運算可能導致域元素溢出 │
│ │
│ 示例: │
│ // 危險:在 254 位域上 │
│ a * b * c * d // 可能溢出 │
│ │
│ // 安全:分步計算並約束 │
│ t1 <== a * b; │
│ t2 <== c * d; │
│ t1 * t2 === result; │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 未定義行為 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 問題:某些運算在約束中未明確定義 │
│ │
│ 示例: │
│ // 除零:當分子為零時 │
│ a / b // 當 b = 0 時結果未定義 │
│ │
│ // 安全:添加條件約束 │
│ (b == 0) => (a == 0); │
│ b * inv(b) === 1; // 當 b ≠ 0 時 │
│ │
└─────────────────────────────────────────────────────────────────┘
7.2 驗證器安全最佳實踐
// 安全驗證器實現
use halo2_proofs::{
plonk::{verify_proof, Advice, Any, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, ProvingKey, VerifyingKey},
poly::VerificationEngine,
commitments::bn256:: CommitmentScheme,
};
fn secure_verify<C: CommitmentScheme>(
vk: &VerifyingKey<C>,
proof: &[u8],
public_inputs: &[C::Scalar],
params: &C::Params,
) -> Result<bool, Error> {
// 1. 驗證 public inputs 長度
if public_inputs.len() != vk.cs.num_instance_columns() {
return Err(Error::InvalidInstance);
}
// 2. 驗證 proof 長度
let expected_len = vk.cs.minimum_degree();
if proof.len() < expected_len {
return Err(Error::InvalidProof);
}
// 3. 使用恆定時間比較
let mut transcript = Blake2bTranscript::new(proof);
let mut engine = VerificationEngine::new(params, vk);
// 4. 執行驗證
let result = engine.verify(&mut transcript, public_inputs);
// 5. 清除敏感數據
drop(transcript);
drop(engine);
result
}
7.3 形式化驗證在 ZK 中的應用
形式化驗證工具:
┌─────────────────────────────────────────────────────────────────┐
│ 電路正確性證明 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 使用 Lean/Coq/Isabelle 對約束系統進行數學證明 │
│ │
│ 示例: │
│ theorem merkle_proof_soundness │
│ (leaf : Value) │
│ (path : list (Hash × bool)) │
│ (root : Hash) │
│ (H : Hash → Hash → Hash) │
│ : │
│ Merkle.verify(leaf, path, root, H) │
│ → ∀ commitment, │
│ commitment = Merkle.commit(leaf, path) │
│ → commitment = root │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 編譯器驗證 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 確保 DSL 編譯器生成的約束正確實現原始程式 │
│ │
│ 工具:Circom、Noir、Pil │
│ │
└─────────────────────────────────────────────────────────────────┘
結論
HALO2 和 PLONK 家族代表了零知識證明技術的兩個重要發展方向。PLONK 以其通用性和靈活性成為ZK電路設計的主流框架,而 HALO2 以其創新的折疊方案和無需可信設置的特性,在遞迴證明和增量計算領域展現出獨特優勢。
理解這些協議的數學原理對於開發高效的零知識應用至關重要。從約束系統的設計到電路的優化布局,從協議的安全性分析到實際的工程實現,每個環節都需要仔細考量。
截至 2026 年第一季度,這些技術已經在多個主流項目中得到實際應用,包括 Zcash 的 Orchard、zkSync Era、Polygon zkEVM、Aztec 等。隨著硬體加速技術的發展和協議層面的持續優化,零知識證明的生成效率正在快速提升,預計在未來幾年內將迎來更大規模的採用。
對於以太坊開發者而言,掌握 PLONK 和 HALO2 的原理及應用,將是構建下一代高效、安全、隱私保護應用的關鍵技能。無論是開發 ZK-Rollup、隱私交易協議,還是去中心化身份系統,這些技術都將發揮核心作用。
參考資源
- Gabizon, A., Williamson, Z. J., & Ciobotaru, O. (2019). "PLONK: Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge." IACR Cryptol. ePrint Arch.
- Bünz, B., et al. (2020). "Halo: Recursive Proof Composition without a Trusted Setup." IEEE S&P 2020.
- Esgin, M. F., et al. (2022). "MatRiCT: Efficient, Succinct and Recursive Zero-Knowledge Proofs for Threshold and String Arithmetic." ACM CCS 2022.
- Zcash Foundation. "Halo 2 Specification." hackmd.io.
- Polygon Zero. "Plonky2: Fast Recursive Arguments with PLONK and FRI." polygon.technology.
- Ethereum Foundation. "ZK-EVM Types." ethereum.org.
- Aztec Network. "Noir: Language for Zero Knowledge." noir-lang.org.
- Circom. "zkSNARK Circuit Compiler." circom-language.org.
- Wrona, K. "ZK-ASM: A Formal Specification for Zero-Knowledge Circuits." a16z crypto.
- Kleppmann, M. "Designing Data-Intensive Applications." O'Reilly Media.
相關文章
- ZK Rollup 電路設計數學推導完整指南:從代數電路到 STARK/SNARK 證明系統的工程實踐 — 本文從密碼學數學基礎出發,系統性地推導代數電路(Arithmetic Circuits)、約束系統(Constraint Systems)、多項式承諾(Polynomial Commitments)等核心概念。深入分析 SNARK(Groth16、PLONK)和 STARK(FRI 協議)兩大主流證明系統的數學推導與工程實現差異。提供完整的數學公式推導、實際的電路設計範例(餘額轉帳、Merkle 樹更新、zkEVM)、以及電路優化策略。涵蓋 2026 年最新的 ZK Rollup 技術發展。
- ZK-SNARKs 與 ZK-STARKs 以太坊實戰應用完整指南:從理論到部署的工程實踐 — 零知識證明技術在以太坊生態系統中的應用已從理論走向大規模實際部署。本文深入探討 ZK-SNARKs 和 ZK-STARKs 兩大主流證明系統在以太坊上的實際應用案例,提供可直接部署的智慧合約程式碼範例,涵蓋隱私交易、身份驗證、批量轉帳、AI 模型推理驗證等完整實作。
- 零知識證明完整技術指南:從基礎密碼學到以太坊應用實踐 — 零知識證明是現代密碼學最革命性的發明之一,允許一方在不透露任何額外信息的情況下向另一方證明某陳述的正確性。本文深入探討零知識證明的數學基礎、主流技術方案(zk-SNARKs、zk-STARKs、PLONK)、以及在以太坊生態系統中的實際應用,包括 ZK Rollup 技術架構、隱私保護應用與開發實踐。我們將從密碼學原語出發,逐步構建完整的零知識證明知識體系。
- zkLLVM 與 zkVM 技術比較完整指南:零知識證明虛擬機與編譯器的工程實踐 — zkVM 與 zkLLVM 是實現零知識證明的兩條主要技術路徑。本文深入分析這兩種技術的架構設計、效能比較、適用場景和選擇策略,幫助開發者理解如何為項目選擇合適的零知識證明解決方案。
- Circle STARK 完整技術指南:密碼學原理、效能優化與應用實踐 — Circle STARK 是 Circle 公司在零知識證明領域的重要貢獻,為金融應用提供了一個高效、合規、易用的 STARK 實現。本指南深入解析 Circle STARK 的密碼學基礎、架構設計、效能特性,並探討其在以太坊生態中的實際應用場景,包括隱私支付、身份驗證、DeFi 隱私應用等。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案完整列表
- Solidity 文檔 智慧合約程式語言官方規格
- EVM 代碼庫 EVM 實作的核心參考
- Alethio EVM 分析 EVM 行為的正規驗證
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!