参考 iden3 官方最新教程文档,用 circom2 和 snarkjs 库创建和执行你的第一个零知识证明。
https://blog.iden3.io/first-zk-proof.html https://docs.circom.io/getting-started/installation https://learnblockchain.cn/article/1078
目前国内参考的经典教程是 circom 老版的(2020 年 4 月的文章),当时教程的一些包文件和指令格式,参数部分已弃用或者改变,circom 也升级到了 circom2,新的 circom2 编译器是通过 rust 生成的。
在本教程中,我们参考 iden3 官方最新教程文档,将指导你使用 circom2 和 snarkjs 库创建和执行你的第一个零知识证明。
在密码学中,零知识证明或零知识协议是一种方法,通过该方法,一方(证明者)可以向另一方(验证者)证明他们知道值x,而无需传达除了他知道值x这个事实之外任何信息。 解释来源于 Wiki
零知识证明使我们能够证明自己的某些特定特征,而无需透露任何额外的信息。
从哲学的角度来看,它们是一组新的加密工具的一部分,这些工具使得透明性不必与隐私性冲突。
术语“ zk-snarks”代表zero-knowledge succinct non-interactive arguments of knowledge:
zero-knowledge :零知识
Succinctness:简洁(证明信息较短,方便验证)
Non-interactivity :无需交互
arguments of knowledge :知识论据
暂时无需了解这些概念意味着什么。 可以简单地将 zk-snarks 视为产生零知识证明的有效(或简洁)方法:可以使证明信息足够短到可以发布到区块链,并且可以被任何有权验证它们的人( 我们称为验证者)以后都能读取。
如果众筹仅针对 KYC 或授权用户,使用 zk-snarks,你可以证明自己是被授权可参加众筹的人,而无需透露自己是谁或花费了多少。
与上述类似,您可以在不透露性别,年龄甚至姓名的情况下证明自己有资格投票。
例如,可以在全国大选中投票,而仅表明您是该国的公民,并且年满 18 岁。
您可以使用 zk-snarks 来证明您最近对 Covid-19 的测试是阴性,而不用透露测试的确切日期或测试的医院:仅需要在官方认可的时间窗口内有效即可。
Circom 是一个可以轻松构建代数电路的库。
snarkjs 是 zk-snarks 协议的独立实现-完全用 JavaScript 编写。
这些库是设计好能协同工作的:在 circom 中构建的任何电路都可以在 snarkjs 中使用。
zk-snarks 不能直接应用于任何计算问题。在使用之前,首先需要将问题转换为正确的形式。第一步就是将其转换为代数电路。
尽管这一步做起来并不总是很明显,但事实证明,我们关心的大多数计算问题都可以转化为代数电路。
关于零知识问题的转换,可参考前文:
零知识 QAP 问题的转化原文:w3hitchhiker.mirror.xyz
你需要系统中的多个依赖项来运行 circom
及其相关工具。
circom
编译器。 为使用 Rust , 你可以安装 rustup
。如果你使用 Linux 或者 macOS,请打开终端输入以下指令。curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
从我们的源代码安装,请克隆 circom
仓库:
git clone https://github.com/iden3/circom.git
进入 circom 目录,使用 cargo build 编译
cargo build --release
你可以按如下指令安装此二进制可执行文件:
cargo install --path circom
前面指令生成 circom
二进制文件将存在目录 $HOME/.cargo/bin
。
现在,你应该能够使用help
查看可执行文件的所有选项:
circom --help
Circom Compiler 2.0.0
IDEN3
Compiler for the Circom programming language
USAGE:
circom [FLAGS] [OPTIONS] [input]
FLAGS:
-h, --help Prints help information
--inspect Does an additional check over the constraints produced
--O0 No simplification is applied
-c, --c Compiles the circuit to c
--json outputs the constraints in json format
--r1cs outputs the constraints in r1cs format
--sym outputs witness in sym format
--wasm Compiles the circuit to wasm
--wat Compiles the circuit to wat
--O1 Only applies var to var and var to constant simplification
-V, --version Prints version information
OPTIONS:
--O2 <full_simplification> Full constraint simplification [default: full]
-o, --output <output> Path to the directory where the output will be written [default: .]
ARGS:
<input> Path to a circuit with a main component [default: ./circuit.circom]
需要在电脑中先安装Node.js
snarkjs
是一个 npm 包,其中包含从 circom
生成的工件生成和验证 ZK 证明的代码。
你可以用以下命令安装snarkjs
:
npm install -g snarkjs
circom
允许程序员定义算术电路的约束。所有约束必须采用 A*B + C = 0 的形式,其中 A、B 和 C 是信号的线性组合。
使用circom
构建的算术电路对信号进行操作。让我们定义我们的第一个电路,它简单地将两个输入信号相乘并产生一个输出信号。
pragma circom 2.0.0;
/*This circuit template checks that c is the multiplication of a and b.*/
template Multiplier2 () {
// Declaration of signals.
signal input a;
signal input b;
signal output c;
// Constraints.
c <== a * b;
}
首先, pragma
指令用于指定编译器版本(类似于 solidity)。这是为了确保电路与 pragma
指令后的编译器版本兼容。否则,编译器会抛出警告。
然后,我们使用关键字template
来定义新电路的形状,为 Multiplier2
。现在,我们必须定义它的信号。信号可以用标识符命名,例如 a, b, c
。
这个电路有2个 private 输入信号,名为 a
和 b
,还有一个输出信号 c
。
输入和输出使用<==
运算符进行关联。 在 circom 中,<==
运算符做两件事。 首先是连接信号。 第二个是施加约束。
在本例中,我们使用<==
将c
连接到a
和b
,同时将c
约束为a * b
的值,即电路做的事情是让强制信号 c
为 a*b
的值。
注意:在每个template
中,我们首先声明它的信号,然后声明相关的约束。
我们创建了叫Multiplier2
的template
电路。
但是,要实际创建电路,我们必须创建此模板的一个实例(使用名为main
的组件实例化它)。 为此,请创建一个包含以下内容的文件:
pragma circom 2.0.0;
template Multiplier2() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
component main = Multiplier2();
使用 circom
编写算术电路后,我们应该将其保存在扩展名为 .circom
的文件。 你可以创建自己的电路或使用我们电路库 circomlib
中的模板。
在我们的案例中,我们创建了multiplier2.circom文件。现在是编译电路以获得表示它的算术方程组的时候了。作为编译的结果,我们还将获得计算见证的程序。我们可以使用以下命令编译电路:
circom multiplier2.circom --r1cs --wasm --sym
使用这些选项,我们生成三种类型的文件:
--r1cs
:生成 multiplier2.r1cs
( R1CS 电路的二进制格式的约束系统)--wasm
:生成 multiplier2_js
目录其中包含Wasm
代码(multiplier2.wasm) 和生成见证所需要的其他文件--sym
:生成 multiplier2.sym
(以注释方式调试和打印约束系统所需的符号文件)我们可以使用选项 -o 来指定创建这些文件的目录
要显示电路的信息,可以运行:
snarkjs info -r multiplier2.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
此信息与我们设计的电路相吻合。 记住,我们有两个私有输入 a 和 b,以及一个输出 c。 我们指定的一个约束是a * b = c
。
可以再检查一遍,通过运行以下命令来打印电路的约束: snarkjs r1cs print multiplier2.r1cs multiplier2.sym
输出如下:
[INFO] snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.a ] * [ main.b ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.c ] = 0
忽略前缀,可以读为:
a*b-c=0
在创建证明之前,我们需要计算与电路的所有约束匹配电路的所有信号。为此,我们将使用circom
生成的Wasm
模块来协助完成这项工作。
使用生成的 Wasm
二进制文件和三个 JavaScript 文件,我们只需提供一个包含输入的文件,模块将执行电路并计算所有中间信号和输出。输入、中间信号和输出的集合称为见证。
在我们的例子中,我们想证明我们能够因式分解数字 33。因此,我们分配 a = 3
和 b = 11
。
请注意,我们也可以将数字 1 分配给一个输入,将数字 33 分配给另一个。所以,我们的证明并没有真正表明我们能够分解数字 33。
我们需要创建一个名为 input.json
的文件,其中包含以标准 json 格式编写的输入:
{"a": 3, "b": 11}
现在,我们计算见证并生成二进制文件 witness.wtns
,其中包含 snarkjs
接受的格式。
在使用标志 --wasm
和电路 multiplier2.circom
调用 circom
编译器后,我们可以找到 multiplier2_js
文件夹,其中包含 multiplier2.wasm 中的 Wasm
代码和所有需要的 JavaScript
文件。
进入 multiplier2_js
目录,添加 input.json
文件并执行:
node generate_witness.js multiplier2.wasm input.json witness.wtns
将生成 ẁitness.wtns
文件, 该文件以与 snarkjs
兼容的二进制格式编码,这是我们用来创建实际证明的工具。
注意. circom
也支持使用 C++ 行进计算见证,我们的例子是采用的小型电路,对于大型电路,C++ 见证计算明显快于 WASM 计算器,使用C++的方法可以参考官方文档。
在编译电路并使用适当的输入运行见证计算器后,我们将拥有一个扩展名为 .wtns 的文件,其中包含所有计算的信号,以及一个扩展名为 .r1cs 的文件,其中包含描述电路的约束。这两个文件都将用于创建我们的证明。
现在,我们将使用 snarkjs 工具为我们的输入,生成证明和验证证明。特别是,使用multiplier2 时,意味着我们可以证明我们能够提供数字 33 的两个因数。也就是说,我们将证明我们知道两个整数 a和 b,因此当我们将它们相乘时,它会得到数字33。
目前,snarkjs 支持 2 个证明系统:Groth16 和 PLONK。
我们样例中采用的方案是 Groth16,使用 PLONK 可以参考 snarkjs教程。
我们将使用 Groth16 zk-SNARK 协议。要使用此协议,你需要生成可信设置(trusted setup)。 Groth16 需要为每个电路生成可信设置。更详细地说,可信设置由两部分组成:
接下来,我们为创建可信设置提供了一个非常基本的仪式,我们还提供了创建和验证 Groth16 证明的基本命令。查看相关的背景部分可以查看 snarkjs 教程以获取更多信息。
首先,我们开始新的“tau 的权力”仪式:
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
然后,我们为仪式做出贡献:
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
现在,我们在文件 pot12_0001.ptau 中有对 tau 权力的贡献,下面,我们就可以继续进行阶段 2。
阶段 2 是特定电路的。执行以下命令开始该阶段的生成:
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v
接下来,我们生成一个 .zkey
文件,其中包含证明和验证密钥以及所有 阶段2的贡献。执行以下命令启动一个新的 zkey:
snarkjs groth16 setup multiplier2.r1cs pot12_final.ptau multiplier2_0000.zkey
为仪式的 阶段2做出贡献:
snarkjs zkey contribute multiplier2_0000.zkey multiplier2_0001.zkey --name="1st Contributor Name" -v
导出验证密钥:
snarkjs zkey export verificationkey multiplier2_0001.zkey verification_key.json
一旦计算出见证并且已经执行了可信设置,我们就可以 生成与电路和见证人相关联的 zk-proof :
snarkjs groth16 prove multiplier2_0001.zkey witness.wtns proof.json public.json
此命令生成 Groth16 证明并输出两个文件:
proof.json
: 它包含了证明public.json
: 它包含公共输入和输出的值。要验证证明,请执行以下指令:
snarkjs groth16 verify verification_key.json public.json proof.json
该命令使用我们之前导出的文件 verify_key.json、proof.json 和 public.json 来检查证明是否有效。如果证明有效,则命令输出 OK。
一个有效的证明不仅证明我们知道一组满足电路的信号,而且证明我们使用的公共输入和输出与 public.json
文件中描述的相匹配。
👉 snarkjs 还可以生成一个 Solidity 验证器,允许在以太坊区块链上验证证明。
首先,我们需要使用以下命令生成 Solidity 代码:
snarkjs zkey export solidityverifier multiplier2_0001.zkey verifier.sol
此命令采用验证密钥 multiplier2_0001.zkey
并在名为 verifier.sol
的文件中输出 Solidity 代码。你可以从此文件中获取代码并将其剪切并粘贴到 Remix 中运行。你将看到代码包含两个合约:Pairing
和Verifier
。你只需要部署Verifier
合约。
你可能使用 Rinkeby、Kovan 或 Ropsten 这样的测试网。你也可以使用 JavaScript VM,但在某些浏览器中,验证需要很长时间并且页面可能会冻结。
Verifier
有一个名为 verifyProof
的view
函数,当且仅当证明和输入有效时才返回 TRUE
。为了方便调用,可以使用 snarkJS
生成调用的参数,输入
snarkjs generatecall
将命令的输出的可以复制粘贴到 Remix 中 verifyProof
方法的参数字段中。如果一切正常,此方法应返回结果 TRUE
。当你尝试只更改参数,就会看到结果是 FALSE
。
以下链接已通过验证,可在浏览器查看生成的verifier.sol 的源码:
https://ropsten.etherscan.io/address/0x9ff6e2a7d7cb20f785aa6c98c4a1772375c9d7f3#code
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!