作者:ZKSwap、Sin7Y 团队联合撰写 文章来源:zks.org
使用 Halo2 开发电路
Halo2 是 ECC 公司在 Halo 的基础上,使用 Plonk 对 Halo 进行升级改造,充分利用了 Plonk 的特性,比如 custom gate,Plonkup 等,使得用 Halo2 开发零知识证明电路更加高效和方便。关于 Halo2 的原理详情,请参见我们之前的文章 Halo2 原理详解。
Halo2 广泛使用在目前众多的 zkEVM 的方案中,我们在这篇文章中,以一个简单的例子为例,来介绍如何进行 Halo2 的电路开发。
Halo2 电路系统
在开始讲解例子之前,先说说 Halo2 的证明系统。Halo2 电路是由表(matrix/table)
定义,一般使用 matrix 的行(rows)
,列(columns)
,单元格(cells)
来表示各种特殊不同的意义。
一个电路取决于如下的配置:
- 有限域 F,单元格元素都是 F 域元素;
- 和另一个 ZKP 电路系统 bellman 不同,Halo2 的电路系统用 advice 列代表 witness,instance 列代表 public input,fixed 列代表电路中固定的部分,Plonk 中引用出来的 lookup table 就是在 fixed 列中,selector 列是 fixed 列的辅助字段,用于确定在什么条件下激活门;
- 一部分可用于相等性约束的列;
- 一个多项式阶上限;
- 一系列多项式约束,这些都是在有限域 F 上的多变量多项式,每行的结果都必须是 0。多项式约束中的变量可以是当前行的某一列的单元格,或者相对于当前行的某一列的某一单元格;
- 一系列在输入表达式(如上述所述的多变量多项式)和 table 列上定义的查找表,这个就是 Plonk 里的 plonkup;
- 一系列用于指定两个单元格有相同值的相等性约束,熟悉 Plonk 的同学知道,这其实就是拷贝约束;
关于 Halo2 的电路系统,参见下图:
其中:
- a 代表的黄色的列,是 advice column
- i 代表的红色的列,是 instance column
- f 代表的蓝色的列,是 fixed column
- s 代表的紫色的列,是 selector column
对于不同的 proof,advice 和 instance column 不同,这个好理解,因为每次生成不同的 proof,witness 和 public input 基本上不同;而 fixed 和 selector column 都是和电路有关,跟输入无关。
图片来自 Ultra Plonk arithmetisation (Halo2)
Halo2 提供的工具
- `Mock Prover`
用于调试电路的工具,可以在单元测试中,将电路中的每个约束都测试一遍。对 Halo2 的电路开发非常重要,我们在电路开发过程中,需要经常使用`halo2::dev::MockProver`来调试代码。如果某个约束不满足,则`run`报错,否则正常返回。
- 电路可视化工具
`circuit_layout` 可以用图表的形式展示电路的不同部分,如上面讲到的 advice/fixed/instance。
- 电路消耗检测工具
`cost-model`工具可以用来计算电路生成的 proof 大小,verify 花费的时间等。
Halo2 电路开发示例
如下,要开发一个电路, `a`, `b ` 作为 private input,`c` 作为 public input,来证明得到:
$$
a^2 * b^2 = c
$$
1. 定义 instructions
因为电路可用于任意场景的证明描述,比如小的判断大小,大的一个程序执行的正确性。在这儿电路需要证明上述等式,就需要定义上述场景的电路,在这儿是乘法。以 a 的平方为例,其可以看成是 a 乘以 a。最外面则是 a 的平方乘以 b 的平方。
并且我们电路处理的是两个数字的相乘,所以这个 instructions 输入和输出都是 Num。该接口如下:
trait NumericInstructions<F: FieldExt>: Chip<F> {
/// Variable representing a number.
type Num;
/// Loads a number into the circuit as a private input.
fn load_private(&self, layouter: impl Layouter<F>, a: Option<F>) -> Result<Self::Num, Error>;
/// Loads a number into the circuit as a fixed constant.
fn load_constant(&self, layouter: impl Layouter<F>, constant: F) -> Result<Self::Num, Error>;