circom试用

参考链接 ==> https://docs.circom.io/

安装

1
2
npm install -g circom
npm install -g snarkjs

构建电路

1.创建circuit.circom文件

1
2
3
4
5
6
7
8
template Multiplier() {
signal private input a;
signal private input b;
signal output c;
c <== a*b;
}

component main = Multiplier();

​ 此电路的目的是让我们向某人证明我们能够因式分解整数c。具体来说,使用此电路,我们将能够证明我们知道两个数字(a和b)相乘得到c,而不会显示a和b。这个电路有2个 private 输入信号,名为 ab ,还有一个输出 c。输入和输出使用<==运算符进行关联。在circom中,<==运算符做两件事。 首先是连接信号。 第二个是施加约束。在本例中,我们使用<==c连接到ab,同时将c约束为a * b的值,即电路做的事情是让强制信号 ca*b 的值。在声明 Multiplier 模板之后, 我们使用名为main的组件实例化它。注意:编译电路时,必须始终有一个名为main的组件。

2.编译电路

1
circom circuit.circom --r1cs --wasm --sym
  • --r1cs: 生成 circuit.r1cs ( r1cs 电路的二进制格式约束系统)
  • --wasm: 生成 circuit.wasm ( wasm 代码用来生成见证 witness 稍后再介绍)
  • --sym: 生成 circuit.sym (以注释方式调试和打印约束系统所需的符号文件)

r1cs是将代数电路转换为zk-snark的第一步。

构建零知识证明

​ 使用snarkjs 来生成和验证 zk-SNARK 证明。我们将证明能够分解数字33。也就是说,将证明我们知道两个整数a和b,以便将它们相乘时得出33。

1.查看电路有关的信息

​ 使用 snarkJS,您可以获得电路的一般统计数据并打印约束。

1
2
3
4
5
6
7
8
➜  circom snarkjs info -r circuit.r1cs 
[INFO] snarkJS: Curve: bn-128
[INFO] snarkJS: # of Wires: 4
[INFO] snarkJS: # of Constraints: 1
[INFO] snarkJS: # of Private Inputs: 2
[INFO] snarkJS: # of Public Inputs: 0
[INFO] snarkJS: # of Labels: 4
[INFO] snarkJS: # of Outputs: 1
1
2
➜  circom snarkjs print -r circuit.r1cs -s circuit.sym
[INFO] snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.a ] * [ main.b ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.c ] = 0

2.setup

2.1 设置的生成分为两个阶段:第一步是创建一些称为tau的幂的值。此处,我们将保留一个“ powers of tau”文件。 运行以下命令:

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
29
30
31
32
33
34
35
36
37
38
➜  circom snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
[DEBUG] snarkJS: Calculating First Challenge Hash
[DEBUG] snarkJS: Calculate Initial Hash: tauG1
[DEBUG] snarkJS: Calculate Initial Hash: tauG2
[DEBUG] snarkJS: Calculate Initial Hash: alphaTauG1
[DEBUG] snarkJS: Calculate Initial Hash: betaTauG1
[DEBUG] snarkJS: Blank Contribution Hash:
786a02f7 42015903 c6c6fd85 2552d272
912f4740 e1584761 8a86e217 f71f5419
d25e1031 afee5853 13896444 934eb04b
903a685b 1448b755 d56f701a fe9be2ce
[INFO] snarkJS: First Contribution Hash:
9e63a5f6 2b96538d aaed2372 481920d1
a40b9195 9ea38ef9 f5f6a303 3b886516
0710d067 c09d0961 5f928ea5 17bcdf49
ad75abd2 c8340b40 0e3b18e9 68b4ffef
➜ circom snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
Enter a random text. (Entropy): zhuangweiming
[DEBUG] snarkJS: Calculating First Challenge Hash
[DEBUG] snarkJS: Calculate Initial Hash: tauG1
[DEBUG] snarkJS: Calculate Initial Hash: tauG2
[DEBUG] snarkJS: Calculate Initial Hash: alphaTauG1
[DEBUG] snarkJS: Calculate Initial Hash: betaTauG1
[DEBUG] snarkJS: processing: tauG1: 0/8191
[DEBUG] snarkJS: processing: tauG2: 0/4096
[DEBUG] snarkJS: processing: alphaTauG1: 0/4096
[DEBUG] snarkJS: processing: betaTauG1: 0/4096
[DEBUG] snarkJS: processing: betaTauG2: 0/1
[INFO] snarkJS: Contribution Response Hash imported:
e15fa0f4 126ae14d 0e724ffa ee1adaba
1f9f44fb a963db61 e0eda605 4a172efa
124d177b 3a565656 1f1a3a23 6dea3366
3b21ec32 8a26d7b2 ac98ff89 2b1962ae
[INFO] snarkJS: Next Challenge Hash:
b77a3861 ad4a4cbb abe3e870 69787d8c
85ab1372 c15bff14 352c15fb 3116c3c8
81791ae6 3258fbcb 898cad52 c9863e00
c336b311 f8bfb38e 3cefc2cd 917c86eb

​ 系统将提示您输入一些随机文本,这些文本将用作熵的来源。输入文本后,命令将输出一个名为pot12_001.ptau的脚本。设置的第一部分是通用的,对任何电路都有用,因此您可以将其保存以在以后的项目中重复使用。

​ 要获得zk-SNARK证明的证明和验证密钥,必须生成设置。此步骤需要生成一些需要消除的随机值。 消除过程至关重要:如果暴露了这些价值,则整个方案的安全性将受到损害。想要构建设置,我们使用多方计算(MPC)仪式,该仪式允许多个独立方共同构建参数。使用MPC,一个参与者删除其贡献的秘密副本就足够了,以确保整个方案的安全。
​ 可信设置的构建分为两个阶段:对任何电路均有效的常规MPC仪式(称为powers of tau ceremony),以及为每个特定电路构造的第二阶段(阶段2)。任何人都可以通过其随机性参与MPC仪式,通常,在获取最终参数之前,将应用一个随机信标

2.2 下一步,称为设置的阶段2,运行命令如下:

1
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v

​ 以上命令运行完成后,pot12_final.ptau文件被生成。该计算在笔记本电脑上大概花费了3分钟时间。

​ 第二阶段的产生与我们使用tau的能力相似。将生成一个.zkey文件,其中将包含证明和验证密钥以及所有第二阶段贡献。

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
// Start a new zkey and make a contribution
➜ circom snarkjs zkey new circuit.r1cs pot12_final.ptau circuit_0000.zkey
[INFO] snarkJS: Reading r1cs
[INFO] snarkJS: Reading tauG1
[INFO] snarkJS: Reading tauG2
[INFO] snarkJS: Reading alphatauG1
[INFO] snarkJS: Reading betatauG1
[INFO] snarkJS: Circuit hash:
965f5c51 98906eb7 8fc597d1 d7b31bdf
ad7f0491 d1cc081d 8d236685 489f05be
b621e87a 1ba57a28 25071ac3 a69a4df1
1ec4b2c6 aa36b8f2 94e37d17 270ce6bf

➜ circom snarkjs zkey contribute circuit_0000.zkey circuit_final.zkey --name="1st Contributor Name" -v
Enter a random text. (Entropy): zhuangweiming
[DEBUG] snarkJS: Applying key: L Section: 0/2
[DEBUG] snarkJS: Applying key: H Section: 0/4
[INFO] snarkJS: Circuit Hash:
965f5c51 98906eb7 8fc597d1 d7b31bdf
ad7f0491 d1cc081d 8d236685 489f05be
b621e87a 1ba57a28 25071ac3 a69a4df1
1ec4b2c6 aa36b8f2 94e37d17 270ce6bf
[INFO] snarkJS: Contribution Hash:
7987d679 5c914cb3 4138e57a ceee7ae4
8974b323 585bb48f 056fdd8f ea858cd3
a625d63e d8394b9c 974912e4 801aff06
0064f25a 677e727a a289252d 397b8d04

​ 和上述步骤一样,系统将提示您输入一些随机文本以提供熵的来源。输出将是一个名为circuit_final.zkey的文件,我们将使用该文件导出验证密钥(verification key)。

​ 现在,来自circuit_final.zkey的验证密钥导出到文件Verification_key.json中。verification key:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": 1,
"vk_alpha_1": [
"15175462370442750548879662426586324604498073737772476635702014058846911119264",
"11126336670194938919608091368599342163660484801811140235738809534621680589277",
"1"
],
"vk_beta_2": [
[
"5251596196706483766793357074374737595900200707659808196530067991530054260554",
"2264756861473257353315497004278362201201416308719887395373581359048933031269"
],[
"10552939216865106848464515813683167112323139053174380454808956637789377680967",
"2701413932452289802745906093844601151278714195849736706988417564960748034273"
],[
"1",
"0"
]
],
"vk_gamma_2": [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
],[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
],[
"1",
"0"
]
],
"vk_delta_2": [
[
"8590425680399084984710617722188581478119753421410764960451677916514215428082",
"15713152249579034550437632242139020796726126371657275384984401462748083625785"
],[
"4862154669589263828958978916321240414342527577427918353789686327852433696883",
"9569117504828540945921139710836305890297695048604628362102771433345477012098"
],[
"1",
"0"
]
],
"vk_alphabeta_12": [
[
[
"1121360319902761693820363426996646168569211552394580075970634177898785008311",
"14249756278025620543130105348741511241747839379662316431830590326729096142975"
],[
"16423439174084021024919681708878615489972548829583206833909869670703901853110",
"9329143387573491748526724433415355164900218750592620992570237269470665264202"
],[
"19623272010929083665872244835381945771016147820964222485760645671773298080071",
"9896773903597386179712166971836923631098313509393860803813715414524549373292"
]
],[
[
"18313799470766842592075662760908626166323669476646669856687182611043590126171",
"7532895603020793142335129648471958187885524608608576812436682807569433790546"
],[
"20059629119556696413695556916087392540474931269939016250934117162067722623426",
"11285185652573117971807135387812020477130047554451136821582438888864777467010"
],[
"18429091017205122590573696082242994893262627891230351210235517202888391625680",
"7734180768772801433386131669021273948517355047659921136453462913759461451288"
]
]
],
"IC": [
[
"17305572679177982597803616153716539127293296363987765110136569516232291230411",
"7425872175786571904106950706682618227429476545503095908552111445555350791878",
"1"
],[
"16125521217907931491019162042616560858833563034453402251972608389495277275869",
"20962607216912168718282504555696865046978428189883507401556888923455626427056",
"1"
]
]
}

​ 可以验证.ptau或.zkey文件的计算是否正确:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
➜  circom snarkjs powersoftau verify pot12_final.ptau
[INFO] snarkJS: Powers Of tau file OK!
[INFO] snarkJS: Next challenge hash:
b77a3861 ad4a4cbb abe3e870 69787d8c
85ab1372 c15bff14 352c15fb 3116c3c8
81791ae6 3258fbcb 898cad52 c9863e00
c336b311 f8bfb38e 3cefc2cd 917c86eb
[INFO] snarkJS: -----------------------------------------------------
[INFO] snarkJS: Contribution #1: First contribution
[INFO] snarkJS: Next Challenge:
b77a3861 ad4a4cbb abe3e870 69787d8c
85ab1372 c15bff14 352c15fb 3116c3c8
81791ae6 3258fbcb 898cad52 c9863e00
c336b311 f8bfb38e 3cefc2cd 917c86eb
[INFO] snarkJS: Response Hash:
e15fa0f4 126ae14d 0e724ffa ee1adaba
1f9f44fb a963db61 e0eda605 4a172efa
124d177b 3a565656 1f1a3a23 6dea3366
3b21ec32 8a26d7b2 ac98ff89 2b1962ae
[INFO] snarkJS: Response Hash:
9e63a5f6 2b96538d aaed2372 481920d1
a40b9195 9ea38ef9 f5f6a303 3b886516
0710d067 c09d0961 5f928ea5 17bcdf49
ad75abd2 c8340b40 0e3b18e9 68b4ffef
[INFO] snarkJS: -----------------------------------------------------
[INFO] snarkJS: Powers of Tau Ok!
➜ circom snarkjs zkey verify circuit.r1cs pot12_final.ptau circuit_final.zkey
[INFO] snarkJS: Reading r1cs
[INFO] snarkJS: Reading tauG1
[INFO] snarkJS: Reading tauG2
[INFO] snarkJS: Reading alphatauG1
[INFO] snarkJS: Reading betatauG1
[INFO] snarkJS: Circuit hash:
965f5c51 98906eb7 8fc597d1 d7b31bdf
ad7f0491 d1cc081d 8d236685 489f05be
b621e87a 1ba57a28 25071ac3 a69a4df1
1ec4b2c6 aa36b8f2 94e37d17 270ce6bf
[INFO] snarkJS: Circuit Hash:
965f5c51 98906eb7 8fc597d1 d7b31bdf
ad7f0491 d1cc081d 8d236685 489f05be
b621e87a 1ba57a28 25071ac3 a69a4df1
1ec4b2c6 aa36b8f2 94e37d17 270ce6bf
[INFO] snarkJS: -------------------------
[INFO] snarkJS: contribution #1 1st Contributor Name:
7987d679 5c914cb3 4138e57a ceee7ae4
8974b323 585bb48f 056fdd8f ea858cd3
a625d63e d8394b9c 974912e4 801aff06
0064f25a 677e727a a289252d 397b8d04
[INFO] snarkJS: -------------------------
[INFO] snarkJS: ZKey Ok!

3.计算witness

​ 在创建任何证明之前,我们需要计算与电路的所有约束匹配的电路的所有信号。为此,我们将使用由circom生成的Wasm模块来帮助完成此工作。我们只需要提供一个包含输入的文件,模块将执行电路并计算所有中间信号和输出。输入,中间信号和输出的集合称为见证人(witness)。

​ 本次的范例中,我们想证明我们能够分解数字33。因此,指定a=3, b=11。创建一个文件,命名为 input.json

1
{"a": 3, "b": 11}

​ 下面开始就算 witness,生成 witness 文件, 命令如下:

1
snarkjs wtns calculate circuit.wasm input.json witness.wtns

4.创建证明

​ 这个命令默认会使用 prooving_key.jsonwitness.json 文件去生成 proof.jsonpublic.json

1
snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json

proof.json 文件包含了实际的证明。而 public.json 文件将仅包含公共的输入(当前的例子没有)和输出(当前的例子是 33)。

5.验证证明

1
2
➜  circom snarkjs groth16 verify verification_key.json public.json proof.json
[INFO] snarkJS: OK!

​ 此命令使用我们之前导出的verify_key.json文件,proof.json和public.json来检查证明是否有效。如果证明有效,则命令输出OK。一个有效的证明不仅证明我们知道满足电路要求的一组信号,而且证明我们使用的公共输入和输出与public.json文件中描述的匹配。

实际上,在此阶段,将把proof.jsonpublic.json文件都交给验证者。但是,出于教程的目的,我们还将扮演验证者的角色。借助证明以及公共输入和输出,我们现在可以向验证者证明我们知道一个或多个满足电路约束的私有信号,而无需透露有关那些私有信号的任何信息。从验证者的角度来看,她可以验证我们是否知道见证中包含的一组私有信号,而无需访问它。这是zk-proof背后魔术的核心!

6.智能合约验证证明

1
snarkjs zkey export solidityverifier circuit_final.zkey verifier.sol

​ 上述命令使用验证密钥circuit_final.zkey并在名为verifier.sol的文件中输出合约代码。可以看到该代码包含两个协定:Pairing和Verifier。只需要部署Verifier合约。

​ 生成智能合约调用的参数,命令如下:

1
2
➜  circom snarkjs zkesc public.json proof.json
["0x29b83d63472e4f47b092fd4ced43a566697f0575186964c7cc3deec2c5c1b0bd", "0x2eb116882d2bacdbeece8a2adf2ab6d0f4420b60e82b4b66ddb18cc26ab2bae2"],[["0x170d208ad3a8bf4358c207706bce9e87cbad11037f642d1ab06f65a5ebf10eb4", "0x0dc07edd498cc16c5174c2a5a2aeed86c2f5689ed25486e8981768d7c7ceae13"],["0x1c52e6144e980ae72e5c5679c979c70e0da4a519fc087dda3b96bbea09179b20", "0x2849fdea744fa924ae19eb15917dac4543c0fa3b888293dfcd365c6251f25223"]],["0x01918c12515eb00b823f0e2a63ef53e15792b4b4fd331b595ffc670e929d6b99", "0x2246e9bb88119c6f8f1c04735beeee038ddc6e66572cb44f1c926575db80be9a"],["0x0000000000000000000000000000000000000000000000000000000000000021"]

​ 验证者具有一个称为verifyProof的函数,当且仅当证明和输入有效时,该函数才返回TRUE

​ 如果仅更改参数中的任何位,则可以检查结果返回 false 。

漏洞修复

​ 我们已经表明可以生成证明我们知道两个因子a和b使得a*b=33的证明。但这不能证明我们知道如何分解数字33,因为我们可以选择a = 1和b = 33,通常对于任何整数n,选择输入a=1,b=n。在此,我们需要修改电路,以便不可能将数字1分配给任何输入。为此,我们将使用0是唯一没有反数的事实。

​ 这里的技巧是使用0不可求倒数的属性,约束不接受 1 作为任何一个输入,即(a-1) 不可求倒数的方式来约束电路。

​ 如果 a 是 1 则 (a-1)*inv = 1 是不可能成立的,通过 1/(a-1) 来计算 inv 。

修正电路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template Multiplier() {
signal private input a;
signal private input b;
signal output c;
signal inva;
signal invb;

inva <-- 1/(a-1);
(a-1)*inva === 1;
invb <-- 1/(b-1);
(b-1)*invb === 1;

c <== a*b;
}

component main = Multiplier();

​ 您可能已经注意到,我们引入了两个新的运算符 : <--===<----> 操作符运算符只为信号分配一个值,而不创建任何约束。=== 操作符添加约束而不分配值。如前所述,<== 为信号分配一个值并添加一个约束。这意味着它只是 <--=== 的组合。但是,由于并非总是希望在同一步骤中同时完成这两个步骤,因此circom 的灵活性使我们可以将这一步分为两步。

​ 事实证明,电路仍然存在一个细微的问题:由于运算是在有限域(Z_r)上进行的,因此我们需要确保乘法不会溢出。

最后

circomlib 写好了一些基本的电路,如:binaritzations、comparators, eddsa, hashes, merkle trees 等等。