10. Plonky2 递归原理与工程:在电路中验证 PLONK + FRI 证明
1. 递归不是再跑一次 native verifier
给定内层 proof
\[\pi_{\mathrm{in}}\]及其 statement
\[x_{\mathrm{in}},\]外层 circuit 需要表达 relation
\[\mathcal R_{\mathrm{rec}} \bigl( vk_{\mathrm{in}}, x_{\mathrm{in}}, \pi_{\mathrm{in}} \bigr)=1\]当且仅当
\[\mathsf{Verify}_{\mathrm{Plonky2}} \bigl( vk_{\mathrm{in}}, x_{\mathrm{in}}, \pi_{\mathrm{in}} \bigr)=1.\]然后外层 prover 为这个 verifier circuit 生成另一份 Plonky2 proof:
\[\pi_{\mathrm{out}} \leftarrow \mathsf{Prove} \bigl( C_{\mathrm{verify}}, \pi_{\mathrm{in}} \bigr).\]验证者以后只验证 $\pi_{\mathrm{out}}$,就间接验证了 $\pi_{\mathrm{in}}$。
flowchart LR
S["内层 statement x_in"] --> P["内层 proof π_in"]
P --> C["Plonky2 verifier circuit"]
S --> C
VK["内层 verifier data"] --> C
C --> O["外层 Plonky2 proof π_out"]
O --> V["native verifier"]
这里没有“证明对象自动递归”的魔法。必须把下列算法逐个 arithmetize:
- proof parsing 与 shape checks;
- Fiat–Shamir challenger;
- Merkle cap/path verification;
- FRI coset interpolation 与 folding;
- extension-field arithmetic;
- quotient recomposition;
- custom gate / permutation / lookup identity;
- public-input 与 circuit identity binding。
2. 为什么很多 SNARK 的递归很贵
如果 inner proof 使用椭圆曲线 $E/\mathbb F_p$,而 outer circuit 原生域是另一域 $\mathbb F_r$,则验证:
- curve point addition;
- scalar multiplication;
- pairing;
- base-field range checks
可能都要用 non-native limbs 表达。
例如把 $\mathbb F_p$ 元素编码成 $\mathbb F_r$ limbs:
\[x=x_0+2^bx_1+\cdots+2^{b(k-1)}x_{k-1}.\]每次乘法不仅要约束 limb convolution,还要约束 carry 和模 $p$ 约减。单个“域乘”会膨胀成大量 outer-field constraints。
Plonky2 的核心选择是让内外层都使用:
\[\mathbb F_p, \qquad p=2^{64}-2^{32}+1,\]并让验证器的关键哈希也有高效 Goldilocks custom gate。
因此:
\[\boxed{ \text{inner arithmetic field} = \text{outer circuit native field}. }\]extension element
\[a+b\varphi\]只需两个 Goldilocks targets,不必模拟另一大素数域。
3. 递归 proof target 的结构
Plonky2 源码中的核心 proof 包含:
\[\pi= \bigl( C_w, C_{Z,\pi}, C_Q, \mathcal O_\zeta, \pi_{\mathrm{FRI}} \bigr).\]对应字段是:
| proof component | 含义 |
|---|---|
wires_cap | wire polynomials 的 LDE Merkle cap |
plonk_zs_partial_products_cap | permutation $Z$、partial products 及相关多项式 cap |
quotient_polys_cap | quotient chunks cap |
openings | $\zeta$ 与 rotations 上的声称点值 |
opening_proof | 批量 FRI opening argument |
递归 builder 调用
1
2
3
4
5
6
let proof_t = builder.add_virtual_proof_with_pis(&inner_common_data);
builder.verify_proof::<C>(
&proof_t,
&inner_verifier_data,
&inner_common_data,
);
其含义是:
- 按
inner_common_data的固定 shape 分配 targets; - 把每个 Merkle digest、opening、FRI step 变成 witness variables;
- 在 circuit 中添加所有 verifier constraints;
- witness generation 时把真实 inner proof 填进去。
add_virtual_proof_with_pis 本身不证明任何事;真正的安全性来自随后添加的 verify_proof constraints。
4. verifier data 与 common data 不能混淆
Plonky2 把验证所需信息拆成两类。
4.1 verifier-only data
主要包含:
- constants 与 $\sigma$ polynomials 的 Merkle cap;
- circuit digest。
记为
\[vk_{\mathrm{only}} = \bigl( C_{\mathrm{const},\sigma}, d_{\mathrm{circ}} \bigr).\]4.2 common circuit data
描述 proof shape 和约束系统公共结构,例如:
- number of wires / routed wires;
- gate set 与 selector layout;
- degree bits;
- quotient degree factor;
- FRI rate、cap height、fold arities、query count;
- public-input 数量;
- lookup shape。
4.3 statement identity
一份 proof 的 statement 实际是:
\[\boxed{ ( d_{\mathrm{circ}}, C_{\mathrm{const},\sigma}, \text{common data}, \text{public inputs} ). }\]只验证 proof bytes 而不固定这些对象,无法说明证明的是哪一个 circuit。
flowchart TB
CD["Common data<br/>proof shape 与协议参数"] --> VC["Verifier circuit"]
VD["Verifier-only data<br/>circuit digest + const/sigma cap"] --> VC
PI["Inner public inputs"] --> VC
PR["Inner proof witness"] --> VC
VC --> OK["约束:指定 circuit 的指定 statement 被接受"]
5. 递归 verifier 的五个阶段
5.1 重建 transcript
circuit 内 challenger 必须按 native verifier 完全相同的顺序吸收:
- FRI 参数;
- inner circuit digest;
- public-input hash;
- wire cap;
- $Z$/partial-product cap;
- quotient cap;
- openings;
- FRI layer caps、final polynomial、PoW witness。
并采样同样的
\[\beta_j,\gamma_j,\alpha_j,\zeta,\rho,\eta_i,q_t.\]只要有一个:
- 序列化顺序;
- length prefix;
- field/byte conversion;
- domain separator
不同,circuit challenge 就不会等于 native challenge。
5.2 验证 Merkle openings
对 query index $q$,从 leaf digest 开始按 index bits 决定左右顺序:
\[h_{i+1} = H\bigl( \operatorname{select}(b_i,h_i,s_i), \operatorname{select}(b_i,s_i,h_i) \bigr).\]到 cap 边界后检查
\[h_{\mathrm{top}} = \operatorname{cap}[q_{\mathrm{high}}].\]5.3 验证 FRI fold chain
认证每个 query coset 的 evaluations,用挑战折叠到下一层,并最终对接 final polynomial。
5.4 重算 quotient identity
在 $\zeta$ 上用 openings 重算所有 gate/permutation/lookup constraints,检查
\[C_{\alpha_j}(\zeta) = Z_H(\zeta)Q_j(\zeta).\]5.5 绑定公开输入与 inner circuit
检查 public-input hash 和 circuit digest 已进入 transcript,且 outer application 对外暴露了它需要的 statement。
6. Merkle cap 为什么特别适合静态递归 circuit
设 LDE tree 高度为
\[h=\log_2|L|.\]普通 root commitment 的每条 path 要验证 $h$ 个 hash。cap height 为 $c$ 时:
- proof 发送 $2^c$ 个 cap digests;
- 每条 path 只需 $h-c$ 个 hashes;
- 用 query 的高 $c$ bits 选择 cap member。
递归电路的粗略 hash 数从
\[q h\]降为
\[q(h-c),\]其中 $q$ 是独立 query 数。
若多棵树或多个 queries 共享 path 节点,还可做压缩;但递归 circuit 通常偏好:
- 固定数组长度;
- 固定 path 深度;
- 固定 selector 结构。
因为动态 path pruning 会让 circuit shape 依 proof 的随机 query collisions 改变。
flowchart TB
ROOT["root"] --> C0["cap node 0"]
ROOT --> C1["cap node 1"]
ROOT --> C2["其他 cap nodes"]
C1 --> M1["path 内部节点"]
M1 --> M2["path 内部节点"]
M2 --> L["queried leaf"]
CAP["proof 直接提供整层 cap"] -.-> C0
CAP -.-> C1
CAP -.-> C2
技术稿报告递归 verifier 大部分门用于 Merkle paths,所以每少一个 path hash 都可能比微调普通域加法更有价值。
7. 高元 FRI 在 circuit 中怎样检查
7.1 binary fold 的直观式
先看二元 coset ${x,-x}$。设
\[f(x)=y_0, \qquad f(-x)=y_1.\]写
\[f(X)=f_{\mathrm{even}}(X^2)+Xf_{\mathrm{odd}}(X^2).\]则
\[\begin{aligned} f_{\mathrm{even}}(x^2)&=\frac{y_0+y_1}{2},\\ f_{\mathrm{odd}}(x^2)&=\frac{y_0-y_1}{2x}. \end{aligned}\]挑战 $\beta$ 下的 fold:
\[y' = \frac{y_0+y_1}{2} + \beta\frac{y_0-y_1}{2x}.\]7.2 一般 $a$ 元 fold
对 coset
\[\{x,x\omega_a,\ldots,x\omega_a^{a-1}\}\]及 evaluations $y_0,\ldots,y_{a-1}$,可先插值得到
\[f(X) = \sum_{j=0}^{a-1}X^j f_j(X^a),\]再算
\[y' = \sum_{j=0}^{a-1}\beta^j f_j(x^a).\]在 circuit 中,这意味着:
- 认证 $a$ 个 evaluations;
- 做固定大小的 coset interpolation;
- 计算 $\beta$ powers;
- 做 extension-field dot product。
7.3 arity 的成本交换
更大 arity:
- FRI rounds 更少;
- Merkle commitments 层数更少;
- 每轮 leaf/coset 更宽;
- interpolation gate 更大。
若 degree bits 为 $d$、每轮 log arity 为 $b$,fold rounds 约为
\[\left\lceil \frac{d-d_{\mathrm{final}}}{b} \right\rceil.\]Plonky2 的递归策略使用固定 arity,正是为了让每一轮复用同一种 interpolation gate shape,而不是为混合 arities 建多套 verifier gates。
8. Poseidon gate 是递归性能的支点
递归 verifier 中至少两处大量使用 hash:
- Merkle tree authentication;
- Fiat–Shamir sponge。
若一次 Poseidon permutation 需要几十行普通 arithmetic gates,则每条 Merkle path 的成本会成倍增加。
Plonky2 把整次 width-12 Poseidon permutation 放入一个宽 custom gate:
\[\mathbf s_{\mathrm{out}} = \operatorname{Poseidon}(\mathbf s_{\mathrm{in}}).\]中间 wires 记录每个非线性层的必要值,约束 $x^7$ 和 linear layers。
这种设计同时解释了:
- 为什么 circuit width 高达 135;
- 为什么 recursion verifier 能维持较低行数;
- 为什么换 hash 参数不是一个局部 API 改动,而会改变 gate layout、circuit digest、proof compatibility。
9. public inputs 怎样穿过递归层
9.1 仅验证 inner proof 不等于暴露 inner statement
假设 outer circuit 私下接收:
\[(x_{\mathrm{in}},\pi_{\mathrm{in}})\]并只约束 inner verification,但 outer proof 没有把 $x_{\mathrm{in}}$ 注册为自己的 public inputs。外部验证者只知道:
“存在某个 statement 和某个 proof 被 inner verifier 接受。”
这通常不是应用想要的命题。
9.2 正确绑定方式
outer circuit 应把需要保留的 statement 投影到 outer public inputs:
\[x_{\mathrm{out}} = g(x_{\mathrm{in}}),\]并约束该等式。
例:
\[\begin{aligned} x_{\mathrm{in}} &=(\text{oldRoot},\text{newRoot},\text{batchHash}),\\ x_{\mathrm{out}} &=(\text{oldRoot},\text{newRoot}). \end{aligned}\]outer verifier 才能检查递归 proof 覆盖的是指定状态转换。
9.3 聚合时的 statement
若聚合两个 proofs:
\[\pi_L,\pi_R,\]常见 outer statement 是:
\[h_{\mathrm{out}} = H( h_{\mathrm{stmt},L}, h_{\mathrm{stmt},R} ).\]电路必须检查两个 inner public-input hashes,并按确定顺序组合;否则 left/right swap、遗漏 child 或重复 child 都可能改变语义。
10. circuit digest 为何也必须递归绑定
若 outer circuit 只接收一个动态 verifier key,而不限制它等于预期 inner circuit,则 prover 可提交另一个容易满足的 circuit:
\[C_{\mathrm{fake}}(w)=0.\]即使 $\pi_{\mathrm{fake}}$ 对 $C_{\mathrm{fake}}$ 完全有效,也不能证明目标 $C_{\mathrm{target}}$。
有两种安全模式:
10.1 固定 inner verifier
把
\[d_{\mathrm{target}}, C_{\mathrm{const},\sigma,\mathrm{target}}\]硬编码进 outer circuit。
10.2 通用 verifier + 公开 VK
把 verifier data 作为 outer public input,并由 application 检查它等于 allowlist 中的 digest。
不安全的中间状态是:
- verifier data 是 private witness;
- circuit 仅证明“某个 verifier 接受”;
- application 又不知道这个 verifier 是谁。
11. 普通递归链
设 $C_0$ 是 base computation circuit。定义
\[C_{i+1} = \bigl[ \mathsf{Verify}(vk_i,\pi_i,x_i)=1 \bigr] + \text{本层附加逻辑}.\]然后
\[\pi_{i+1} \leftarrow \mathsf{Prove}(C_{i+1}).\]sequenceDiagram
participant B as Base computation
participant P0 as Proof π_0
participant R1 as Recursion circuit 1
participant P1 as Proof π_1
participant R2 as Recursion circuit 2
participant P2 as Proof π_2
B->>P0: prove base statement
P0->>R1: verify in circuit
R1->>P1: prove verification
P1->>R2: verify in circuit
R2->>P2: prove verification
只要每层 proof shape 受固定 common data 控制,proof size 可与原始 computation 长度无关。每加一层,proving time 仍需支付一次 recursion circuit 的证明成本。
12. recursion threshold 的含义
设一个 Plonky2 proof 的 in-circuit verifier 需要
\[G_{\mathrm{verify}}\]个有效 gate rows,padding 后 trace degree 为
\[N_{\mathrm{verify}} = 2^{\lceil\log_2G_{\mathrm{verify}}\rceil}.\]如果外层证明自己的 verifier 后,下一层仍能保持相同的 degree bucket 和 proof shape,就达到 fixed-point-like recursion:
\[\operatorname{Shape}(\pi_{i+1}) = \operatorname{Shape}(\pi_i).\]“recursion threshold 为 $2^{12}$ gates”一类历史说法,是指特定参数、特定硬件与特定 verifier circuit 的尺寸量级,不是协议常数。
threshold 会随下列因素改变:
- query rounds;
- Merkle cap height;
- FRI arity/final degree;
- hash gate;
- public inputs;
- lookup 数量;
- ZK randomization;
- proof compression layout;
- compiler/gate packing。
13. cyclic recursion 的自引用难题
13.1 悖论表象
想让同一个 circuit $C$ 验证自身上一层 proof,需要在构建 $C$ 时知道
\[vk_C.\]但 $vk_C$ 又取决于 $C$ 的最终 gate layout,似乎形成循环:
\[C\to vk_C\to C.\]13.2 Plonky2 的处理
把 verifier data 放入 cyclic circuit 的 public inputs:
\[\operatorname{PI}_{\mathrm{vk}} = \bigl( d_C, C_{\mathrm{const},\sigma,C} \bigr).\]电路内部:
- 从上一 proof 的 public inputs 取出 inner verifier data;
- 约束它等于本层公开的 verifier data;
- 条件验证上一 cyclic proof;
- base case 时改为验证一个 dummy/base proof。
构建结束后,外部再检查:
\[\operatorname{PI}_{\mathrm{vk}} = vk_C.\]源码中的 check_cyclic_proof_verifier_data 正是这一额外检查。它不能被省略。
flowchart TB
VK["本电路 verifier data<br/>作为 public inputs"] --> EQ["相等性约束"]
PREV["上一 cyclic proof 的 PI 中的 VK"] --> EQ
PREV --> COND{"是否递归分支"}
BASE["dummy / base proof"] --> COND
COND --> VER["条件 recursive verification"]
EQ --> VER
VER --> NEXT["下一层 cyclic proof"]
13.3 base case 不是“不验证”
静态 circuit 不能在 base layer 删除整个 verifier 子电路。常见做法是:
\[b\cdot \mathsf{Verify}(\pi_{\mathrm{prev}}) + (1-b)\cdot \mathsf{Verify}(\pi_{\mathrm{dummy}}) =1.\]或者对验证结果/inputs 做条件选择。
必须同时约束
\[b(b-1)=0.\]base proof 也必须是对固定 dummy circuit/data 的有效 proof,不能用未约束垃圾 witness 代替。
14. aggregation、compression 与 cyclic recursion 不同
| 概念 | 输入 | 输出 statement | 是否有时间顺序 |
|---|---|---|---|
| recursive composition | 一个 inner proof | “inner proof valid” | 可有可无 |
| cyclic recursion | 同一 circuit 的上一 proof | 累积状态/链 | 通常有 |
| aggregation | 多个独立 proofs | “全部 children valid” | 不要求 |
| compression | 任意大/宽 proof | 更小或固定形状 proof | 不要求 |
二叉聚合树:
flowchart TB
P0["base proof 0"] --> A0["aggregate"]
P1["base proof 1"] --> A0
P2["base proof 2"] --> A1["aggregate"]
P3["base proof 3"] --> A1
A0 --> R["root aggregate"]
A1 --> R
R --> F["最终 proof"]
若每个 aggregation circuit 验证两个 children,其工作量近似翻倍,但树深只有
\[\lceil\log_2m\rceil.\]同一层的 nodes 可并行 proving。
15. proof size 为什么能变成常数
假设 base computation 被分成 $T$ 个 chunks,各产生 proof:
\[\pi_0,\ldots,\pi_{T-1}.\]树形递归后最终 root 仍是一份固定 Plonky2 proof,其尺寸主要取决于:
- cap 数量与 cap height;
- query 数;
- FRI layers 与 arity;
- opening widths;
- final polynomial;
- public-input 长度。
它不直接随 $T$ 线性增长:
\[|\pi_{\mathrm{root}}| = O_{\text{fixed config}}(1).\]这里的常数可以是几十 KB,而不是 KZG proof 的几百 bytes。succinctness 是渐近性质,不等于“任何系统 proof 都一样短”。
16. ZK 在递归层中的传播
考虑:
\[\pi_{\mathrm{out}} \text{ proves } \mathsf{Verify}(\pi_{\mathrm{in}})=1.\]若 inner proof 是 ZK,但 outer circuit 使用非 ZK 配置,outer proof 仍可能泄露:
- inner proof targets 的函数;
- verifier execution trace 的内部 values;
- outer circuit 其他 private witness。
反过来,outer proof 是 ZK 也不会修复 已经向同一观察者公开 的 inner proof 或 transcript 所造成的泄露。这里必须先明确攻击者能看见什么,而不能笼统要求“每一层都 ZK”。
16.1 inner proof 只作为私有 witness
令 outer relation 为
\[R_{\mathrm{out}}(x_{\mathrm{out}};x_{\mathrm{in}},\pi_{\mathrm{in}}) = [\mathsf{Verify}_{\mathrm{in}}(x_{\mathrm{in}},\pi_{\mathrm{in}})=1] \land [x_{\mathrm{out}}=g(x_{\mathrm{in}})].\]若 $\pi_{\mathrm{in}}$ 从未向 external verifier 发布,只在可信 proving boundary 内作为 outer private witness,则 inner scheme 本身不必是 ZK,outer wrapper 仍可对外提供 witness privacy。一个清晰的充分条件是
\[\boxed{ \mathsf{ZK}_{\mathrm{wrapper,external}} \Leftarrow \mathsf{ZK}_{\mathrm{outer}} \land \mathsf{PrivateToExternal}(\pi_{\mathrm{in}}) \land \mathsf{SafeProjection}(g). }\]这里 SafeProjection 表示 outer public inputs 只公开应用确实允许公开的 $g(x_{\mathrm{in}})$;outer ZK 隐藏的是其余 private witness,包括 $\pi_{\mathrm{in}}$。
16.2 inner proof 被发布或交给不可信聚合者
若 $\pi_{\mathrm{in}}$ 或其 transcript 被单独发布,outer ZK 无法撤回已经发生的泄露,此时还要针对能看到 inner proof 的观察者要求并审计
\[\mathsf{ZK}_{\mathrm{inner}} \land \mathsf{SafePublicStatement}_{\mathrm{inner}}.\]同理,即使最终只发布 outer proof,若 inner proof 先交给了不可信 aggregation service,则 inner ZK 可能是保护 witness 不受该聚合者观察所必需的。隐私结论应按“外部 verifier、聚合者、proof storage”等观察者分别表述,而不是无条件写成 $\bigwedge_i\mathsf{ZK}_i$。
特别注意:standard_recursion_config() 默认 zero_knowledge=false;若要隐私,应明确选用、审计并测试 ZK config。
17. 一个递归 verifier 的成本模型
可把 gate rows 粗分为:
\[G_{\mathrm{rec}} \approx G_{\mathrm{transcript}} +G_{\mathrm{Merkle}} +G_{\mathrm{FRI}} +G_{\mathrm{PLONK}} +G_{\mathrm{routing}}.\]其中:
17.1 Merkle 项
\[G_{\mathrm{Merkle}} \approx N_{\mathrm{paths}} \cdot(h-c) \cdot G_H.\]17.2 FRI interpolation 项
若第 $i$ 层 arity 为 $a_i$:
\[G_{\mathrm{FRI}} \approx q\sum_i \bigl( G_{\mathrm{interp}}(a_i) +G_{\mathbb E} \bigr).\]17.3 PLONK identity 项
主要依赖:
- wire openings 数;
- routed wires;
- partial-product 数;
- gate/lookup constraint 数;
- parallel challenges 数。
这个模型解释参数交换:
- cap 更高:proof commitment 大,Merkle gates 少;
- arity 更高:每轮插值贵,轮数少;
- rate 更低:LDE/Merkle 大,但 query soundness 更强;
- query 更少:电路小,但 soundness 下降;
- hash 更便于门化:递归快,但必须重新评估密码安全。
18. native hash 与 in-circuit hash 的接口
固定 Plonky2 源码的实际调用关系是:
| 接口 | 固定源码中的职责 |
|---|---|
C::Hasher | Merkle tree/cap,以及 native Challenger<F, C::Hasher> 和 circuit 内 RecursiveChallenger<F, C::Hasher, D> |
C::InnerHasher | 对 public inputs 求 hash;所得 digest 再作为一个 transcript message 被 C::Hasher challenger 吸收 |
AlgebraicHasher | 表示该 hash 有可在 arithmetic circuit 中约束的 permutation/gadget |
因此,尽管 GenericConfig 中关于 InnerHasher 的历史注释容易使人误读,challenge derivation 的可执行源码使用的是 C::Hasher,不是 C::InnerHasher。
PoseidonGoldilocksConfig 令二者都是 Poseidon。KeccakGoldilocksConfig 则令 Hasher = KeccakHash<25>、InnerHasher = PoseidonHash,所以该配置的 native Merkle 和 transcript challenger 都是截断 Keccak,只有 public-input hash 是 Poseidon;不能把它描述成 Poseidon transcript。
若要递归验证某个 proof,outer circuit 必须能表达 inner C::Hasher。固定 recursive verifier 的类型约束明确要求
1
C::Hasher: AlgebraicHasher<F>
仅实现 native Hasher 而没有相应 algebraic circuit gadget,不足以递归。因而 stock KeccakGoldilocksConfig 可用于 native proving/verifying,却不能直接通过这条递归 verifier 路径实例化。
这也是为什么“换成链上喜欢的 Keccak”可能:
- native verifier 更兼容;
- recursive verifier 却显著更大。
19. 伪代码:构建一个安全的聚合层
下面只表达安全结构,不对应可直接编译 API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn build_aggregate(
left_common: CommonCircuitData,
left_vk: VerifierOnlyData,
right_common: CommonCircuitData,
right_vk: VerifierOnlyData,
) -> Circuit {
let mut b = CircuitBuilder::new(zk_recursion_config());
let left = b.add_virtual_proof_with_pis(left_common);
let right = b.add_virtual_proof_with_pis(right_common);
// VK 固定为 circuit constants,避免 prover 换 statement。
b.verify_proof(left, constant(left_vk), left_common);
b.verify_proof(right, constant(right_vk), right_common);
// 显式提取并绑定 children statements。
let left_statement = parse_and_check_left_pis(left.public_inputs);
let right_statement = parse_and_check_right_pis(right.public_inputs);
let root = hash_domain_separated(
"aggregate-v1",
left_statement,
right_statement,
);
b.register_public_inputs(root);
b.build()
}
需要注意:
- left/right 的位置进入 domain-separated hash;
- inner VK 固定;
- public input schema 固定;
- outer config 显式启用 ZK;
- proof shape 来自可信 common data,而非任意 proof bytes。
20. 常见递归错误
20.1 只验证 proof,不检查 public inputs
结果证明“某个 statement 成立”,而不是目标 statement。
20.2 verifier key 是私有且未约束
prover 可换成自己选择的 circuit。
20.3 transcript 漏吸收一个字段
native 与 recursive verifier 可能对同一 proof 产生不同挑战,或出现 transcript collision。
20.4 shape 从不可信 proof 推导
可导致:
- 少验证 Merkle 层;
- 少验证 query;
- opening width 混淆;
- verifier circuit 与部署 policy 不一致。
20.5 把 BoolTarget::new_unsafe 当作免费布尔约束
“unsafe bool target”只是一种类型包装;必须追溯它的来源是否已经通过 bit decomposition/range constraints 保证为 0 或 1。
20.6 base case 跳过约束
静态 circuit 中的 conditional branch 必须约束 condition 与两边数据。不能因为应用说“这是第一层”就省略 circuit 内的代数绑定。
20.7 默认递归配置被误称为 ZK
默认 standard_recursion_config 明确关闭 ZK。
20.8 用历史 benchmark 预测当前机器
技术稿中的约 300 ms、43 KB 等数字对应特定 2021 MacBook Air、参数和旧代码。它们只能说明当时的设计点,不能作为当前 SLA。
21. 与 curve-cycle / folding 路线的本质差异
Plonky2 递归是:
\[\boxed{ \text{完整 verifier-in-circuit recursive composition}. }\]它没有像 Halo 那样主要累积 curve opening claims,也没有像 Nova 那样把两个 relaxed instances 做线性 folding。
| 路线 | 每层主要工作 | 最终需要 |
|---|---|---|
| Plonky2 recursion | 完整检查 Merkle + FRI + PLONK identity | 一份 Plonky2 proof |
| Halo accumulation | 累积 group/PCS opening claim | decider |
| Nova folding | fold relaxed R1CS instance/witness commitments | decider/compression |
Plonky2 的优势来自 verifier 本身被精心设计成 small-field/hash-friendly,而不是绕开 verifier。
22. 状态与使用边界
截至资料快照:
- Plonky2 官方仓库已弃用;
- 代码曾在
v1.0.0前接受审计,但审计不等于无漏洞; - 默认 FRI 目标是 conjectured 100 bit;
- 默认 Poseidon 参数曾被官方 README 提示可能约 95 bit;
- 新系统应优先评估 Plonky3 及其当前递归项目,而非直接复制 Plonky2 默认值。
已有系统若继续使用,应至少固定并记录:
\[\begin{aligned} &\text{commit hash},\quad \text{Rust toolchain},\quad \text{field/hash config},\\ &\text{FRI params},\quad \text{ZK flag},\quad \text{proof encoding},\quad \text{audit delta}. \end{aligned}\]23. 总结
Plonky2 递归快的因果链是:
flowchart LR
F["内外同一 Goldilocks 域"] --> A["无大规模 non-native field"]
H["Poseidon 整置换 custom gate"] --> M["Merkle verifier 行数下降"]
C["Merkle caps"] --> M
R["固定 FRI arity"] --> I["插值 gate 可复用"]
A --> T["较低 recursion threshold"]
M --> T
I --> T
T --> P["固定形状递归 proof"]
但安全 statement 只有在以下对象都被绑定时才完整:
\[\boxed{ \text{proof} +\text{public inputs} +\text{inner circuit identity} +\text{common data} +\text{protocol parameters}. }\]24. 一手资料
- Plonky2 technical writeup
- 固定 recursive verifier source(
C::Hasher: AlgebraicHasher) - cyclic recursion source
- proof structure
- 固定 transcript challenge derivation(
C::Hasher) - 固定 hash configurations
- official deprecation and security notes
上一篇:Plonky2 算术化与 FRI
下一篇:Plonky2.5 跨系统递归桥
返回:总目录