零知识证明DApp开发实践身份证明/以太坊

Posted 跨链技术践行者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了零知识证明DApp开发实践身份证明/以太坊相关的知识,希望对你有一定的参考价值。

在这个教程中,我们将学习如何开发一个基于以太坊的零知识身份证明DApp, 学习如何开发Circom零知识电路、如何生成并方法Solidity零知识验证智能合约, 以及如何利用javascript在链下生成零知识证据,并在教程最后提供完整的源代码下载。

1、零知识身份证明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].out;
  }

  // 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开发实践身份证明/以太坊的主要内容,如果未能解决你的问题,请参考以下文章

Miximus以太坊混币应用EthSnarks/零知识证明

零知识证明实战在线旅游业ZoKrates

以太坊dapp智能合约ERC20的生态教程

ZoKrates实战在线旅游业零知识证明

如何从零开始学习区块链技术——推荐从以太坊开发DApp开始

以太坊DApp开发实战基础