前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >构建你的第一个零知识 snark 电路(Circom2)

构建你的第一个零知识 snark 电路(Circom2)

作者头像
Tiny熊
发布2022-11-07 10:04:50
1.4K0
发布2022-11-07 10:04:50
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:Xiang | W3.Hitchhiker[1]

参考文档:

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[2]

零知识证明使我们能够证明自己的某些特定特征,而无需透露任何额外的信息。

从哲学的角度来看,它们是一组新的加密工具的一部分,这些工具使得透明性不必与隐私性冲突。

什么是 zk-snark?

术语“ zk-snarks”代表zero-knowledge succinct non-interactive arguments of knowledge:

zero-knowledge :零知识

Succinctness:简洁(证明信息较短,方便验证)

Non-interactivity :无需交互

arguments of knowledge :知识论据

暂时无需了解这些概念意味着什么。可以简单地将 zk-snarks 视为产生零知识证明的有效(或简洁)方法:可以使证明信息足够短到可以发布到区块链,并且可以被任何有权验证它们的人( 我们称为验证者)以后都能读取。

一些例子

众筹

如果众筹仅针对 KYC 或授权用户,使用 zk-snarks,你可以证明自己是被授权可参加众筹的人,而无需透露自己是谁或花费了多少。

匿名投票

与上述类似,您可以在不透露性别,年龄甚至姓名的情况下证明自己有资格投票。

例如,可以在全国大选中投票,而仅表明您是该国的公民,并且年满 18 岁。

Covid-19 新冠病毒测试

您可以使用 zk-snarks 来证明您最近对 Covid-19 的测试是阴性,而不用透露测试的确切日期或测试的医院:仅需要在官方认可的时间窗口内有效即可。

我们需要使用两个库:circom[3] 和 snarkjs[4].

Circom 是一个可以轻松构建代数电路的库。

snarkjs 是 zk-snarks 协议的独立实现-完全用 JavaScript 编写。

这些库是设计好能协同工作的:在 circom 中构建的任何电路都可以在 snarkjs 中使用。

为什么我们需要电路?

zk-snarks 不能直接应用于任何计算问题。在使用之前,首先需要将问题转换为正确的形式。第一步就是将其转换为代数电路。

尽管这一步做起来并不总是很明显,但事实证明,我们关心的大多数计算问题都可以转化为代数电路。

关于零知识问题的转换,可参考前文:

零知识 QAP 问题的转化原文:w3hitchhiker.mirror.xyz[5]

1、安装

安装依赖

你需要系统中的多个依赖项来运行 circom 及其相关工具。

  • 核心工具是用 Rust 编写的circom 编译器。为使用 Rust , 你可以安装 rustup。如果你使用 Linux 或者 macOS,请打开终端输入以下指令。

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

安装 circom

从我们的源代码安装,请克隆 circom 仓库:

git clone https://github.com/iden3/circom.git

进入 circom 目录,使用 cargo build 编译

cargo build --release

你可以按如下指令安装此二进制可执行文件:

cargo install --path circom

前面指令生成 circom 二进制文件将存在目录 $HOME/.cargo/bin

现在,你应该能够使用help查看可执行文件的所有选项:

代码语言:javascript
复制
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]
安装 snarkjs

需要在电脑中先安装Node.js

snarkjs 是一个 npm 包,其中包含从 circom生成的工件生成和验证 ZK 证明的代码。

你可以用以下命令安装snarkjsnpm install -g snarkjs

2、设计电路

circom 允许程序员定义算术电路的约束。所有约束必须采用 A*B + C = 0 的形式,其中 A、B 和 C 是信号的线性组合。

使用circom 构建的算术电路对信号进行操作。让我们定义我们的第一个电路,它简单地将两个输入信号相乘并产生一个输出信号。

代码语言:javascript
复制
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 输入信号,名为 ab ,还有一个输出信号 c

输入和输出使用<==运算符进行关联。在 circom 中,<==运算符做两件事。首先是连接信号。第二个是施加约束。

在本例中,我们使用<==c连接到ab,同时将c约束为a * b的值,即电路做的事情是让强制信号 ca*b 的值。

注意:在每个template 中,我们首先声明它的信号,然后声明相关的约束。

3、编译电路

我们创建了叫Multiplier2template 电路。

但是,要实际创建电路,我们必须创建此模板的一个实例(使用名为main的组件实例化它)。为此,请创建一个包含以下内容的文件:

代码语言:javascript
复制
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[6] 电路的二进制格式的约束系统)
  • --wasm:生成 multiplier2_js 目录其中包含Wasm 代码(multiplier2.wasm) 和生成见证[7]所需要的其他文件
  • --sym:生成 multiplier2.sym(以注释方式调试和打印约束系统所需的符号文件)

我们可以使用选项 -o 来指定创建这些文件的目录

查看电路有关的信息

要显示电路的信息,可以运行:

snarkjs info -r multiplier2.r1cs

可以看到如下输出:

代码语言:javascript
复制
[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

输出如下:

代码语言:javascript
复制
[INFO]  snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.a ] * [ main.b ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.c ] = 0

忽略前缀,可以读为:

a*b-c=0

4、计算见证

什么是见证?

在创建证明之前,我们需要计算与电路的所有约束匹配电路的所有信号。为此,我们将使用circom 生成的Wasm 模块来协助完成这项工作。

使用生成的 Wasm二进制文件和三个 JavaScript 文件,我们只需提供一个包含输入的文件,模块将执行电路并计算所有中间信号和输出。输入、中间信号和输出的集合称为见证[8]

在我们的例子中,我们想证明我们能够因式分解数字 33。因此,我们分配 a = 3b = 11

请注意,我们也可以将数字 1 分配给一个输入,将数字 33 分配给另一个。所以,我们的证明并没有真正表明我们能够分解数字 33。

我们需要创建一个名为 input.json 的文件,其中包含以标准 json 格式编写的输入:

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

现在,我们计算见证并生成二进制文件 witness.wtns,其中包含 snarkjs接受的格式。

在使用标志 --wasm 和电路 multiplier2.circom 调用 circom 编译器后,我们可以找到 multiplier2_js 文件夹,其中包含 multiplier2.wasm 中的 Wasm 代码和所有需要的 JavaScript 文件。

使用 WebAssembly 计算见证

进入 multiplier2_js 目录,添加 input.json 文件并执行:

node generate_witness.js multiplier2.wasm input.json witness.wtns

见证文件

将生成 ẁitness.wtns 文件, 该文件以与 snarkjs兼容的二进制格式编码,这是我们用来创建实际证明的工具。

注意. circom 也支持使用 C++ 行进计算见证,我们的例子是采用的小型电路,对于大型电路,C++ 见证计算明显快于 WASM 计算器,使用 C++的方法可以参考官方文档。

5、验证电路

在编译电路并使用适当的输入运行见证计算器后,我们将拥有一个扩展名为 .wtns 的文件,其中包含所有计算的信号,以及一个扩展名为 .r1cs 的文件,其中包含描述电路的约束。这两个文件都将用于创建我们的证明。

现在,我们将使用 snarkjs 工具为我们的输入,生成证明和验证证明。特别是,使用 multiplier2 时,意味着我们可以证明我们能够提供数字 33 的两个因数。也就是说,我们将证明我们知道两个整数 a 和 b,因此当我们将它们相乘时,它会得到数字 33。

可信设置

目前,snarkjs 支持 2 个证明系统:Groth16 和 PLONK。

我们样例中采用的方案是 Groth16,使用 PLONK 可以参考 snarkjs 教程[9]

Groth16

我们将使用 Groth16[10] zk-SNARK 协议。要使用此协议,你需要生成可信设置(trusted setup[11])。Groth16 需要为每个电路生成可信设置。更详细地说,可信设置由两部分组成:

  • tau 的权力,它独立于电路。
  • 阶段 2,取决于电路。

接下来,我们为创建可信设置提供了一个非常基本的仪式,我们还提供了创建和验证 Groth16[12] 证明的基本命令。查看相关的背景部分可以查看 snarkjs 教程[13]以获取更多信息。

Tau 的权力

首先,我们开始新的“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

阶段 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[14] 证明并输出两个文件:

  • proof.json: 它包含了证明
  • public.json: 它包含公共输入和输出的值。
Verifying a Proof 验证证明

要验证证明,请执行以下指令:

snarkjs groth16 verify verification_key.json public.json proof.json

该命令使用我们之前导出的文件 verify_key.json、proof.json 和 public.json 来检查证明是否有效。如果证明有效,则命令输出 OK。

一个有效的证明不仅证明我们知道一组满足电路的信号,而且证明我们使用的公共输入和输出与 public.json 文件中描述的相匹配。

6、从智能合约上进行验证

👉 snarkjs 还可以生成一个 Solidity 验证器,允许在以太坊区块链上验证证明。

首先,我们需要使用以下命令生成 Solidity 代码:

snarkjs zkey export solidityverifier multiplier2_0001.zkey verifier.sol

此命令采用验证密钥 multiplier2_0001.zkey 并在名为 verifier.sol的文件中输出 Solidity 代码。你可以从此文件中获取代码并将其剪切并粘贴到 Remix 中运行。你将看到代码包含两个合约:PairingVerifier。你只需要部署Verifier 合约。

你可能使用 Rinkeby、Kovan 或 Ropsten 这样的测试网。你也可以使用 JavaScript VM,但在某些浏览器中,验证需要很长时间并且页面可能会冻结。

Verifier 有一个名为 verifyProofview 函数,当且仅当证明和输入有效时才返回 TRUE 。为了方便调用,可以使用 snarkJS 生成调用的参数,输入

snarkjs generatecall

将命令的输出的可以复制粘贴到 Remix 中 verifyProof 方法的参数字段中。如果一切正常,此方法应返回结果 TRUE。当你尝试只更改参数,就会看到结果是 FALSE

以下链接已通过验证,可在浏览器查看生成的 verifier.sol 的源码:

https://ropsten.etherscan.io/address/0x9ff6e2a7d7cb20f785aa6c98c4a1772375c9d7f3#code

参考资料

[1]

Xiang|W3.Hitchhiker: https://learnblockchain.cn/people/2399

[2]

来源于 Wiki: https://en.wikipedia.org/wiki/Zero-knowledge_proof

[3]

circom: https://github.com/iden3/circom/blob/master/TUTORIAL.md

[4]

snarkjs: https://github.com/iden3/snarkjs

[5]

零知识 QAP 问题的转化原文:w3hitchhiker.mirror.xyz: https://w3hitchhiker.mirror.xyz/sjtV-I7l6_XY9uVkykvFRRAriAAusTLFq8s-gCGdviE

[6]

R1CS: https://docs.circom.io/background/background#rank-1-constraint-system

[7]

见证: https://docs.circom.io/background/background#witness

[8]

见证: https://docs.circom.io/background/background#witness

[9]

snarkjs教程: https://github.com/iden3/snarkjs

[10]

Groth16: https://eprint.iacr.org/2016/260

[11]

trusted setup: https://docs.circom.io/background/background#trusted-setup

[12]

Groth16: https://eprint.iacr.org/2016/260

[13]

snarkjs 教程: https://github.com/iden3/snarkjs

[14]

Groth16: https://eprint.iacr.org/2016/260

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考文档:
  • 零知识基础概念
    • 什么是零知识证明?
      • 什么是 zk-snark?
        • 一些例子
          • 为什么我们需要电路?
          • 1、安装
            • 安装依赖
              • 安装 circom
                • 安装 snarkjs
                • 2、设计电路
                • 3、编译电路
                  • 查看电路有关的信息
                  • 4、计算见证
                    • 什么是见证?
                      • 使用 WebAssembly 计算见证
                        • 见证文件
                        • 5、验证电路
                          • 可信设置
                            • Groth16
                              • Tau 的权力
                                • 阶段 2
                                  • 生成证明
                                    • Verifying a Proof 验证证明
                                    • 6、从智能合约上进行验证
                                      • 参考资料
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档