Halo2 累積機制與 PLONK 電路約束推導完整指南:零知識證明的進階實作
本文深入分析 Halo2 的累積機制和 PLONKish 約束系統的數學原理。涵蓋有限域基礎、KZG 多項式承諾、PLONK 約束系統完整推導、Halo2 的累積器設計、R1CS 與 PLONK 比較、以及如何使用 Halo2 SDK 實作零知識電路。提供完整的代數推導過程和 Rust 程式碼範例,是理解現代零知識證明系統的核心教材。
Halo2 累積機制與 PLONK 電路約束推導完整指南:零知識證明的進階實作
摘要
Halo2 是由 Zcash 基金會開發的下一代零知識證明系統,它採用了一種創新的「累積機制」(Accumulation Scheme)來實現遞迴證明驗證,徹底擺脫了傳統 zkSNARK 系統對「可信設置」(Trusted Setup)的依賴。本文深入分析 Halo2 的累積機制設計原理、PLONKish 約束系統的數學推導、以及遞迴證明的實際應用。
本文涵蓋以下核心主題:KZG 多項式承諾的代數基礎、PLONKish 約束系統的完整推導過程、Halo2 的「累積器」設計、R1CS 與 PLONK 約束系統的比較、以及如何使用 Halo2 SDK 實作簡單的零知識電路。讀者需要具備抽象代數、有限域運算和基礎密碼學的背景知識。
一、數學基礎:有限域與群論
1.1 有限域的代數結構
零知識證明系統的數學基礎建立在有限域(Finite Field)的理論之上。有限域是一個包含有限個元素的集合,在其上定義了加法和乘法兩種運算,且滿足封閉性、結合律、單位元、逆元等代數公理。
有限域的定義:
一個有限域 F_p(其中 p 為質數)包含:
- 元素集合:{0, 1, 2, ..., p-1}
- 加法:封閉於模 p 運算
- 乘法:封閉於模 p 運算(排除 0)
域公理:
1. (F, +) 是阿貝爾群,單位元為 0
2. (F*, ×) 是阿貝爾群,單位元為 1
3. 分配律:a × (b + c) = a × b + a × c
常用有限域:
- F_p:用於以太坊簽章、ECC
- F_{p^2}:用於橢圓曲線配對(BN128)
- F_r:用於橢圓曲線(secp256k1)
1.2 橢圓曲線群的密碼學應用
橢圓曲線密碼學是現代零知識證明的核心工具。我們使用的 BN128 曲線定義如下:
BN128 曲線方程:
E: y² = x³ + 3(在 F_p 上)
其中:
p = 21888242871839275222246405745257275088696311157297823662689037894645226208583
G₁ = (1, 2)(基點)
G₂ = (11559732032986387107991004021392285783925812861821192530917403151452391805634,
1085701299908655776552308812456685197809907767967297693726597777552116067522)
配對運算是理解 KZG 承諾的關鍵。一個雙線性配對(Pairing)是這樣的映射:
e: G₁ × G₂ → G_T
雙線性性質:
1. e(P + Q, R) = e(P, R) × e(Q, R)
2. e(P, Q + R) = e(P, Q) × e(P, R)
3. e(aP, bQ) = e(P, Q)^{ab}
這種性質使得我們可以「驗證乘法關係」,而無需知道具體的乘法因子。
1.3 多項式與承諾
在零知識證明中,我們經常需要對多項式進行承諾,以便在不知道多項式內容的情況下驗證其性質。
多項式承諾的基本思想:
多項式 P(x) = a₀ + a₁x + a₂x² + ... + aₙxⁿ
承諾過程:
1. 選擇一個隨機的「陷阱門」s(secret)
2. 計算 Commitment = P(s) × G₁
3. 只公開 Commitment,保密 s
驗證過程:
- 給定 Commitment 和 opening (P, y)
- 驗證者檢查 P(s) = y
- 這需要在知道 s 的情況下進行
KZG 承諾使用配對來實現這個目標:
- Commitment 隱藏了多項式的係數
- 任何人都可以驗證某個點的值是正確的
二、KZG 多項式承諾
2.1 承諾的代數推導
KZG(Kate-Zaverucha-Goldberg)承諾是由 Kate、Zaverucha 和 Goldberg 於 2010 年提出的多項式承諾方案。它利用了橢圓曲線配對的雙線性性質。
設置階段:
可信設置(Trusted Setup):
1. 選擇一個橢圓曲線群 G₁, G₂ 和配對 e: G₁ × G₂ → G_T
2. 選擇一個秘密的「評估點」s ∈ F_p
3. 計算 Powers of Tau(τ powers):
- [1]G₁ = G₁
- [s]G₁ = s × G₁
- [s²]G₁ = s² × G₁
- ...
- [s^n]G₁ = s^n × G₁
同樣在 G₂ 上計算 [s]G₂, [s²]G₂, ...
4. 發布所有公共參數 SRS(Structured Reference String):
- {([s^i]G₁, [s^i]G₂) for i = 0, 1, ..., n}
- 這些被稱為 "powers of tau"
承諾計算:
對於多項式 P(x) = a₀ + a₁x + ... + aₙxⁿ:
KZG 承諾推導:
1. 計算多項式承諾:
C = a₀[s]G₁ + a₁[s²]G₁ + ... + aₙ[s^{n+1}]G₁
= (a₀ + a₁s + a₂s² + ... + aₙsⁿ) × G₁
= P(s) × G₁
2. 這就是我們的承諾 C ∈ G₁
注意:我們實際上無法直接計算 P(s) × G₁,
因為我們不知道 s。但我們可以使用 SRS 來「組合」出這個結果。
承諾的隱藏性:
為什麼承諾是隱藏的?
假設攻擊者想要從 C 反推 P(x) 的係數。
C = P(s) × G₁
要解出 a₀, a₁, ..., aₙ,攻擊者需要解決:
a₀ + a₁s + ... + aₙsⁿ = x
其中 x 滿足 x × G₁ = C
這是一個離散對數問題(已知 G₁ 和 C,求 x),
在橢圓曲線密碼學中,這被認為是計算困難的。
2.2 開啟(Opening)與驗證
現在我們需要能夠「打開」承諾,證明某個多項式在某個點的取值。
開啟協議:
給定承諾 C 和多項式 P(x),我們想要證明 P(z) = y。
步驟 1:計算商多項式
Q(x) = (P(x) - y) / (x - z)
這需要 P(z) = y,否則 Q(x) 不是多項式(會有餘數)。
步驟 2:承諾 Q(x)
C_Q = commit(Q)
步驟 3:驗證者檢查
使用配對驗證:
e(C - y×G₁, [1]G₂) = e(C_Q, [s-z]G₂)
推導:
左邊 = e((P(s)-y)G₁, G₂) = e(P(s)-y, 1) × e(G₁, G₂) = e(P(s)-y, 1)
右邊 = e(Q(s), s-z) = e((P(s)-y)/(s-z), s-z) = e(P(s)-y, 1)
因此,如果等式成立,則 P(z) = y。
批量開啟:
在實際應用中,我們經常需要同時開啟多個多項式或多个點。Halo2 使用了一種優化技術:
批量開啟優化:
給定多個多項式 P₁(x), P₂(x), ..., Pₖ(x)
和多個點 z₁, z₂, ..., zₘ
使用隨機性聚合:
Q(x) = Σᵢ r^i × (P_i(x) - P_i(z_i)) / (x - z_i)
其中 r 是驗證者選擇的隨機數。
這將 m 個開啟證明合併為 1 個,
驗證成本從 O(m) 降低到 O(1)。
三、PLONKish 約束系統
3.1 約束系統的基本概念
PLONK(Permutations over Lagrange-bases for Оecurring zk-SNARKs)是一種通用的零知識證明系統。它的核心思想是將任意計算轉換為一組「約束」(Constraints),並使用多項式來描述和驗證這些約束。
約束的三種類型:
PLONK 中的約束:
1. 算術約束(Arithmetic Constraints)
q_L × a + q_R × b + q_O × c + q_M × a × b + q_C = 0
其中 q_* 是選擇器多項式(Selector Polynomials)
2. 複製約束(Copy Constraints)
確保電路中不同位置的「線」具有相同的值
例如:電線 #3 的值等於電線 #7 的值
3. 查找約束(Lookup Constraints)(UltraPLONK 擴展)
允許「查表」操作
例如:證明某個值在預定義的表中存在
3.2 電路映射:從計算到約束
讓我們通過一個具體例子來理解如何將計算轉換為約束。
例子:驗證 a × b = c
電路佈局:
input a ─────┐
│
├─→ [×] ──→ output c
│
input b ────┘
約束系統:
1. 乘法約束
a × b - c = 0
在 PLONK 中,這寫成:
q_M × a × b + q_O × c + q_C = 0
其中 q_M = 1, q_O = -1, q_C = 0
2. 輸入約束(電線分配)
w_0 = a (左輸入)
w_1 = b (右輸入)
w_2 = c (輸出)
更複雜的例子:a² + b = c
電路佈局:
input a ──→ [×] ──→ [+] ──→ output c
↑ ↑
a b
約束系統:
1. 第一個乘法約束:a × a - d = 0
其中 d 是中間結果
2. 第二個加法約束:d + b - c = 0
PLONK 加法約束:
q_L × d + q_R × b + q_O × (-c) + q_C = 0
其中 q_L = 1, q_R = 1, q_O = -1, q_C = 0
3.3 約束的矩陣表示
PLONK 使用一個統一的框架來表示所有約束:
約束矩陣結構(Gate Constraints):
┌────────────────────────────────────────────────────────────┐
│ q_L[i] × a[i] + q_R[i] × b[i] + q_O[i] × c[i] │
│ + q_M[i] × a[i] × b[i] + q_C[i] = 0 │
└────────────────────────────────────────────────────────────┘
其中:
- i 是「門」的索引
- a[i], b[i], c[i] 是第 i 個門的左/右/輸出電線值
- q_*[i] 是選擇器多項式在第 i 個位置的取值
選擇器多項式用於「開關」不同的約束類型:
| 門類型 | q_M | q_L | q_R | q_O | q_C |
|--------|-----|-----|-----|-----|-----|
| 乘法 | 1 | 0 | 0 | -1 | 0 |
| 加法 | 0 | 1 | 1 | -1 | 0 |
| 常數 | 0 | 0 | 0 | 0 | v |
3.4 複製約束的處理
複製約束是 PLONK 最巧妙的部分之一。當電路中多個位置需要相同的值時(例如,一個輸入被多個門使用),我們需要確保這些位置的「電線」確實具有相同的值。
問題表述:
假設我們有 n 個電線位置:w_0, w_1, ..., w_{n-1}
以及 m 個複製約束:
(w_0 = w_3), (w_1 = w_2), (w_5 = w_7), ...
如何高效地驗證這些等式?
PLONK 的解決方案:排列論證:
PLONK 使用「排列論證」來高效地處理複製約束。核心思想是:
排列論證的思想:
1. 為每個電線位置分配一個「標籤」
每個位置一開始有自己的標籤
2. 對於每個複製約束 (i = j)
我們「交換」位置 i 和 j 的標籤
3. 最後,所有相等的位置應該有相同的「最終標籤」
這可以通過一個「排列函數」來形式化:
σ : {0, 1, ..., n-1} → {0, 1, ..., n-1}
σ 描述了每個位置應該與哪個位置交換。
排列論證的代數推導:
PLONK 排列論證的核心等式:
令 ω₁, ω₂, ..., ωₙ 為域中的「無法蘭元素」(roots of unity)
定義兩個多項式:
- f(x):按照原始順序排列的電線值
- g(x):按照排列後順序排列的電線值
如果存在一個排列 σ 使得 g(x) = f(σ(x)),
則 f 和 g 在所有點上都「匹配」。
PLONK 使用 Grand Product 論證來驗證這個條件:
令 Z(x) = ∏_{i=1}^{k} (f(ω_i) - g(ω_i))
如果 Z(x) 在所有 ω_i 上都為 0,則 f = g(經過排列)。
四、Halo2 的累積機制
4.1 為什麼需要累積?
傳統 zkSNARK 系統(如 Groth16)的一個主要限制是需要「可信設置」。對於每個電路,都需要執行一個複雜的設置儀式來生成公共參數。如果電路發生變化,就需要重新執行整個設置過程。
可信設置的問題:
可信設置的缺點:
1. 安全性假設
如果設置過程中的任何一個參與者是誠實的,整個系統就是安全的。
但如果所有參與者都串通,可以偽造虛假證明。
2. 電路特定性
Groth16 的參數只對一個特定電路有效。
如果電路改變,需要重新設置。
3. 儀式的複雜性
安全的設置儀式需要多方參與,成本高且難以組織。
累積機制的解決方案:
Halo2 採用了「累積機制」來解決這個問題:
累積機制的核心思想:
1. 遞迴證明
我們可以將一個證明「包裝」成另一個證明的一部分。
這允許我們「累積」多個證明的驗證成本。
2. 無需可信設置
使用「內積論證」(Inner Product Argument)
替代配對基的承諾方案
3. 增量可擴展性
可以逐步增加計算能力,無需重新設置
4.2 累積器的數學基礎
Halo2 的累積機制建立在「多項式 IOP」(Polynomial Interactive Oracle Proof)的基礎上。讓我們深入理解其數學原理。
內積證明(Inner Product Proof):
內積證明的目標:
給定向量 a = (a₁, a₂, ..., aₙ) 和 b = (b₁, b₂, ..., bₙ)
prover 想要讓 verifier 相信 ⟨a, b⟩ = c
其中 ⟨a, b⟩ = Σᵢ aᵢ × bᵢ 是內積。
Bulletproofs/Halo2 使用了一種對數大小的內積證明。
步驟 1:初始化
- Prover 計算 c = ⟨a, b⟩
- 設置 L = [], R = [](將存儲承諾的向量)
步驟 2:迭代(每輪循環)
假設 n 為偶數,將 a 和 b 各分成兩半:
a_L = (a₁, ..., a_{n/2}), a_R = (a_{n/2+1}, ..., aₙ)
b_L = (b₁, ..., b_{n/2}), b_R = (b_{n/2+1}, ..., bₙ)
定義:
- a' = a_L × L_scalar + a_R × R_scalar
- b' = b_L + (b_R × L_scalar²)
其中 L_scalar 是 verifier 選擇的隨機數
這裡使用了一種巧妙的「壓縮」技術:
- 新的向量長度減半
- 新的內積 = 原始內積 + L_scalar × (a_L⟨b_R⟩ + a_R⟨b_L⟩) + L_scalar² × ⟨a_R, b_R⟩
累積證明的遞迴結構:
Halo2 累積器的運作方式:
1. 初始狀態
Accumulator_0 = (C₁, C₂, ..., Cₖ)
其中 Cᵢ 是初始承諾
2. 每一步累積
Prover 將當前累積器與新元素組合
生成新的累積器
3. 最終驗證
只需要驗證最終的累積器
所有的中間步驟都被「累積」進去了
4.3 累積器的形式化定義
累積方案的數學定義:
一個累積方案由以下算法組成:
1. Setup(λ, R) → pp
生成公共參數
λ 是安全參數,R 是關係描述
2. Accumulate(pp, A, u) → (A', π)
輸入:當前累積器 A,新元素 u
輸出:新累積器 A' 和證明 π
3. Verify(pp, A', π) → {accept, reject}
驗證累積是否有效
正確性要求:
對於任意 A 和 u,如果 (A', π) = Accumulate(pp, A, u)
則 Verify(pp, A', π) = accept
贗燒性要求:
攻擊者無法生成一個假的 (A', π)
使得 Verify 接受,但 A' 不包含真實的累積值
4.4 Halo2 累積器的實際實現
// Halo2 累積器的概念性 Rust 代碼
use halo2_proofs::{
arithmetic::FieldExt,
circuit::Layouter,
plonk::{Advice, Column, ConstraintSystem, Errors, Selector, Table},
poly::Rotation,
};
/// 累積器結構
pub struct Accumulator<F: FieldExt> {
// 累積的值
pub value: F,
// 累積的數量
pub count: u64,
}
impl<F: FieldExt> Accumulator<F> {
/// 創建新的累積器
pub fn new() -> Self {
Self {
value: F::ZERO,
count: 0,
}
}
/// 添加一個新元素到累積器
pub fn accumulate(&mut self, element: F) {
// 累積邏輯:簡單的加法
// 在實際應用中,這可以是任意交換半群操作
self.value = self.value + element;
self.count += 1;
}
}
/// 累積器約束的電路配置
pub struct AccumulatorConfig {
advice: [Column<Advice>; 3],
selector: Selector,
table: Table<Advice>,
}
impl<F: FieldExt> AccumulatorConfig {
/// 配置累積器電路
pub fn configure(meta: &mut ConstraintSystem<F>) -> Self {
let advice = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let selector = meta.selector();
// 定義查找表(用於 UltraPLONK)
let table = meta.lookup_table_column();
// 配置約束
meta.create_gate("accumulate", |meta| {
let q = meta.query_selector(selector);
let a = meta.query_advice(advice[0], Rotation::cur());
let b = meta.query_advice(advice[1], Rotation::cur());
let c = meta.query_advice(advice[2], Rotation::cur());
// 約束:c = a + b
// 這是累積操作的核心約束
vec![q * (a + b - c)]
});
Self { advice, selector, table }
}
/// 合成累積器電路
pub fn synthesize(
&self,
accumulator: &Accumulator<F>,
layouter: &mut impl Layouter<F>,
) -> Result<(), Errors> {
layouter.assign_region(|| "accumulator", |mut region| {
// 初始化
region.assign_advice(|| "initial value", self.advice[0], 0, || accumulator.value)?;
region.assign_advice(|| "input", self.advice[1], 0, || accumulator.value)?;
region.assign_advice(|| "output", self.advice[2], 0, || accumulator.value)?;
// 啟用選擇器
self.selector.enable(&mut region, 0)?;
Ok(())
})
}
}
五、PLONK 約束的完整推導
5.1 從電路到多項式
讓我們完整推導 PLONK 約束系統將電路轉換為多項式約束的過程。
步驟 1:電路佈局
考慮一個簡單的電路:計算 (a + b) × c
電路佈局:
a ──→ [+] ──→ d ──→ [×] ──→ out
b ──→ [/] ──┘ c ──→ [/]
門:
- Gate 0: d = a + b
- Gate 1: out = d × c
步驟 2:分配電線值
定義電線:
w_0 = a (input)
w_1 = b (input)
w_2 = c (input)
w_3 = d (intermediate)
w_4 = out (output)
門分配:
| 門 | 左輸入 | 右輸入 | 輸出 |
|----|--------|--------|------|
| 0 | w_0 | w_1 | w_3 |
| 1 | w_3 | w_2 | w_4 |
步驟 3:生成約束
門 0(加法)的約束:
q_L = 1, q_R = 1, q_M = 0, q_O = -1, q_C = 0
約束:w_0 + w_1 - w_3 = 0
門 1(乘法)的約束:
q_L = 0, q_R = 0, q_M = 1, q_O = -1, q_C = 0
約束:w_3 × w_2 - w_4 = 0
步驟 4:選擇器多項式
選擇器多項式 q_M(x):
在域元素 ω₀, ω₁ 上:
q_M(ω₀) = 0(門 0 是加法)
q_M(ω₁) = 1(門 1 是乘法)
我們可以插值得到完整的 q_M(x)。
同理可以定義 q_L(x), q_R(x), q_O(x), q_C(x)。
約束多項式:
q(x) = 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)
我們需要對所有門位置 i 滿足 q(ωᵢ) = 0。
5.2 約束的零知識性質
PLONK 的約束系統設計確保了零知識性質:
零知識約束設計:
1. 隨機性注入
電線值不是直接公開,而是作為 Commitment 的一部分
Verifier 只看到承諾,不知道具體值
2. 線性化技巧
大多數約束是線性的,易於驗證
非線性約束(乘法)使用承諾方案保護
3. 交換半群結構
累積操作可以是任意交換半群
加法、乘法、最大值等都適用
電路安全性分析:
- 電線值的隱藏由承諾方案保證
- 約束的正確性由多項式論證保證
- 複製約束由排列論證保證
5.3 約束優化技巧
約束優化策略:
1. 選擇器合併
將多個具有相同結構的門合併
減少選擇器多項式的度
2. 複用計算
將公共子表達式提取為新變量
減少約束數量
3. 非線性約束最小化
非線性約束(乘法)的驗證成本較高
應盡量減少乘法約束數量
4. 查找表替代
使用 UltraPLONK 查找表替代複雜約束
某些操作(如位運算)用查找表更高效
六、Halo2 SDK 實際應用
6.1 開發環境設置
# 安裝 Halo2
cargo add halo2_proofs halo2wrong halo2ecc
# Halo2 的依賴結構
# - halo2_proofs: 核心證明系統
# - halo2wrong: 錯誤處理工具
# - halo2ecc: 橢圓曲線運算(用於特定應用)
6.2 簡單電路示例:證明你知道 a + b = c
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{Chip, Layouter, SimpleFloorPlanner, Value},
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use halo2_proofs::dev::MockProver;
// 定義晶片配置
struct SumConfig {
advice: [Column<Advice>; 3],
selector: Selector,
}
struct SumChip<F: FieldExt> {
config: SumConfig,
_marker: std::marker::PhantomData<F>,
}
impl<F: FieldExt> Chip<F> for SumChip<F> {
type Config = SumConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<F: FieldExt> SumChip<F> {
fn new(config: SumConfig) -> Self {
Self {
config,
_marker: std::marker::PhantomData,
}
}
fn configure(meta: &mut ConstraintSystem<F>) -> SumConfig {
let advice = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let selector = meta.complex_selector();
meta.create_gate("sum constraint", |meta| {
let s = meta.query_selector(selector);
let a = meta.query_advice(advice[0], Rotation::cur());
let b = meta.query_advice(advice[1], Rotation::cur());
let c = meta.query_advice(advice[2], Rotation::cur());
// 約束:a + b = c
vec![s * (a + b - c)]
});
SumConfig { advice, selector }
}
fn expose(
&self,
mut layouter: impl Layouter<F>,
column: Column<Advice>,
row: usize,
) -> Result<AssignedCell<F, F>, Error> {
layouter.constrain_instance(column, row)
}
}
// 定義電路
#[derive(Default)]
struct MyCircuit<F: FieldExt> {
a: Value<F>,
b: Value<F>,
c: Value<F>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
type Config = SumConfig;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
SumChip::configure(meta)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let chip = SumChip::new(config);
layouter.assign_region(|| "sum gate", |mut region| {
// 啟用選擇器
chip.config.selector.enable(&mut region, 0)?;
// 分配 a
region.assign_advice(
|| "a",
chip.config.advice[0],
0,
|| self.a,
)?;
// 分配 b
region.assign_advice(
|| "b",
chip.config.advice[1],
0,
|| self.b,
)?;
// 分配 c(結果)
region.assign_advice(
|| "c",
chip.config.advice[2],
0,
|| self.c,
)?;
Ok(())
})
}
}
// 測試代碼
fn main() {
use halo2_proofs::pasta::Fp;
let a = Fp::from(3);
let b = Fp::from(5);
let c = Fp::from(8); // 正確結果:3 + 5 = 8
let circuit = MyCircuit {
a: Value::known(a),
b: Value::known(b),
c: Value::known(c),
};
// Mock 證明
let prover = MockProver::run(4, &circuit, vec![]).unwrap();
prover.verify().expect("proof verification failed");
println!("Sum proof verified successfully!");
}
6.3 遞迴證明示例
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{Layouter, Value},
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Table},
poly::Rotation,
};
// 累積器電路:將多個值相加
struct AccumulatorCircuit<F: FieldExt> {
values: Vec<F>,
}
impl<F: FieldExt> AccumulatorCircuit<F> {
fn new(values: Vec<F>) -> Self {
Self { values }
}
}
impl<F: FieldExt> Circuit<F> for AccumulatorCircuit<F> {
type Config = AccumulatorConfig;
type FloorPlanner = SimpleFloorPlanner;
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advice = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let selector = meta.selector();
meta.create_gate("accumulate", |meta| {
let q = meta.query_selector(selector);
let prev = meta.query_advice(advice[0], Rotation::cur());
let curr = meta.query_advice(advice[1], Rotation::next());
let result = meta.query_advice(advice[2], Rotation::cur());
// 約束:result = prev + curr
vec![q * (prev + curr - result)]
});
Self::Config { advice, selector }
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
layouter.assign_region(|| "accumulate", |mut region| {
// 初始化為 0
let mut acc = F::ZERO;
region.assign_advice(
|| "initial",
config.advice[0],
0,
|| Value::known(acc),
)?;
// 累加每個值
for (i, v) in self.values.iter().enumerate() {
config.selector.enable(&mut region, i)?;
let new_acc = acc + v;
region.assign_advice(
|| "current",
config.advice[1],
i,
|| Value::known(*v),
)?;
region.assign_advice(
|| "result",
config.advice[2],
i,
|| Value::known(new_acc),
)?;
acc = new_acc;
}
Ok(())
})
}
}
// 遞迴證明组合器
struct RecursiveProofComposer<F: FieldExt> {
inner_proofs: Vec<Proof>,
_marker: std::marker::PhantomData<F>,
}
impl<F: FieldExt> RecursiveProofComposer<F> {
/// 組合多個內部證明
fn compose(&self) -> Proof {
// 遞迴組合的邏輯
// 將多個證明的驗證「累積」成一個最終證明
Proof {
// 組合所有內部證明的 commitment
accumulated_commitment: self.accumulate(),
// 最終驗證所需的隨機挑戰
challenges: self.generate_challenges(),
}
}
fn accumulate(&self) -> Vec<u8> {
// 累積邏輯
todo!()
}
fn generate_challenges(&self) -> Vec<F> {
// 菲德曼變換(Fiat-Shamir)
todo!()
}
}
七、性能比較與實務建議
7.1 PLONK 實現的比較
| 實現 | 證明大小 | 驗證時間 | 設置要求 | 語言 |
|---|---|---|---|---|
| zkSync (PLONKish) | ~1 KB | ~5 ms | Universal | Rust |
| Aztec (TurboPLONK) | ~500 B | ~3 ms | Universal | Rust |
| Halo2 | ~200 B | ~10 ms | Transparent | Rust |
| Gnark (PLONK) | ~800 B | ~8 ms | Universal | Go |
7.2 電路設計最佳實踐
電路設計指南:
1. 約束數量最小化
- 每個約束都有成本
- 使用選擇器來「開關」約束
- 合併相似結構
2. 查找表替代複雜約束
- 位運算、範圍檢查等用查找表更高效
- UltraPLONK 的查找約束
3. 隨機性有效利用
- 公開隨機性用於 Fiat-Shamir
- 私有隨機性用於電路布局
4. 電路-layout 優化
- 最小化行數(影響 SRS 大小)
- 平衡列數和行數
5. 遞迴證明的使用場景
- 聚合多個交易
- 驗證歷史狀態
- 跨鏈消息驗證
八、結論
本文深入分析了 Halo2 的累積機制和 PLONK 約束系統的數學原理。從有限域基礎到 KZG 承諾,從約束推導到實際代碼實現,我們展示了現代零知識證明系統的完整技術棧。
Halo2 的累積機制代表了零知識證明領域的重要突破:它擺脫了可信設置的束縛,提供了透明的設置過程,同時通過遞迴組合實現了指數級的驗證效率提升。PLONK 的約束系統則提供了表達任意計算的強大框架,使得開發者可以將複雜的業務邏輯轉換為可驗證的電路。
展望未來,我們可以預期以下發展趨勢:
- 性能持續優化:隨著硬體加速(如 GPU、FPGA)和算法改進,零知識證明的生成和驗證速度將進一步提升。
- 開發工具成熟化:更高層次的抽象層將降低零知識應用的開發門檻,使得更多應用場景得以實現。
- 標準化進程:隨著應用場景的增加,電路描述語言和證明格式的標準化將成為必要。
- 遞迴證明的普及:作為提高可擴展性的關鍵技術,遞迴證明將在區塊鏈二層、跨鏈橋和隱私計算中發揮越來越重要的作用。
參考資料
- Kate, Zaverucha, Goldberg (2010). "Constant-Size Commitments to Polynomials and Their Applications".
- Gabizon, Williamson, Ciobotaru (2019). "PLONK: Permutations over Lagrange-bases for Oecurs zk-SNARKs".
- Bünz, Bootle, Boneh, Poelstra, Wuille, Maxwell (2018). "Bulletproofs: Short Proofs for Confidential Transactions".
- Zcash Foundation. "Halo2 Design Documentation".
- Ethereum Foundation. "EIP-196: Precompiled contracts for addition and scalar multiplication on the elliptic curve alt_bn128".
- Weyl, Ohlsson (2022). "A Cambrian Explosion of Zero Knowledge Proofs".
- Bootle, Cerully, Goldsmith, Jutla (2022). "Recursive Proof Composition".
COMMIT: Add Halo2 accumulation mechanism and PLONK circuit derivation guide
相關文章
- KZG 承諾代數推導與 PLONK 電路約束完整指南:從多項式承諾到零知識電路的數學原理 — KZG 承諾方案是以太坊 Layer 2 生態系統中 ZK-Rollup 的核心密碼學基礎。本文從代數推導的角度系統性地介紹 KZG 承諾的數學構造、信任設置( Powers of Tau )、安全性證明,以及 PLONK 電路中約束系統的完整設計。我們提供詳細的代數推導過程:包括雙線性配對的數學基礎、BLS12-381 曲線參數、商多項式構造、估值驗證方程的推導、PLONK 門約束與排列約束的代數形式、以及實際部署中的 Gas 成本優化。同時包含 Circom 電路設計範例和 zkSync、Starknet 等項目的工程實踐分析。
- PLONK 與 Halo2 約束系統完整數學推導指南:從代數基礎到電路設計的深度實作 — 本文提供 PLONK 和 Halo2 約束系統的完整數學推導,從橢圓曲線和配對的代數基礎開始,逐步推導約束系統的核心機制,包括多項式承諾(KZG 方案的代數結構完整推導)、置換論證、查詢論證。同時提供完整的電路設計範例,涵蓋算術約束電路(加法、乘法)、範圍檢查電路、Verkle 樹承諾電路、私密餘額轉帳電路、ZKML 推論電路等實作範例。
- 零知識證明數學推導完整指南:從密碼學基礎到以太坊應用實戰 — 本文從數學推導的角度,全面分析零知識證明的基本原理、主要類型(SNARK、STARK、Bulletproofs)、電路設計方法,以及在以太坊上的實際應用部署。涵蓋完整的代數推導、Groth16 和 Plonkish 約束系統、FRI 協議、以及 zkEVM 架構分析。詳細比較不同 ZK 系統的 Gas 消耗與 TPS 表現,提供量化數據支撐的事實依據。
- 零知識電路開發完整教學:從理論基礎到智能合約整合的開發者路徑 — 本文提供從電路設計基礎到實際智能合約整合的完整開發者路徑。涵蓋:零知識證明的形式化定義與代數電路視角、Circom 開發環境架設與工具鏈配置、常見電路設計模式(比較器、範圍證明、Merkle 樹驗證)、複雜電路設計原則與調試技巧、從電路到 Solidity 驗證合約的完整工作流、信任設置(Powers of Tau)詳解、以及 NOIR 和 Halo2 等新一代工具的入門介紹。提供完整的代碼範例和開發實踐指導。
- ZK-SNARKs 數學推導完整指南:從零知識證明基礎到 Groth16 協議工程實踐 — 本文從工程師的視角出發,提供零知識證明數學基礎的完整推導。從密碼學安全假設出發,逐步建立零知識證明的理論框架,深入分析 zk-SNARKs 的核心協議——包括 Groth16、PLONK 與 Halo2 的設計原理與數學推導,並提供完整的程式碼範例說明如何在以太坊上實際部署零知識證明系統。
延伸閱讀與來源
- zkSNARKs 論文 Gro16 ZK-SNARK 論文
- ZK-STARKs 論文 STARK 論文,透明化零知識證明
- Aztec Network ZK Rollup 隱私協議
- Railgun System 跨鏈隱私協議
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!