dusk-plonk电路构建核心解析:Composer、Witness与约束系统概念全解
2026/8/25 17:37:11 网站建设 项目流程

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::*就能拿到ComposerWitnessConstraintCompiler等全部主力类型。

Composer:电路构建的总指挥

Composer是整个约束系统的"工地现场",定义在 src/composer.rs 中。它内部维护了五样东西:

  • constraints: Vec<Gate>—— 所有已追加的门(约束行)
  • witnesses: Vec<BlsScalar>—— 所有 witness 的实际取值
  • public_inputs—— 稀疏存储的公共输入(行号 → 值)
  • perm—— 置换参数(permutation),用于 PLONK 的连线证明
  • runtime—— 调试运行时,输出 CDF 事件

初始化:为什么第一个 Composer 不是空的?

调用Composer::initialized()时,系统会自动做三件事:

  1. 分配第 0 号 witness 为0、第 1 号 witness 为1(见 src/composer.rs 中的ZERO/ONE常量),这是 turbo composer 的硬性约定:任何电路的 wire 0、wire 1 永远是零和一;
  2. 用断言把01这两个常量"钉死"在电路里,防止被恶意 prover 篡改;
  3. 追加两个 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;
  • WitnessDefault值就是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符号含义
Multiplicationq_M乘积项系数
Left/Rightq_L / q_R左、右 wire 系数
Outputq_O输出 wire 系数
Fourthq_F第四 wire(advice)系数
Constantq_C常数项
PublicInputPI公共输入项

剩下的ArithmeticRangeLogicGroupAddFixedBaseGroupAddVariableBase是 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^6b < 2^4a + b + 42 = c(c 为公共输入)、a·b + d = 42、以及一次 JubJub 曲线上的标量乘法。整个circuit方法就是三大概念的完整演练:

  1. 分配 Witnesscomposer.append_witness(self.a)拿到a的端子;
  2. 加约束component_range_bits::<6>(a)用一组 gate 锁住 a 的位宽;gate_add+assert_equal完成加法;
  3. 暴露公共输入composer.append_public(self.c)让验证者无需信任 prover 就知道 c 的值。

证明与验证只需三行:Compiler::compile::<TestCircuit>(&pp, label)生成 prover/verifier,prover.prove(&mut rng, &circuit)产出Proofverifier.verify(&proof, &public_inputs)收尾。

调试与进阶:让电路构建更透明

  • CDF 调试器:开启debugfeature 后,运行时事件(witness 分配、约束追加)会输出到 CDF 文件(路径由CDF_OUTPUT环境变量指定),实现位于 src/debugger.rs,方便你逐行分析 gate 构成;
  • 电路压缩Circuit::compress()可以把电路序列化为字节串,之后直接编译而无需重跑电路定义,适合嵌入式场景;
  • 正确性测试:tests/ 目录按功能拆分了booleanrangelogicecc等集成测试,是理解每类 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询