资讯详情

区块链 solidity 零知识证明DApp开发实践【身份证明/以太坊】

在本教程中,我们将学习如何发展基于以太坊的零知识认证DApp, 学习如何发展Circom零知识电路、如何生成并方法Solidity智能合约零知识验证, 以及如何使用Javascript在链下生成零知识证据,并在教程结束时提供完整的源代码下载。

区块链开发教程链接:以太坊|比特币|EOS|Tendermint|Hyperledger Fabric|Omni/USDT|Ripple

零知识身份证明DApp概述

我们将开发零知识应用程序来证明用户属于特定的组 使用过程如下图所示:

我们的开发过程分为以下步骤:

  • 开发零知识电路
  • 用于验证零知识电路的生成Solidity库
  • 智能合同的开发与集成Solidity库
  • 本地生成证据并在链上验证

2.零知识证明以太坊DApp开发环境建设

就像你不需要完全理解一样,就像你不需要完全理解就像你不需要完全理解就像你不需要完全理解一样HTTP协议也可以开发web同样的应用,已经有很多了 基于零知识的工具有助于开发工具DApp而有密码学或数学基础。

我推荐以下开发语言和工具链:

  • JavaScript/TypeScript:应用采用javascript/typescript由于以太坊生态得到了很好的支持
  • Solidity: 智能合约用Solidity发展,因为它很成熟,社区也很好
  • Truffle:使用Truffle框架作为智能合约的开发和部署
  • Circom:使用Circom开发零知识证明电路

3、CIRCOM零知识电路开发:判断私钥是否匹配公钥集

我们的目标是创建一个输入的私钥是否对应的电路 于输入的公钥集合之一。该电路的伪代码如下:

1 2 3 4 5 6 7 8 9 10 11 
// Note that a private key is a scalar value (int) // whereas a public key is a point in space (Tuple[int, int]) const zk_identity = (private_key, public_keys) => {   // derive_public_from_private is a function that   // returns a public key given a private key   derived_public_key = derive_public_from_private(private_key)   for (let pk in public_keys):     if derived_public_key === pk:       return true   return false } 

我们现在要开始使用它circom编写零知识电路了。circom可以查阅语法 其官方文件。

首先,创建项目文件夹并安装必要的依赖包:

1 2 3 4 5 6 7 
npm install circom circomlib snarkjs websnark  mkdir contracts mkdir circuits mkdir -p build/circuits  touch circuits/circuit.circom 

现在写电路文件circuit.circom,首先引入(incluude)必要的基础电路 并定义PublicKey模板:

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 
include "../node_modules/circomlib/circuits/bitify.circom"; include "../node_modules/circomlib/circuits/escalarmulfix.circom"; include "../node_modules/circomlib/circuits/comparators.circom";  template PublicKey() {   // Note: private key needs to be hashed, and then pruned   // to make sure its compatible with the babyJubJub curve   signal private input in;   signal output out[2];    component privBits = Num2Bits(253);   privBits.in <== in;    var BASE8 = [     5299619240641551281634865583518297030282874472190772894086521144482721001553,     16950150798460657717958625567821834550301663161624707787222815936182638968203   ];    component mulFix = EscalarMulFix(253, BASE8);   for (var i = 0; i < 253; i  ) {     mulFix.e[i] <== privBits.out[i];   }    out[0] <== mulFix.out[0];   out[1] <== mulFix.out[1]; } 

PublicKey模板的作用是在babyJubJub在曲线上找到私钥(电路输入)对应的公钥(电路输出)。 请注意,在上述电路中,我们将私钥私钥声明为私有信号,因此生成的证据不包括在内 任何可以重建输入私钥的信息。

一旦上述基本模块完成,我们现在可以构建零知识来证明电路的主逻辑 —— 验证 指定用户是否属于一组:

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 
include ...  template PublicKey() {   ... }  template ZkIdentity(groupSize) {   // Public Keys in the smart contract   // Note: this assumes that the publicKeys   // are all unique   signal input publicKeys[groupSize][2];    // Prover's private key   signal private input privateKey;    // Prover's derived public key   component publicKey = PublicKey();   publicKey.in <== privateKey;    // Make sure that derived public key needs to   // matche to at least one public key in the   // smart contract to validate their identity   var sum = 0;    // Create a component to check if two values are   // equal   component equals[groupSize][2];   for (var i = 0; i < groupSize; i  ) {     // Helper component to check if two     // values are equal     // We don't want to use ===     // as that will fail immediately if     // the predicate doesn't hold true     equals[i][0] = IsEqual();     equals[i][1] = IsEqual();      equals[i][0].in[0] <== publicKeys[i][0];     equals[i][0].in[1] <== publicKey.out[0];      equals[i][1].in[0] <== publicKeys[i][1];     equals[i][1].in[1] <== publicKey.out[1];      sum  = equals[i][0].out;     sum  = equals[i][1].ou;
  }

  // equals[i][j].out will return 1 if the values are equal
  // and 0 if the values are not equal
  // Therefore, if the derived public key (a point in space)
  // matches a public keys listed in the smart contract, the sum of
  // all the equals[i][j].out should be equal to 2
  sum === 2;
}


// Main entry point
component main = ZkIdentity(2);

现在我们编译、设置并生成该电路的Solidity验证器:

1
2
3
4
5
6
7
8
9
10
11
$(npm bin)/circom circuits/circuit.circom -o build/circuits/circuit.json

# snarkjs setup might take a few seconds
$(npm bin)/snarkjs setup --protocol groth -c build/circuits/circuit.json --pk build/circuits/provingKey.json --vk build/circuits/verifyingKey.json

# Generate solidity lib to verify proof
$(npm bin)/snarkjs generateverifier --pk build/circuits/provingKey.json --vk build/circuits/verifyingKey.json -v contracts/Verifier.sol

# You should now have a new "Verifier.sol" in your contracts directory
# $ ls contracts
# Migrations.sol Verifier.sol

注意我们使用groth协议生成证明密钥和验证密钥,因为我们希望使用websnark来生成证据, 因为websnark要比snarkjs性能好的多。

一旦完成上面的环节,我们就已经实现了零知识证明逻辑。下面的部分我们将 介绍如何使用生成的Solidity零知识验证合约。

4、Solidity零知识验证合约

在完成零知识电路的设置之后,会生成一个名为Verifier.sol的solidity库。如果 你查看这个文件的内容,就会看到其中包含如下的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...

  function verifyProof(
            uint[2] memory a,
            uint[2][2] memory b,
            uint[2] memory c,
            uint[4] 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;
        }
    }

...

这是用于验证零知识证据有效性的辅助函数。verifyProof函数接收4个参数,但是 我们只关心其中表示电路公共输入的input参数,我们将使用它在智能合约代码中 验证用户的身份。让我们看一下具体的实现代码:

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
pragma solidity 0.5.11;

import "./Verifier.sol";

contract ZkIdentity is Verifier {
    address public owner;
    uint256[2][2] public publicKeys;

    constructor() public {
        owner = msg.sender;
        publicKeys = [
            [
                11588997684490517626294634429607198421449322964619894214090255452938985192043,
                15263799208273363060537485776371352256460743310329028590780329826273136298011
            ],
            [
                3554016859368109379302439886604355056694273932204896584100714954675075151666,
                17802713187051641282792755605644920157679664448965917618898436110214540390950
            ]
        ];
    }

    function isInGroup(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[4] memory input // public inputs
    ) public view returns (bool) {
        if (
            input[0] != publicKeys[0][0] &&
            input[1] != publicKeys[0][1] &&
            input[2] != publicKeys[1][0] &&
            input[3] != publicKeys[1][1]
        ) {
            revert("Supplied public keys do not match contracts");
        }

        return verifyProof(a, b, c, input);
    }
}

我们创建一个新的合约ZkIdentity.sol,它继承自生成的Verifier.sol, 有一个包含2个成员公钥的初始群组,以及一个名为isInGroup的函数,该函数 首先验证电路的公开输入信号与智能合约中的群组一致,然后返回对输入 证据的验证结果。

逻辑并不复杂,不过的确也满足了我们的目标:验证一个用户属于特定 的群组而无需透露用户是谁。

在继续下面的部分之前,需要先部署合约到链上。

5、用JavaScript生成零知识证据并与智能合约交互

一旦我们完成了零知识电路并实现了智能合约逻辑,就可以生成证据 并调用智能合约的isInGroup方法进行验证了。

下面的伪代码展示了如何生成证据并利用智能合约进行验证,你可以 访问这里 查看完整的js代码:

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
// Assuming below already exists
const provingKey // provingKey.json
const circuit // zero-knowledge circuit we wrote
const zkIdentityContract // Zk-Identity contract instance

const privateKey  // Private key that corresponds to one of the public key in the smart contract
const publicKeys = [
  [
      11588997684490517626294634429607198421449322964619894214090255452938985192043n,
      15263799208273363060537485776371352256460743310329028590780329826273136298011n
  ],
  [
      3554016859368109379302439886604355056694273932204896584100714954675075151666n,
      17802713187051641282792755605644920157679664448965917618898436110214540390950n
  ]
]

const circuitInputs = {
  privateKey,
  publicKeys
}

const witness = circuit.calculateWitness(circuitInputs)
const proof = groth16GenProof(witness, provingKey)

const isInGroup = zkIdentityContract.isInGroup(
  proof.a,
  proof.b,
  proof.c,
  witness.publicSignals
)

运行js代码就可以证明你属于一个群组而无需透露你是谁!

教程的完整代码下载地址。


原文链接:A Practical Guide To Building Zero Knowledge dApps

汇智网翻译整理,转载请标明出处

 零知识证明DApp开发实践【身份证明/以太坊】 | 学习软件编程

标签: 集成电路true

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台