# Circom 语言教程与 circomlib 演示

Circom 语言教程与 circomlib 演示

## 关于生产使用的说明

Circom 是学习 zk-snarks 的绝佳工具。然而，由于它是相当底层的，因此意外添加微妙的错误的可能性更多。在实际应用中，程序员应考虑使用更高级的零知识编程语言。在部署持有用户资金的智能合约之前，你应始终进行审计，但对于 zk 电路来说，这一点尤为重要，因为攻击向量不太为人所知。

## 安装

``npm install -g snarkjs@latest``

## Hello World

zk-circuits 的 Hello World 是进行乘法运算，所以我们从这里开始。

``````pragma circom 2.1.6;

template Multiply() {
signal input a;
signal input b;
signal output out;

out &lt;== a * b;
}

component main = Multiply();``````

``````\$ circom multiply.circom
template instances: 1
Everything went okay, circom safe``````

## 生成 R1CS 文件

``circom multiply.circom --r1cs --sym``

--r1cs 标志告诉 circom 生成一个 R1CS 文件，--sym 标志表示“保存变量名”。这很快就会变得清楚。

``````multiply.r1cs
multiply.sym``````

``snarkjs r1cs print multiply.r1cs``

``[INFO]  snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.a ] * [ main.b ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.out ] = 0``

``-1 * a * b - (-1*out) = 0;``

``````-1 * a * b - (-1*out) = 0;
-1 * a * b = -1*out;
a * b = out;``````

## 不允许非二次约束！

``````pragma circom 2.1.6;

template Multiply() {
signal input a;
signal input b;
signal input c;
signal output out;

out &lt;== a * b * c;
}

component main = Multiply();``````

``````error[T3001]: Non quadratic constraints are not allowed!
┌─ "multiply.circom":9:3
│
9 │   out &lt;== a * b * c;
│   ^^^^^^^^^^^^^^^^^ found here
│
= call trace:
->Multiply

previous errors were found``````

### 拆分非二次约束

``````pragma circom 2.1.6;

template Multiply() {
signal input a;
signal input b;
signal input c;
signal s1;
signal output out;

s1 &lt;== a * b;
out &lt;== s1 * c;
}

component main = Multiply();``````

``````[INFO]  snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.a ] * [ main.b ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.s1 ] = 0
[INFO]  snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.s1 ] * [ main.c ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.out ] = 0``````

``````a * b  = s1
s1 * c = out``````

## 计算见证（witness）

``circom multiply.circom --r1cs --sym --wasm``

``{"a": "2","b": "3","c": "5"}``

``````signal input a;
signal input b;
signal input c;``````

``````node generate_witness.js multiply.wasm input.json witness.wtns

snarkjs wtns export json witness.wtns

cat witness.json
[
"1",
"30",
"2",
"3",
"5",
"6"
]``````

## 公共输入

### 动机：nullifier 方案

``````template SomePublic() {

signal input a;
signal input b;
signal input c;
signal v;
signal output out;

v &lt;== a * b;
out &lt;== c * v;
}

component main {public [a, c]} = SomePublic();``````

## Circom 中的数组

``````pragma circom 2.1.6;

template Powers(n) {
signal input a;
signal output powers[n];

powers[0] &lt;== a;
for (var i = 1; i &lt; n; i++) {
powers[i] &lt;==  powers[i - 1] * a;
}
}
component main = Powers(6);``````

### 变量

``````pragma circom 2.1.6;

template Powers() {
signal input a;
signal output powers[6];

powers[0] &lt;== a;
powers[1] &lt;== powers[0] * a;
powers[2] &lt;== powers[1] * a;
powers[3] &lt;== powers[2] * a;
powers[4] &lt;== powers[3] * a;
powers[5] &lt;== powers[4] * a;

}
component main = Powers();``````

### 信号（Signal） vs 变量（variable）

`&lt;--``&lt;==``===`操作符用于信号，而不是变量。我们将在稍后解释这些不熟悉的操作符。

``````signal a;
a = 2; // using a variable assignment for a signal

var v;
v &lt;-- a + b; // using a signal assignment for a variable is not allowed``````

## === 与 <==

``````pragma circom 2.1.6;

template Multiply() {
signal input a;
signal input b;
signal output c;

c &lt;-- a * b;
c === a * b;
}

template MultiplySame() {
signal input a;
signal input b;
signal output c;

c &lt;== a * b;
}``````

`&lt;==`操作符先计算，然后赋值，然后添加约束。如果你只想约束，使用`===`

``````pragma circom 2.1.6;

template Multiply() {
signal input a;
signal input b;
signal input c;

c === a * b;
}

component main {public [c]} = Multiply();``````

Circom 不需要存在输出信号，因为那只是对公共输入的一种语法糖。请记住，从零知识证明的角度来看，“输入”只是见证向量的一个条目，因此从零知识证明的角度来看，一切都是输入。在上面的示例中，没有输出信号，但这是一个具有适当约束的完全有效的电路。

## 将模板连接在一起

Circom 模板是可重用和可组合的，如下面的示例所示。在这里，Square 是 SumOfSquares 使用的一个模板。请注意，输入 a 和 b 如何“连接（wired）”到组件 Square()。

``````pragma circom 2.1.6;

template Square() {

signal input in;
signal output out;

out &lt;== in * in;
}

template SumOfSquares() {
signal input a;
signal input b;
signal output out;

component sq1 = Square();
component sq2 = Square();

// 连接（wiring）组件
sq1.in &lt;== a;
sq2.in &lt;== b;

out &lt;== sq1.out + sq2.out;
}

component main = SumOfSquares();``````

## 组件的多个输入

``````template Mul {

signal input in[2]; // takes two inputs
signal output out; // single output

out &lt;== in[0] * in[1];
}``````

## 不安全的 Powers，请注意`&lt;--`

``````pragma circom 2.1.6;

template Powers {
signal input a;
signal output powers[6];

powers[0] &lt;== a;
powers[1] &lt;== a * a;
powers[2] &lt;-- a ** 3;
powers[3] &lt;-- a ** 4;
powers[4] &lt;-- a ** 5;
powers[5] &lt;-- a ** 6;

}
component main = Powers();``````

``````(base) ➜  hello-circom circom bad-powers.circom --r1cs
template instances: 1
non-linear constraints: 1 ### only one constraint ###
linear constraints: 0
public inputs: 0
public outputs: 6
private inputs: 1
private outputs: 0
wires: 7
labels: 8
Written successfully: ./powers.r1cs
Everything went okay, circom safe``````

## Circomlib

Iden3 维护着一个有用的 circom 模板仓库，称为 circomlib。此时，我们已经掌握了足够的 Circom 知识，可以开始学习这些模板了。现在是介绍一个简单但有用的模板，演示了 `&lt;--` 的使用的好时机。

## IsZero

IsZero 模板在输入信号为零时返回 1，非零时返回零。

``````template IsZero() {
signal input in;
signal output out;

signal inv;

inv &lt;-- in!=0 ? 1/in : 0;

out &lt;== -in*inv +1;
in*out === 0;
}``````

out: {1,0}

inv: {0, in^{-1}, invalid}

in: {0, non-zero}

## 在约束信号乘积时，请使用 IsEqual 模板而不是===

``````template IsEqual() {
signal input in[2];
signal output out;

component isz = IsZero();

in[1] - in[0] ==> isz.in;

isz.out ==> out;
}``````

## 如果操作不是二次的，===和<==将不会创建约束

``````template FactorOfFiveFootgun() {

signal input in;
signal output out;

out &lt;== in * 5;
}

component main = FactorOfFiveFootgun();``````

``````(base) ➜  hello-circom circom footgun.circom --r1cs
template instances: 1
non-linear constraints: 0 ### no constraints ###
linear constraints: 0
public inputs: 0
public outputs: 1
private inputs: 1
private outputs: 0
wires: 2
labels: 3
Written successfully: ./footgun.r1cs
Everything went okay, circom safe``````

## 示例：计算平均值

n 个信号的平均值等于它们的总和除以信号的数量。在有限域中，除法等同于乘以倒数，因此我们需要将信号相加，然后乘以数组长度的倒数。

``````pragma circom 2.1.6;

include "node_modules/circomlib/circuits/comparators.circom";

template AverageWrong(n) {

signal input in[n];
signal denominator;
signal output out;

// sum up the inputsvar sum;
for (var i = 0; i &lt; n; i++) {
sum += in[i];
}

// compute the denominator
denominator_inv &lt;-- 1 / n;

// "force" the denominator to be equal to the inverse of n1 === denominator_inv * n; // this does not create a constraint!// sum / denominator
out &lt;== sum * denominator_inv;
}

component main  = AverageWrong(5);``````

``````pragma circom 2.1.6;

include "node_modules/circomlib/circuits/comparators.circom";

template Average(n) {

signal input in[n];
signal denominator_inv;
signal output out;

var sum;
for (var i = 0; i &lt; n; i++) {
sum += in[i];
}

denominator_inv &lt;-- 1 / n;

component eq = IsEqual();
eq.in[0] &lt;== 1;
eq.in[1] &lt;== denominator_inv * n;

out &lt;== sum * denominator_inv;

}

component main  = Average(5);``````

``````[INFO]  snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.in[0] +21888242871839275222246405745257275088548364400416034343698204186575808495616main.in[1] +21888242871839275222246405745257275088548364400416034343698204186575808495616main.in[2] +21888242871839275222246405745257275088548364400416034343698204186575808495616main.in[3] +21888242871839275222246405745257275088548364400416034343698204186575808495616main.in[4] ] * [ main.denominator ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.out ] = 0

[INFO]  snarkJS: [ 51 +21888242871839275222246405745257275088548364400416034343698204186575808495616main.denominator ] * [ main.eq.isz.inv ] - [ 1 +21888242871839275222246405745257275088548364400416034343698204186575808495616main.eq.out ] = 0

[INFO]  snarkJS: [ 51 +21888242871839275222246405745257275088548364400416034343698204186575808495616main.denominator ] * [ main.eq.out ] - [  ] = 0``````

## circom 中的函数

``````pragma circom 2.1.6;

include "node_modules/circomlib/circuits/comparators.circom";

function invert(x) {
return 1 / x;
}

template Average(n) {

signal input in[n];
signal denominator;
signal output out;

var sum;
for (var i = 0; i &lt; n; i++) {
sum += in[i];
}

denominator &lt;-- invert(n);

component eq = IsEqual();
eq.in[0] &lt;== denominator;
eq.in[1] &lt;== n;

out &lt;== sum * denominator;
}

component main  = Average(5);``````

## 约束取决于条件的值，而在约束生成阶段可能是未知的

``````template IsOver21() {
signal input age;
signal output oldEnough;

if (age >= 21) {
oldEnough &lt;== 1;
} else {
oldEnough &lt;== 0;
}
}``````

a > b

## Num2Bits

``z = (10,000 + x) - y``

Circom 在二进制表示中做的事情与十进制表示相同。

``````template Num2Bits(n) {
signal input in;
signal output out[n];
var lc1=0;
// this serves as an accumulator to "recompute" in bit-by-bit
var e2=1;
for (var i = 0; i&lt;n; i++) {
out[i] &lt;-- (in >> i) & 1;
out[i] * (out[i] -1 ) === 0; // force out[i] to be 1 or 0
lc1 += out[i] * e2; //add to the accumulator if the bit is 1
e2 = e2+e2; // takes on values 1,2,4,8,...
}

lc1 === in;
}``````

## LessThan

``````template LessThan(n) {
assert(n &lt;= 252);
signal input in[2];
signal output out;

component n2b = Num2Bits(n+1);

n2b.in &lt;== in[0] + (1&lt;&lt;n) - in[1];

out &lt;== 1-n2b.out[n];
}``````

## Over21 功能示例

Circomlib 提供了一个名为GreaterThan的比较器，它是 LessThan 的简单变体，因此我们不会在这里解释它。有兴趣的读者可以直接查阅源代码。

``npm install circomlib``

``````pragma circom 2.1.6;

include "node_modules/circomlib/circuits/comparators.circom";

template Over21() {

signal input age;
signal input ageLimit;
signal output oldEnough;

// 8 bits is plenty to store age
component gt = GreaterThan(8);
gt.in[0] &lt;== age;
gt.in[1] &lt;== 21;

oldEnough &lt;== gt.out;
}

component main = Over21();``````

## Comparators.circom

• IsZero
• IsEqual（将两个输入相减并将结果传递给 IsZero）
• LessThan
• LessEqThan（由 LessThan 派生）
• GreaterThan（由 LessThan 派生）
• GreaterEqThan（由 LessThan 派生）
• ForceEqualIfEnabled

## ForceEqualIfEnabled

``````template ForceEqualIfEnabled() {
signal input enabled;
signal input in[2];

component isz = IsZero();

in[1] - in[0] ==> isz.in;

(1 - isz.out)*enabled === 0;
}``````

## Circom assert

assert 语句不会添加任何约束，它只是对非恶意证明者的输入进行一次健全性检查。

## 布尔运算符

``````template And() {

signal input in[2];
signal output c;

c &lt;== in[0] && in[1];
}``````

``````template And() {
signal input in[2];
signal output c;

// force inputs to be zero or one
in[0] === in[0] * in[0];
in[1] === in[1] * in[1];

// c will be 1 iff in[0] and in[1] are 1
c &lt;== in[0] * in[1];
}``````

## ZK 友好的哈希函数

``````pragma circom 2.0.0;
include "node_modules/circomlib/circuits/sha256/sha256.circom";

component main = Sha256(256);``````

``````(base) ➜  hello-circom circom Sha256-example.circom --r1cs
template instances: 99
non-linear constraints: 29380 ### WOW! ###
linear constraints: 0
public inputs: 0
public outputs: 256
private inputs: 256
private outputs: 0
wires: 29325
labels: 204521
Written successfully: ./Sha256-example.r1cs
Everything went okay, circom safe``````

• Mimc
• Pedersen
• Poseidon

ZK 友好的哈希函数是一个庞大的主题，最好单独讨论，因此我们将其推迟到以后的文章中。

0x9e64...7c84