dusk-plonk电路构建核心解析:Composer、Witness与约束系统概念全解
【免费下载链接】plonkPure Rust implementation of the PLONK ZKProof System done by the Dusk team项目地址: https://gitcode.com/gh_mirrors/plo/plonk
dusk-plonk 是 Dusk 团队用纯 Rust 实现的 PLONK 零知识证明(ZKProof)系统,运行在 BLS12-381 曲线之上,内置 KZG10 多项式承诺方案。要在这个库里"搭电路、写证明",绕不开的三大核心概念就是Composer(电路构建器)、Witness( witness 变量)和约束系统(Constraint System)。本文用尽量少的代码,带你一次性把这三者的职责和关系讲清楚,帮你快速上手 PLONK 电路构建。
一张图看懂 dusk-plonk 的核心流程
在深入细节之前,先记住这条主线(出自项目架构约定 CLAUDE.md):
Circuit(电路定义) → Composer(约束系统 + Witness 分配) → Compiler(编译) → ProverKey / VerifierKey(证明/验证密钥) → Proof(证明)→ Verify(验证)| 阶段 | 核心类型 | 职责 |
|---|---|---|
| 电路定义 | Circuittrait | 声明"要证明什么" |
| 电路构建 | Composer | 分配 witness、追加 gate 约束 |
| 编译 | Compiler | 生成 Prover / Verifier |
| 证明与验证 | Proof | 生成并校验零知识证明 |
所有公开入口都集中在 prelude 模块中,见 src/prelude.rs,日常只需要use dusk_plonk::prelude::*就能拿到Composer、Witness、Constraint、Compiler等全部主力类型。
Composer:电路构建的总指挥
Composer是整个约束系统的"工地现场",定义在 src/composer.rs 中。它内部维护了五样东西:
constraints: Vec<Gate>—— 所有已追加的门(约束行)witnesses: Vec<BlsScalar>—— 所有 witness 的实际取值public_inputs—— 稀疏存储的公共输入(行号 → 值)perm—— 置换参数(permutation),用于 PLONK 的连线证明runtime—— 调试运行时,输出 CDF 事件
初始化:为什么第一个 Composer 不是空的?
调用Composer::initialized()时,系统会自动做三件事:
- 分配第 0 号 witness 为
0、第 1 号 witness 为1(见 src/composer.rs 中的ZERO/ONE常量),这是 turbo composer 的硬性约定:任何电路的 wire 0、wire 1 永远是零和一; - 用断言把
0、1这两个常量"钉死"在电路里,防止被恶意 prover 篡改; - 追加两个 dummy gate(见
append_dummy_gates),防止 witness 多项式退化为零多项式、防止置换退化为恒等置换——这是 PLONK 完备性的关键细节。
所以一个"空电路"其实已经有 4 个约束了。之后你再调用composer.constraints()统计大小时,要记得把这部分算进去。
常用构建 API 速查
| 方法 | 作用 |
|---|---|
append_witness(x) | 分配一个新的私有 witness,返回Witness索引 |
append_public(x) | 分配 witness 并追加一个 gate,将其标记为公共输入 |
append_constant(x) | 分配 witness 并断言它等于常量 x |
gate_add(s)/gate_mul(s) | 追加一行算术 gate 并自动算出输出 witness |
assert_equal(a, b) | 追加 gate 约束 a == b |
assert_equal_constant(a, c, p) | 约束 a == c,可选携带公共输入 p |
component_range_bits::<N>(a) | 范围约束,证明 a 是一个 N 位数 |
一个典型的加法约束长这样(来自示例 examples/circuit.rs):
let constraint = Constraint::new() .left(1) .right(1) .a(a) .b(b) .constant(BlsScalar::from(42)); let result = composer.gate_add(constraint);gate_add的内部逻辑是:把q_O设为 -1,求解c = q_L·a + q_R·b + q_F·d + q_C + PI,把c作为新 witness 追加进电路,再追加一行 gate(见append_evaluated_output实现)。注意源码里的注释特别强调:这一行 gate 就是健全性边界——少了它,恶意 prover 就能悄悄替换输出值。
Witness:电路里的"变量盒"
Witness定义在 src/composer/constraint_system/witness.rs。它本身非常简单——只封装了一个索引:
pub struct Witness { index: usize }可以把它理解为电路中的"接线端子":你手里拿到的不是数值本身,而是一个指向witnesses数组的号码牌。真正的值由 prover 在证明时填入,由约束保证它必须正确。
两个内置常量值得记住:
Witness::ZERO(索引 0)和Witness::ONE(索引 1)——对应初始化时钉死的 0 和 1;Witness的Default值就是ZERO。
每个 gate 有 4 根 wire(左 a、右 b、输出 c、第四 d),wire 属于哪个 gate 由WireData枚举(Left(n)/Right(n)/Output(n)/Fourth(n))描述。置换参数perm正是靠追踪这些 wire 的流向,来保证"上一行的输出接到下一行的输入"这一布线正确性。
约束系统与 Gate:一行多项式评估
这是三件套中最"数学"的一环。核心类型有两个,都在 src/composer/constraint_system/constraint.rs:
Constraint:约束的"草稿"
Constraint是一个 12 个系数 + 4 个 witness 槽位的结构体,通过链式方法配置:mult(1).left(1).a(x).b(y)等。系数通过Selector枚举寻址,常见的外部选择器有:
| Selector | 符号 | 含义 |
|---|---|---|
Multiplication | q_M | 乘积项系数 |
Left/Right | q_L / q_R | 左、右 wire 系数 |
Output | q_O | 输出 wire 系数 |
Fourth | q_F | 第四 wire(advice)系数 |
Constant | q_C | 常数项 |
PublicInput | PI | 公共输入项 |
剩下的Arithmetic、Range、Logic、GroupAddFixedBase、GroupAddVariableBase是 dusk-plonk 特有的内部选择器,用来区分普通算术 gate 与 range/logic/ECC 加速 gate——这也是它相比标准 PLONK 的"自定义门"优化所在(完整规格见 docs/dusk-plonk-specs.pdf)。
Gate:约束的"成品"
Composer::append_gate会把Constraint固化成一个Gate(定义在 src/composer/gate.rs)。算术 gate 强制执行的等式是:
q_M·a·b + q_L·a + q_R·b + q_O·c + q_F·d + q_C + PI = 0所有 Gate 按行排成一列,就是整条电路。每一列 wire(a/b/c/d)构成一个 witness 多项式,配合 KZG10 承诺(见 src/commitment_scheme/kzg10/)和 FFT(src/fft/),最终被打包成 PLONK 证明。
从示例电路串联三大概念
回到 examples/circuit.rs 中的TestCircuit,它证明了五件事:a < 2^6、b < 2^4、a + b + 42 = c(c 为公共输入)、a·b + d = 42、以及一次 JubJub 曲线上的标量乘法。整个circuit方法就是三大概念的完整演练:
- 分配 Witness:
composer.append_witness(self.a)拿到a的端子; - 加约束:
component_range_bits::<6>(a)用一组 gate 锁住 a 的位宽;gate_add+assert_equal完成加法; - 暴露公共输入:
composer.append_public(self.c)让验证者无需信任 prover 就知道 c 的值。
证明与验证只需三行:Compiler::compile::<TestCircuit>(&pp, label)生成 prover/verifier,prover.prove(&mut rng, &circuit)产出Proof,verifier.verify(&proof, &public_inputs)收尾。
调试与进阶:让电路构建更透明
- CDF 调试器:开启
debugfeature 后,运行时事件(witness 分配、约束追加)会输出到 CDF 文件(路径由CDF_OUTPUT环境变量指定),实现位于 src/debugger.rs,方便你逐行分析 gate 构成; - 电路压缩:
Circuit::compress()可以把电路序列化为字节串,之后直接编译而无需重跑电路定义,适合嵌入式场景; - 正确性测试:tests/ 目录按功能拆分了
boolean、range、logic、ecc等集成测试,是理解每类 gate 行为的最好教材; - 性能参考:README 给出的基准是 2^16 约束规模下证明约 7.9 秒、验证约 2.8 毫秒(验证时间与电路规模无关);跑测试记得加
--release,debug 模式可能慢上百倍。
总结:三句话记住 dusk-plonk 的电路构建
| 概念 | 一句话定位 | 关键文件 |
|---|---|---|
| Composer | 电路工地:分配变量、追加 gate 的总指挥 | src/composer.rs |
| Witness | 变量端子:索引即身份,0/1 号永远钉死 | src/composer/constraint_system/witness.rs |
| 约束系统 | 每行 gate 是一个多项式等式,整体构成电路 | src/composer/constraint_system/constraint.rs |
抓住Circuit → Composer → Compiler → Proof这条主线,再对照 examples/circuit.rs 动手改一改,你就能快速从"读懂 dusk-plonk"过渡到"用 dusk-plonk 写自己的零知识电路"。
【免费下载链接】plonkPure Rust implementation of the PLONK ZKProof System done by the Dusk team项目地址: https://gitcode.com/gh_mirrors/plo/plonk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考