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