文章

10. Plonky2 递归原理与工程:在电路中验证 PLONK + FRI 证明

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_capwire polynomials 的 LDE Merkle cap
plonk_zs_partial_products_cappermutation $Z$、partial products 及相关多项式 cap
quotient_polys_capquotient 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,
);

其含义是:

  1. inner_common_data 的固定 shape 分配 targets;
  2. 把每个 Merkle digest、opening、FRI step 变成 witness variables;
  3. 在 circuit 中添加所有 verifier constraints;
  4. 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 完全相同的顺序吸收:

  1. FRI 参数;
  2. inner circuit digest;
  3. public-input hash;
  4. wire cap;
  5. $Z$/partial-product cap;
  6. quotient cap;
  7. openings;
  8. 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:

  1. Merkle tree authentication;
  2. 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).\]

电路内部:

  1. 从上一 proof 的 public inputs 取出 inner verifier data;
  2. 约束它等于本层公开的 verifier data;
  3. 条件验证上一 cyclic proof;
  4. 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::HasherMerkle 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 claimdecider
Nova foldingfold relaxed R1CS instance/witness commitmentsdecider/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 算术化与 FRI
下一篇:Plonky2.5 跨系统递归桥
返回:总目录

本文由作者按照 CC BY 4.0 进行授权