利用circom和snarkjs实现zkSNARK零知识证明的智能合约应用

0. 实验环境

  • Ubuntu Linux 20.04
  • Node.js v18.12.1
  • circom@0.5.46
  • snarkjs@0.5.0

apt安装的nodejs版本一直报错,需要安装更高版本的nodejs,直接从官网下载nodejs再配置bin目录的环境变量即可。

将下载的node-v18.12.1-linux-x64安装包解压到/user/local目录下。

然后配置环境变量,在/etc/profile文件末尾添加如下内容:

export PATH=/usr/local/node-v18.12.1-linux-x64/bin:$PATH

然后执行刷新环境变量的命令:

source /etc/profile

npm官方内容被屏蔽了,需要设置npm的国内源:

npm config set registry http://mirrors.cloud.tencent.com/npm/

安装circom和snarkjs:

npm install -g snarkjs
npm install -g circom

这里npm直接安装的circom版本是0.5.46,不过现在基本上通用的都是circom 2.0了,circom 2.0的安装教程来自官网:https://docs.circom.io/getting-started/installation/

1. 理解zkSNARK

很多博客与综述都在讲解zkSNARK的实现原理,而本篇文章主要从应用的角度阐述zkSNARK。估计读者都是计算机安全领域的探索者,已经有zkSNARK的理论基础,我简略地写下这篇文章是想展示一下zkSNARK的具体实现流程,让学者们在理论研究之余尝试一下调用库实现zkSNARK的步骤,加深对zkSNARK的理解。

在本示例中,首先选取两个大素数p和q,向外界公开两数相乘的结果n=p*q,使用zkSNARK向验证者证明自己知道整数n的两个素因子,但是不想透漏p和q的具体数值,实现“零知识”证明。

2. 编写电路circuit.circom并编译

zkSNARK需要将待证明的程序运算关系转化成多项式方程,向外界证明等式成立。其中,多项式的运算过程以电路的形式表示,证明自己的私有输入与公开参数满足电路关系,但是不泄露私有输入的具体内容。

新建文件命名为circuit.circom,输入如下内容:

template circuit() {
    signal private input a;
    signal private input b;
    signal output c;
    c <== a*b;
}

component main = circuit();

circom的语法在官网有详细解释。

signal理解为电路中的连线,不能随意赋值。与一般编程语言的变量在内存中任意赋值不同。

这一段代码的意思是,私有输入a和b的乘积必须与c相等,称为“约束条件”。

执行编译命令:

circom circuit.circom --r1cs --wasm --sym

生成三个文件circuit.r1cs、circuit.wasm、circuit.sym。

查看约束电路的信息:

snarkjs ri 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
snarkjs rp circuit.r1cs

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

3. 根据私有输入input.json生成witness

新建文件input.json,编写私有输入:

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

这里的a和b就是电路中的两个私有输入。

生成witness:

snarkjs wc circuit.wasm input.json witness.wtns

将witness转化成json格式:

snarkjs wej witness.wtns witness.json

[
 "1",
 "21",
 "3",
 "7"
]

这里的数组就是r1cs里面的向量。

4. 可信设置

利用powersOfTau进行可信设置:

snarkjs ptn bn128 10 powersOfTau10_0000.ptau

snarkjs pt2 powersOfTau10_0000.ptau pot10_final.ptau

5. 生成验证密钥

执行如下命令:

snarkjs zkn circuit.r1cs pot10_final.ptau circuit_0000.zkey

snarkjs zkev circuit_0000.zkey

生成了验证密钥文件verification_key.json:

{
 "protocol": "groth16",
 "curve": "bn128",
 "nPublic": 1,
 "vk_alpha_1": [
  "1",
  "2",
  "1"
 ],
 "vk_beta_2": [
  [
   "10857046999023057135944570762232829481370756359578518086990519993285655852781",
   "11559732032986387107991004021392285783925812861821192530917403151452391805634"
  ],
  [
   "8495653923123431417604973247489272438418190587263600148770280649306958101930",
   "4082367875863433681332203403145435568316851327593401208105741076214120093531"
  ],
  [
   "1",
   "0"
  ]
 ],
 "vk_gamma_2": [
  [
   "10857046999023057135944570762232829481370756359578518086990519993285655852781",
   "11559732032986387107991004021392285783925812861821192530917403151452391805634"
  ],
  [
   "8495653923123431417604973247489272438418190587263600148770280649306958101930",
   "4082367875863433681332203403145435568316851327593401208105741076214120093531"
  ],
  [
   "1",
   "0"
  ]
 ],
 "vk_delta_2": [
  [
   "10857046999023057135944570762232829481370756359578518086990519993285655852781",
   "11559732032986387107991004021392285783925812861821192530917403151452391805634"
  ],
  [
   "8495653923123431417604973247489272438418190587263600148770280649306958101930",
   "4082367875863433681332203403145435568316851327593401208105741076214120093531"
  ],
  [
   "1",
   "0"
  ]
 ],
 "vk_alphabeta_12": [
  [
   [
    "17264119758069723980713015158403419364912226240334615592005620718956030922389",
    "1300711225518851207585954685848229181392358478699795190245709208408267917898"
   ],
   [
    "8894217292938489450175280157304813535227569267786222825147475294561798790624",
    "1829859855596098509359522796979920150769875799037311140071969971193843357227"
   ],
   [
    "4968700049505451466697923764727215585075098085662966862137174841375779106779",
    "12814315002058128940449527172080950701976819591738376253772993495204862218736"
   ]
  ],
  [
   [
    "4233474252585134102088637248223601499779641130562251948384759786370563844606",
    "9420544134055737381096389798327244442442230840902787283326002357297404128074"
   ],
   [
    "13457906610892676317612909831857663099224588803620954529514857102808143524905",
    "5122435115068592725432309312491733755581898052459744089947319066829791570839"
   ],
   [
    "8891987925005301465158626530377582234132838601606565363865129986128301774627",
    "440796048150724096437130979851431985500142692666486515369083499585648077975"
   ]
  ]
 ],
 "IC": [
  [
   "0",
   "1",
   "0"
  ],
  [
   "1",
   "21888242871839275222246405745257275088696311157297823662689037894645226208581",
   "1"
  ]
 ]
}

6. 生成proof

执行生成proof的命令:

snarkjs g16p circuit_0000.zkey witness.wtns

生成了公共参数public.json:

[
 "21"
]

生成了证明proof.json:

{
 "pi_a": [
  "19989501194150232030287836231786249461121144567445024823064735350287474506223",
  "21655946435202999151519101772821474971537905530411470752874989158703798792312",
  "1"
 ],
 "pi_b": [
  [
   "16849405748801758296101495735786887364124830646811289624179679686910073081847",
   "8262572077976115071765367472495699796017916450544371038971924549367335179729"
  ],
  [
   "12958773022321817503613723687947680235067294837906750271427696212863641553842",
   "7087347573327104818719324688424308951919471849681614073561604633419212094"
  ],
  [
   "1",
   "0"
  ]
 ],
 "pi_c": [
  "9246929219549012289734255058845496297360333953855121701793792902617376293680",
  "21873404623428113396140883503409567642025944127192624541920936238800869635767",
  "1"
 ],
 "protocol": "groth16",
 "curve": "bn128"
}

7. 验证proof

执行验证proof的命令:

snarkjs g16v verification_key.json public.json proof.json

[INFO]  snarkJS: OK!

8. 导出solidity智能合约源码

执行命令生成验证proof的智能合约solidity源码:

snarkjs zkey export solidityverifier circuit_0000.zkey verifier.sol
//
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// 2019 OKIMS
//      ported to solidity 0.6
//      fixed linter warnings
//      added requiere error messages
//
//
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.11;
library Pairing {
    struct G1Point {
        uint X;
        uint Y;
    }
    // Encoding of field elements is: X[0] * z + X[1]
    struct G2Point {
        uint[2] X;
        uint[2] Y;
    }
    /// @return the generator of G1
    function P1() internal pure returns (G1Point memory) {
        return G1Point(1, 2);
    }
    /// @return the generator of G2
    function P2() internal pure returns (G2Point memory) {
        // Original code point
        return G2Point(
            [11559732032986387107991004021392285783925812861821192530917403151452391805634,
             10857046999023057135944570762232829481370756359578518086990519993285655852781],
            [4082367875863433681332203403145435568316851327593401208105741076214120093531,
             8495653923123431417604973247489272438418190587263600148770280649306958101930]
        );

/*
        // Changed by Jordi point
        return G2Point(
            [10857046999023057135944570762232829481370756359578518086990519993285655852781,
             11559732032986387107991004021392285783925812861821192530917403151452391805634],
            [8495653923123431417604973247489272438418190587263600148770280649306958101930,
             4082367875863433681332203403145435568316851327593401208105741076214120093531]
        );
*/
    }
    /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
    function negate(G1Point memory p) internal pure returns (G1Point memory r) {
        // The prime q in the base field F_q for G1
        uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
        if (p.X == 0 && p.Y == 0)
            return G1Point(0, 0);
        return G1Point(p.X, q - (p.Y % q));
    }
    /// @return r the sum of two points of G1
    function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
        uint[4] memory input;
        input[0] = p1.X;
        input[1] = p1.Y;
        input[2] = p2.X;
        input[3] = p2.Y;
        bool success;
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
            // Use "invalid" to make gas estimation work
            switch success case 0 { invalid() }
        }
        require(success,"pairing-add-failed");
    }
    /// @return r the product of a point on G1 and a scalar, i.e.
    /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
    function scalar_mul(G1Point memory p, uint s) internal view returns (G1Point memory r) {
        uint[3] memory input;
        input[0] = p.X;
        input[1] = p.Y;
        input[2] = s;
        bool success;
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
            // Use "invalid" to make gas estimation work
            switch success case 0 { invalid() }
        }
        require (success,"pairing-mul-failed");
    }
    /// @return the result of computing the pairing check
    /// e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
    /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should
    /// return true.
    function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) {
        require(p1.length == p2.length,"pairing-lengths-failed");
        uint elements = p1.length;
        uint inputSize = elements * 6;
        uint[] memory input = new uint[](inputSize);
        for (uint i = 0; i < elements; i++)
        {
            input[i * 6 + 0] = p1[i].X;
            input[i * 6 + 1] = p1[i].Y;
            input[i * 6 + 2] = p2[i].X[0];
            input[i * 6 + 3] = p2[i].X[1];
            input[i * 6 + 4] = p2[i].Y[0];
            input[i * 6 + 5] = p2[i].Y[1];
        }
        uint[1] memory out;
        bool success;
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
            // Use "invalid" to make gas estimation work
            switch success case 0 { invalid() }
        }
        require(success,"pairing-opcode-failed");
        return out[0] != 0;
    }
    /// Convenience method for a pairing check for two pairs.
    function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) {
        G1Point[] memory p1 = new G1Point[](2);
        G2Point[] memory p2 = new G2Point[](2);
        p1[0] = a1;
        p1[1] = b1;
        p2[0] = a2;
        p2[1] = b2;
        return pairing(p1, p2);
    }
    /// Convenience method for a pairing check for three pairs.
    function pairingProd3(
            G1Point memory a1, G2Point memory a2,
            G1Point memory b1, G2Point memory b2,
            G1Point memory c1, G2Point memory c2
    ) internal view returns (bool) {
        G1Point[] memory p1 = new G1Point[](3);
        G2Point[] memory p2 = new G2Point[](3);
        p1[0] = a1;
        p1[1] = b1;
        p1[2] = c1;
        p2[0] = a2;
        p2[1] = b2;
        p2[2] = c2;
        return pairing(p1, p2);
    }
    /// Convenience method for a pairing check for four pairs.
    function pairingProd4(
            G1Point memory a1, G2Point memory a2,
            G1Point memory b1, G2Point memory b2,
            G1Point memory c1, G2Point memory c2,
            G1Point memory d1, G2Point memory d2
    ) internal view returns (bool) {
        G1Point[] memory p1 = new G1Point[](4);
        G2Point[] memory p2 = new G2Point[](4);
        p1[0] = a1;
        p1[1] = b1;
        p1[2] = c1;
        p1[3] = d1;
        p2[0] = a2;
        p2[1] = b2;
        p2[2] = c2;
        p2[3] = d2;
        return pairing(p1, p2);
    }
}
contract Verifier {
    using Pairing for *;
    struct VerifyingKey {
        Pairing.G1Point alfa1;
        Pairing.G2Point beta2;
        Pairing.G2Point gamma2;
        Pairing.G2Point delta2;
        Pairing.G1Point[] IC;
    }
    struct Proof {
        Pairing.G1Point A;
        Pairing.G2Point B;
        Pairing.G1Point C;
    }
    function verifyingKey() internal pure returns (VerifyingKey memory vk) {
        vk.alfa1 = Pairing.G1Point(
            1,
            2
        );

        vk.beta2 = Pairing.G2Point(
            [11559732032986387107991004021392285783925812861821192530917403151452391805634,
             10857046999023057135944570762232829481370756359578518086990519993285655852781],
            [4082367875863433681332203403145435568316851327593401208105741076214120093531,
             8495653923123431417604973247489272438418190587263600148770280649306958101930]
        );
        vk.gamma2 = Pairing.G2Point(
            [11559732032986387107991004021392285783925812861821192530917403151452391805634,
             10857046999023057135944570762232829481370756359578518086990519993285655852781],
            [4082367875863433681332203403145435568316851327593401208105741076214120093531,
             8495653923123431417604973247489272438418190587263600148770280649306958101930]
        );
        vk.delta2 = Pairing.G2Point(
            [11559732032986387107991004021392285783925812861821192530917403151452391805634,
             10857046999023057135944570762232829481370756359578518086990519993285655852781],
            [4082367875863433681332203403145435568316851327593401208105741076214120093531,
             8495653923123431417604973247489272438418190587263600148770280649306958101930]
        );
        vk.IC = new Pairing.G1Point[](2);
        
        vk.IC[0] = Pairing.G1Point( 
            0,
            1
        );                                      
        
        vk.IC[1] = Pairing.G1Point( 
            1,
            21888242871839275222246405745257275088696311157297823662689037894645226208581
        );                                      
        
    }
    function verify(uint[] memory input, Proof memory proof) internal view returns (uint) {
        uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
        VerifyingKey memory vk = verifyingKey();
        require(input.length + 1 == vk.IC.length,"verifier-bad-input");
        // Compute the linear combination vk_x
        Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
        for (uint i = 0; i < input.length; i++) {
            require(input[i] < snark_scalar_field,"verifier-gte-snark-scalar-field");
            vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
        }
        vk_x = Pairing.addition(vk_x, vk.IC[0]);
        if (!Pairing.pairingProd4(
            Pairing.negate(proof.A), proof.B,
            vk.alfa1, vk.beta2,
            vk_x, vk.gamma2,
            proof.C, vk.delta2
        )) return 1;
        return 0;
    }
    /// @return r  bool true if proof is valid
    function verifyProof(
            uint[2] memory a,
            uint[2][2] memory b,
            uint[2] memory c,
            uint[1] memory input
        ) public view returns (bool r) {
        Proof memory proof;
        proof.A = Pairing.G1Point(a[0], a[1]);
        proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
        proof.C = Pairing.G1Point(c[0], c[1]);
        uint[] memory inputValues = new uint[](input.length);
        for(uint i = 0; i < input.length; i++){
            inputValues[i] = input[i];
        }
        if (verify(inputValues, proof) == 0) {
            return true;
        } else {
            return false;
        }
    }
}

9. 导出智能合约调用参数

执行以下命令:

snarkjs zkesc public.json proof.json

得到智能合约参数列表:

["0x2c31a81fc8fffb039b2bddc37bb14a140ab018433fd53c9466d00fe36bc639ef", "0x2fe0d4d12a03f74cc73ef9f647c4885f497d4f9dce0ff3dc9328f4a1add1b078"],[["0x12447337b8a54079c9abca925b273afddbd111adafeb0cfebaf454f5105f55d1", "0x25406cfa638aad3c35fbd02b9ffebf2251499ed5259c1f08948a7410ab4f8ff7"],["0x000402e4550530aa896b4969fc8b10caa7bde28d1a5507b36091a9af676e653e", "0x1ca667d5ff619b39d02f1ab56a3ed332cbd95aa691b1d4b02bab50d206958bb2"]],["0x147193935c7932b1bf375bfb0be81fee00713cf2660495361eeee7d514150330", "0x305be885a82d6047035324468dff233b3e0afa14bb16ecca04967f8fd645b6b7"],["0x0000000000000000000000000000000000000000000000000000000000000015"]

可能是生成的智能合约中涉及到不可用的opcode,remix和ganache都没有执行成功,后续我再测试一下geth等以太坊平台。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

易龙杨Longyang Yi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值