在 TON 上构建一个简单的 ZK 项目
👋 介绍
零知识(ZK)证明是一种基本的密码学原语,它允许一方(证明者)向另一方(验证者)证明一个陈述是真实的,而不泄露除了该陈述本身的有效性之外的任何信息。零知识证明是构建隐私保护系统的强大工具,已在多种应用中使用,包括匿名支付、匿名消息系统和无信任桥接。
在 2023 年 6 月之前,不能在 TON 上验证加密证明。由于配对算法背后复杂计算的普遍性,有必要通过添加 TVM 操作码来增加 TVM 的功能以执行证明验证。该功能已在 2023 年 6 月更新中添加,截至本文撰写时仅在测试网上可用。
🦄 本教程将覆盖
- 零知识密码学的基 础知识,特别是 zk-SNARKs(零知识简洁非互动式知识论证)
- 启动受信任设置仪式(使用 Tau 力量)
- 编写和编译一个简单的 ZK 电路(使用 Circom 语言)
- 生成、部署和测试一个 FunC 合约来验证样本 ZK 证明
🟥🟦 以颜色为重点的 ZK 证明解释
在我们深入了解零知识之前,让我们从一个简单的问题开始。假设你想向一个色盲人证明,可以区分不同颜色是可能的。我们将使用一种互动解决方案来解决这个问题。假设色盲人(验证者)找到两张相同的纸,一张为红色 🟥 一张为蓝色 🟦。
验证者向你(证明者)展示其中一张纸并要求你记住颜色。然后验证者将那张特定的纸放在背后,保持不变或更换它,并询问你颜色是否有变化。如果你能够分辨出颜色的区别,那么你可以看到颜色(或者你只是幸运地猜对了正确的颜色)。
现在,如果验证者完成这个过程 10 次,而你每次都能分辨出颜色的区别,那么验证者对正确颜色的使用有 ~99.90234% 的把握(1 - (1/2)^10)。因此,如果验证者完成这个过程 30 次,那么验证者将有 99.99999990686774% 的把握(1 - (1/2)^30)。
尽管如此,这是一个互动式解决方案,让 DApp 要求用户发送 30 笔交易来证明特定数据是不高效的。因此,需要一个非互动式解决方案;这就是 Zk-SNARKs 和 Zk-STARKs 的用武之地。
出于本教程的目的,我们只会涵盖 Zk-SNARKs。然而,你可以在 StarkWare 网 站 上阅读更多关于 Zk-STARKs 如何工作的信息,而关于 Zk-SNARKs 和 Zk-STARKs 之间差异的信息可以在这篇 Panther Protocol 博客文章 上找到。
🎯 Zk-SNARK: 零知识简洁非互动式知识论证
Zk-SNARK 是一个非互动式证明系统,其中证明者可以向验证者展示一个证明,以证明一个陈述是真实的。同时,验证者能够在非常短的时间内验证证明。通常,处理 Zk-SNARK 包括三个主要阶段:
- 使用 多方计算(MPC) 协议进行受信任设置,以生成证明和验证密钥(使用 Tau 力量)
- 使用证明者密钥、公开输入和私密输入(见证)生成证明
- 验证证明
让我们设置我们的开发环境并开始编码!
⚙ 开发环境设置
我们开始这个过程的步骤如下:
- 使用 Blueprint 创建一个名为 "simple-zk" 的新项目,执行以下命令后,输入你的合约名称(例如 ZkSimple),然后选择第一个选项(使用一个空合约)。
npm create ton@latest simple-zk
- 接下来我们会克隆被调整以支持 FunC 合约的 snarkjs 库
git clone https://github.com/kroist/snarkjs.git
cd snarkjs
npm ci
cd ../simple-zk
- 然后我们将安装 ZkSNARKs 所需的库
npm add --save-dev snarkjs ffjavascript
npm i -g circom
- 接下来我们将下面的部分添加到 package.json 中(请注意,我们将使用的一些操作码在主网版本中尚未可用)
"overrides": {
"@ton-community/func-js-bin": "0.4.5-tvmbeta.1",
"@ton-community/func-js": "0.6.3-tvmbeta.1"
}
- 另外,我们需要更改 @ton-community/sandbox 的版本,以便使用最新的 TVM 更新
npm i --save-dev @ton-community/[email protected]
太好了!现在我们准备好开始在 TON 上编写我们的第一个 ZK 项目了!
我们当前有两个主要文件夹构成了我们的 ZK 项目:
simple-zk
文件夹:包含我们的 Blueprint 模板,这将使我们能够编写我们的电路和合约以及测试snarkjs
文件夹:包含我们在第 2 步中克隆的 snarkjs 库
Circom 电路
首先让我们创建一个文件夹 simple-zk/circuits
并在其中创建一个文件并添加以下代码:
template Multiplier() {
signal private input a;
signal private input b;
//private input means that this input is not public and will not be revealed in the proof
signal output c;
c <== a*b;
}
component main = Multiplier();
上面我们添加了一个简单的乘法器电路。通过使用这个电路,我们可以证明我们知道两个数字相乘的结果是特定的数字(c)而不泄露这些对应的数字(a 和 b)本身。
要了解更多关于 circom 语言的信息,请考虑查看这个网站。
接下来我们将创建一个文件夹来存放我们的构建文件,并通过执行以下操作将数据移动到那里(在 simple-zk
文件夹中):
mkdir -p ./build/circuits
cd ./build/circuits
💪 使用 Powers of TAU 创建受信任设置
现在是时候进行受信任设置了。要完成这个过程,我们将使用 Powers of Tau 方法(可能需要几分钟时间来完成)。让我们开始吧:
echo 'prepare phase1'
node ../../../snarkjs/build/cli.cjs powersoftau new bls12-381 14 pot14_0000.ptau -v
echo 'contribute phase1 first'
node ../../../snarkjs/build/cli.cjs powersoftau contribute pot14_0000.ptau pot14_0001.ptau --name="First contribution" -v -e="some random text"
echo 'contribute phase1 second'
node ../../../snarkjs/build/cli.cjs powersoftau contribute pot14_0001.ptau pot14_0002.ptau --name="Second contribution" -v -e="some random text"
echo 'apply a random beacon'
node ../../../snarkjs/build/cli.cjs powersoftau beacon pot14_0002.ptau pot14_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon"
echo 'prepare phase2'
node ../../../snarkjs/build/cli.cjs powersoftau prepare phase2 pot14_beacon.ptau pot14_final.ptau -v
echo 'Verify the final ptau'
node ../../../snarkjs/build/cli.cjs powersoftau verify pot14_final.ptau
完成上述过程后,它将在 build/circuits 文件夹中创建 pot14_final.ptau 文件,该文件可用于编写未来相关电路。
如果编写了具有更多约束的更复杂电路,则需要使用更大参数生成您的 PTAU 设置。
你可以删除不必要的文件:
rm pot14_0000.ptau pot14_0001.ptau pot14_0002.ptau pot14_beacon.ptau
📜 电路编译
现在让我们通过在 build/circuits
文件夹下运行以下命令来编译电路:
circom ../../circuits/test.circom --r1cs circuit.r1cs --wasm circuit.wasm --prime bls12381 --sym circuit.sym
现在我们的电路被编译到了 build/circuits/circuit.sym
、build/circuits/circuit.r1cs
和 build/circuits/circuit.wasm
文件中。
altbn-128 和 bls12-381 椭圆曲线目前被 snarkjs 支持。altbn-128 曲线仅在 Ethereum 上支持。然而,在 TON 上只支持 bls12-381 曲线。
让我们通过输入以下命令来检查我们电路的约 束大小:
node ../../../snarkjs/build/cli.cjs r1cs info circuit.r1cs
因此,正确的结果应该是:
[INFO] snarkJS: Curve: bls12-381
[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
现在我们可以通过执行以下操作来生成参考 zkey:
node ../../../snarkjs/build/cli.cjs zkey new circuit.r1cs pot14_final.ptau circuit_0000.zkey
然后我们将以下贡献添加到 zkey 中:
echo "some random text" | node ../../../snarkjs/build/cli.cjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st Contributor Name" -v
接下来,让我们导出最终的 zkey:
echo "another random text" | node ../../../snarkjs/build/cli.cjs zkey contribute circuit_0001.zkey circuit_final.zkey
现在我们的最终 zkey 存在于 build/circuits/circuit_final.zkey
文件中。然后通过输入以下内容来验证 zkey:
node ../../../snarkjs/build/cli.cjs zkey verify circuit.r1cs pot14_final.ptau circuit_final.zkey
最后,是时候生成验证密钥了:
node ../../../snarkjs/build/cli.cjs zkey export verificationkey circuit_final.zkey verification_key.json
然后我们将删除不必要的文件:
rm circuit_0000.zkey circuit_0001.zkey
在完成上述过程后,build/circuits
文件夹应如下显示:
build
└── circuits
├── circuit_final.zkey
├── circuit.r1cs
├── circuit.sym
├── circuit.wasm
├── pot14_final.ptau
└── verification_key.json