From 7e7876a0939dea3dfb7ac6e20fa09c10225f940b Mon Sep 17 00:00:00 2001 From: Frederik Zwilling Date: Fri, 14 Jul 2023 16:28:41 +0200 Subject: [PATCH 1/2] new basic DApp using zkKYC without repeatability --- contracts/BasicKYCExampleDApp.sol | 87 +++++++++++ test/contracts/BasicKYCExampleDAppTest.ts | 175 ++++++++++++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 contracts/BasicKYCExampleDApp.sol create mode 100644 test/contracts/BasicKYCExampleDAppTest.ts diff --git a/contracts/BasicKYCExampleDApp.sol b/contracts/BasicKYCExampleDApp.sol new file mode 100644 index 0000000..ec176de --- /dev/null +++ b/contracts/BasicKYCExampleDApp.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./VerificationSBT.sol"; +import "./interfaces/IZkKYCVerifier.sol"; + +/** + * @title BasicKYCExampleDApp + * @author Galactica dev team + * @notice A simple DApp that requires a zkKYC proof to issue a Verification SBT. + * Registration can be repeated when the previous Verification SBT expired. + * The ZKP only check that the user has a valid zkKYC. It does not have other disclosures and does not include fraud investigation. + */ +contract BasicKYCExampleDApp { + VerificationSBT public SBT; + IZkKYCVerifier public verifierWrapper; + + constructor(VerificationSBT _SBT, IZkKYCVerifier _verifierWrapper) { + SBT = _SBT; + verifierWrapper = _verifierWrapper; + } + + /** + * @notice isVerified checks if an address provided a valid ZKP and got a verification SBT + * @param account the address to check + */ + function isVerified(address account) public view returns (bool) { + return SBT.isVerificationSBTValid(account, address(this)); + } + + function registerKYC( + uint[2] memory a, + uint[2][2] memory b, + uint[2] memory c, + uint[] memory input + ) public { + require( + !SBT.isVerificationSBTValid(msg.sender, address(this)), + "The user already has a verification SBT. Please wait until it expires." + ); + + bytes32 humanID = bytes32(input[verifierWrapper.INDEX_HUMAN_ID()]); + uint dAppAddress = input[verifierWrapper.INDEX_DAPP_ID()]; + + // check that the public dAppAddress is correct + require( + dAppAddress == uint(uint160(address(this))), + "incorrect dAppAddress" + ); + + // check the zk proof + require(verifierWrapper.verifyProof(a, b, c, input), "invalid proof"); + + //afterwards we mint the verification SBT + uint256[2] memory userPubKey = [ + input[verifierWrapper.INDEX_USER_PUBKEY_AX()], + input[verifierWrapper.INDEX_USER_PUBKEY_AY()] + ]; + uint amountInstitutions = verifierWrapper + .getAmountFraudInvestigationInstitutions(); + bytes32[] memory encryptedData = new bytes32[](amountInstitutions * 2); + for (uint i = 0; i < amountInstitutions; i++) { + encryptedData[2 * i] = bytes32( + input[verifierWrapper.START_INDEX_ENCRYPTED_DATA() + 2 * i] + ); + encryptedData[2 * i + 1] = bytes32( + input[verifierWrapper.START_INDEX_ENCRYPTED_DATA() + 2 * i + 1] + ); + } + uint expirationTime = input[ + verifierWrapper.INDEX_VERIFICATION_EXPIRATION() + ]; + uint256[2] memory providerPubKey = [ + input[verifierWrapper.INDEX_PROVIDER_PUBKEY_AX()], + input[verifierWrapper.INDEX_PROVIDER_PUBKEY_AY()] + ]; + SBT.mintVerificationSBT( + msg.sender, + verifierWrapper, + expirationTime, + encryptedData, + userPubKey, + humanID, + providerPubKey + ); + } +} diff --git a/test/contracts/BasicKYCExampleDAppTest.ts b/test/contracts/BasicKYCExampleDAppTest.ts new file mode 100644 index 0000000..ea14282 --- /dev/null +++ b/test/contracts/BasicKYCExampleDAppTest.ts @@ -0,0 +1,175 @@ +/* Copyright (C) 2023 Galactica Network. This file is part of zkKYC. zkKYC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. zkKYC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +const snarkjs = require('snarkjs'); +const hre = require('hardhat'); + +import chai from 'chai'; +chai.config.includeStack = true; +const { expect } = chai; + +import { MockKYCRegistry } from '../../typechain-types/contracts/mock/MockKYCRegistry'; +import { BasicKYCExampleDApp } from '../../typechain-types/contracts/BasicKYCExampleDApp'; +import { VerificationSBT } from '../../typechain-types/contracts/VerificationSBT'; +import { ZkKYC } from '../../typechain-types/contracts/ZkKYC'; +import { ZkKYCVerifier } from '../../typechain-types/contracts/ZkKYCVerifier'; +import { + fromDecToHex, + processProof, + processPublicSignals, + fromHexToBytes32, +} from '../../lib/helpers'; +import { generateSampleZkKYC, generateZkKYCProofInput } from '../../scripts/generateZKKYCInput'; +import { ZKCertificate } from '../../lib/zkCertificate'; + + +describe('BasicKYCExampleDApp', async () => { + // reset the testing chain so we can perform time related tests + /* await hre.network.provider.send('hardhat_reset'); */ + let zkKycSC: ZkKYC; + let zkKYCVerifier: ZkKYCVerifier; + let mockKYCRegistry: MockKYCRegistry; + let verificationSBT: VerificationSBT; + let basicExampleDApp: BasicKYCExampleDApp; + + let deployer: SignerWithAddress; + let user: SignerWithAddress; + let zkKYC: ZKCertificate; + let sampleInput: any, circuitWasmPath: string, circuitZkeyPath: string; + + beforeEach(async () => { + // TODO: use fixture instead + // reset the testing chain so we can perform time related tests + await hre.network.provider.send('hardhat_reset'); + + [deployer, user] = await hre.ethers.getSigners(); + + // set up KYCRegistry, ZkKYCVerifier, ZkKYC + const mockKYCRegistryFactory = await ethers.getContractFactory( + 'MockKYCRegistry', + deployer + ); + mockKYCRegistry = + await mockKYCRegistryFactory.deploy() as MockKYCRegistry; + + const zkKYCVerifierFactory = await ethers.getContractFactory( + 'ZkKYCVerifier', + deployer + ); + zkKYCVerifier = + await zkKYCVerifierFactory.deploy() as ZkKYCVerifier; + + const zkKYCFactory = await ethers.getContractFactory( + 'ZkKYC', + deployer + ); + zkKycSC = (await zkKYCFactory.deploy( + deployer.address, + zkKYCVerifier.address, + mockKYCRegistry.address, + [] + )) as ZkKYC; + await zkKYCVerifier.deployed(); + + const verificationSBTFactory = await ethers.getContractFactory( + 'VerificationSBT', + deployer + ); + verificationSBT = await verificationSBTFactory.deploy() as VerificationSBT; + + + const repeatableZKPTestFactory = await ethers.getContractFactory( + 'BasicKYCExampleDApp', + deployer + ); + basicExampleDApp = await repeatableZKPTestFactory.deploy( + verificationSBT.address, + zkKycSC.address, + ) as BasicKYCExampleDApp; + + // inputs to create proof + zkKYC = await generateSampleZkKYC(); + sampleInput = await generateZkKYCProofInput(zkKYC, 0, basicExampleDApp.address); + sampleInput.dAppAddress = basicExampleDApp.address; + + // advance time a bit to set it later in the test + sampleInput.currentTime += 100; + + // get signer object authorized to use the zkKYC record + user = await hre.ethers.getImpersonatedSigner(sampleInput.userAddress); + + circuitWasmPath = './circuits/build/zkKYC.wasm'; + circuitZkeyPath = './circuits/build/zkKYC.zkey'; + }); + + it('should issue VerificationSBT on correct proof and refuse to re-register before expiration', async () => { + let { proof, publicSignals } = await snarkjs.groth16.fullProve( + sampleInput, + circuitWasmPath, + circuitZkeyPath + ); + + const publicRoot = publicSignals[await zkKycSC.INDEX_ROOT()]; + const publicTime = parseInt(publicSignals[await zkKycSC.INDEX_CURRENT_TIME()], 10); + // set the merkle root to the correct one + await mockKYCRegistry.setMerkleRoot( + fromHexToBytes32(fromDecToHex(publicRoot)) + ); + + // set time to the public time + await hre.network.provider.send('evm_setNextBlockTimestamp', [publicTime]); + await hre.network.provider.send('evm_mine'); + + let [a, b, c] = processProof(proof); + + let publicInputs = processPublicSignals(publicSignals); + await basicExampleDApp.connect(user).registerKYC(a, b, c, publicInputs); + + expect(await verificationSBT.isVerificationSBTValid(user.address, basicExampleDApp.address)).to.be.true; + + expect(basicExampleDApp.connect(user).registerKYC(a, b, c, publicInputs)).to.be.revertedWith("user already has a verification SBT"); + + // wait until verification SBT expires to renew it + const sbt = await verificationSBT.getVerificationSBTInfo(user.address, basicExampleDApp.address); + let laterProofInput = { ...sampleInput }; + laterProofInput.currentTime = sbt.expirationTime.toNumber() + 1; + await hre.network.provider.send('evm_setNextBlockTimestamp', [laterProofInput.currentTime]); + await hre.network.provider.send('evm_mine'); + + expect(await verificationSBT.isVerificationSBTValid(user.address, basicExampleDApp.address)).to.be.false; + + let laterProof = await snarkjs.groth16.fullProve( + laterProofInput, + circuitWasmPath, + circuitZkeyPath + ); + [a, b, c] = processProof(laterProof.proof); + publicInputs = processPublicSignals(laterProof.publicSignals); + await basicExampleDApp.connect(user).registerKYC(a, b, c, laterProof.publicSignals); + + expect(await verificationSBT.isVerificationSBTValid(user.address, basicExampleDApp.address)).to.be.true; + }); + + it('should catch incorrect proof', async () => { + const { proof, publicSignals } = await snarkjs.groth16.fullProve( + sampleInput, + circuitWasmPath, + circuitZkeyPath + ); + + const publicRoot = publicSignals[await zkKycSC.INDEX_ROOT()]; + // set the merkle root to the correct one + + await mockKYCRegistry.setMerkleRoot( + fromHexToBytes32(fromDecToHex(publicRoot)) + ); + let [a, b, c] = processProof(proof); + + let publicInputs = processPublicSignals(publicSignals); + + // switch c, a to get an incorrect proof + // it doesn't fail on time because the time change remains from the previous test + await expect(basicExampleDApp.connect(user).registerKYC(c, b, a, publicInputs)) + .to.be.reverted; + }); +}); From afeee934f1f3d888c9739ca93bc66010a7bdf47b Mon Sep 17 00:00:00 2001 From: Frederik Zwilling Date: Fri, 14 Jul 2023 16:38:51 +0200 Subject: [PATCH 2/2] deployment script for BasicKYCExampleDApp --- contracts/BasicKYCExampleDApp.sol | 1 - scripts/deployBasicKYCExampleDApp.ts | 29 ++++++++++++++++++++++++++++ scripts/deployDevnetGuardian.ts | 8 ++++---- scripts/deployExampleDApp.ts | 8 ++++---- scripts/deployRepeatableZKPTest.ts | 4 ++-- scripts/getMerkleProofFromChain.ts | 10 +++++----- scripts/issueZkKYC.ts | 8 ++++---- test/contracts/RepeatableZKPTest.ts | 1 + 8 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 scripts/deployBasicKYCExampleDApp.ts diff --git a/contracts/BasicKYCExampleDApp.sol b/contracts/BasicKYCExampleDApp.sol index ec176de..af27620 100644 --- a/contracts/BasicKYCExampleDApp.sol +++ b/contracts/BasicKYCExampleDApp.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "./VerificationSBT.sol"; import "./interfaces/IZkKYCVerifier.sol"; diff --git a/scripts/deployBasicKYCExampleDApp.ts b/scripts/deployBasicKYCExampleDApp.ts new file mode 100644 index 0000000..76f9bbe --- /dev/null +++ b/scripts/deployBasicKYCExampleDApp.ts @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 Galactica Network. This file is part of zkKYC. zkKYC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. zkKYC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import hre from "hardhat"; +import { deploySC } from '../lib/hardhatHelpers'; + +const log = console.log; + + +async function main() { + // parameters + const verificationSBT = '0xc1a96F7DD532fa4B774C41f9Eb853893314cB036'; + const zkKYC = '0x4568bBf22031930e35F13E1A15BdF7a619a60539'; // you can reuse the zkKYC smart contract from the deployment of the RepeatableZKPTest + + // wallets + const [deployer] = await hre.ethers.getSigners(); + log(`Using account ${deployer.address} to deploy contracts`); + log(`Account balance: ${(await deployer.getBalance()).toString()}`); + + // deploying everything + await deploySC('BasicKYCExampleDApp', true, {}, + [verificationSBT, zkKYC] + ); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/deployDevnetGuardian.ts b/scripts/deployDevnetGuardian.ts index 73b9e95..06a2309 100644 --- a/scripts/deployDevnetGuardian.ts +++ b/scripts/deployDevnetGuardian.ts @@ -10,14 +10,14 @@ const log = console.log; */ async function main() { // parameters - const centerRegistryAddr = '0x2547a64b4CDe45ad691BC393aBe20D405cD5139D'; - const recordRegistryAddr = '0x8b7f9322F2CF92908eDB02a76DD8A2cAd6E566B5'; + const centerRegistryAddr = '0x4De49e2047eE726B833fa815bf7392958245832d'; + const recordRegistryAddr = '0x8eD8311ED65eBe2b11ED8cB7076E779c1030F9cF'; // wallets - const [ deployer ] = await hre.ethers.getSigners(); + const [deployer] = await hre.ethers.getSigners(); log(`Using account ${deployer.address} to deploy contracts`); log(`Account balance: ${(await deployer.getBalance()).toString()}`); - + // deploying everything const devnetGuardian = await deploySC('DevnetGuardian', true, {}, [recordRegistryAddr]); log(`DevnetGuardian deployed to: ${devnetGuardian.address}`); diff --git a/scripts/deployExampleDApp.ts b/scripts/deployExampleDApp.ts index ef54649..1bf1df9 100644 --- a/scripts/deployExampleDApp.ts +++ b/scripts/deployExampleDApp.ts @@ -7,14 +7,14 @@ const log = console.log; async function main() { // parameters - const verificationSBT = '0x4E49d2383158568F5d4A30075e63614Dd7459060'; - const ageProofZkKYC = '0x71d80ea7744302E5b1cFD61a7a26153FF221ca9E'; + const verificationSBT = '0xc1a96F7DD532fa4B774C41f9Eb853893314cB036'; + const ageProofZkKYC = '0x7790dDa9E7569bc3580E675D75Ad115E7B35c6ff'; // wallets - const [ deployer ] = await hre.ethers.getSigners(); + const [deployer] = await hre.ethers.getSigners(); log(`Using account ${deployer.address} to deploy contracts`); log(`Account balance: ${(await deployer.getBalance()).toString()}`); - + // deploying everything const mockDApp = await deploySC('MockDApp', true, {}, [verificationSBT, ageProofZkKYC] diff --git a/scripts/deployRepeatableZKPTest.ts b/scripts/deployRepeatableZKPTest.ts index 46078f0..7ad88a8 100644 --- a/scripts/deployRepeatableZKPTest.ts +++ b/scripts/deployRepeatableZKPTest.ts @@ -7,8 +7,8 @@ const log = console.log; async function main() { // parameters - const verificationSBT = '0x4E49d2383158568F5d4A30075e63614Dd7459060'; - const zkKYCRegistry = '0x855d8DeF49d550df733Afb848aC723AEEBa58adF'; + const verificationSBT = '0xc1a96F7DD532fa4B774C41f9Eb853893314cB036'; + const zkKYCRegistry = '0x8eD8311ED65eBe2b11ED8cB7076E779c1030F9cF'; // wallets const [deployer] = await hre.ethers.getSigners(); diff --git a/scripts/getMerkleProofFromChain.ts b/scripts/getMerkleProofFromChain.ts index ae34e26..b92a681 100644 --- a/scripts/getMerkleProofFromChain.ts +++ b/scripts/getMerkleProofFromChain.ts @@ -8,9 +8,9 @@ import { queryOnChainLeaves } from "../lib/queryMerkleTree"; * @description Script for creating a merkle tree for testing from a list of UTXOs, benchmark version */ async function main() { - const registryAddress = "0x8b7f9322F2CF92908eDB02a76DD8A2cAd6E566B5"; + const registryAddress = "0x8eD8311ED65eBe2b11ED8cB7076E779c1030F9cF"; const leavesToProve = [ - "19630604862894493237865119507631642105595355222686969752403793856928034143008", + "1722999490154515264044226908745492848723838509493895212716723397473228533371", ]; // Create a new poseidon instance for hashing @@ -23,8 +23,8 @@ async function main() { const merkleTree = new MerkleTree(merkleDepth, poseidon); const leaves = await queryOnChainLeaves(ethers, registryAddress); const batchSize = 10_000; - for (let i = 0; i < leaves.length; i += batchSize){ - merkleTree.insertLeaves(leaves.slice(i, i+batchSize)); + for (let i = 0; i < leaves.length; i += batchSize) { + merkleTree.insertLeaves(leaves.slice(i, i + batchSize)); } console.log(`Merkle leaves: ${merkleTree.tree[0]}`) @@ -43,7 +43,7 @@ async function main() { console.log(`Merkle proof for ${leaf}:\n`, JSON.stringify(output, null, 2)); } } - + // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch((error) => { diff --git a/scripts/issueZkKYC.ts b/scripts/issueZkKYC.ts index 1a1dc02..bd07395 100644 --- a/scripts/issueZkKYC.ts +++ b/scripts/issueZkKYC.ts @@ -5,15 +5,15 @@ import { fromDecToHex, fromHexToBytes32 } from "../lib/helpers"; async function main() { // parameters - const centerRegistryAddr = '0x529ab188ecf1fA75FA5cA99c46ABb8a69e470075'; - const recordRegistryAddr = '0x855d8DeF49d550df733Afb848aC723AEEBa58adF'; + const centerRegistryAddr = '0x4De49e2047eE726B833fa815bf7392958245832d'; + const recordRegistryAddr = '0x8eD8311ED65eBe2b11ED8cB7076E779c1030F9cF'; const zkKYCLeafHashes = [ - '19630604862894493237865119507631642105595355222686969752403793856928034143008', + '13553445873695920927397403858740937949838667812849138092988169793799956616387', '913338630289763938167212770624253461411251029088142596559861590717003723041', ]; // wallets - const [ deployer ] = await ethers.getSigners(); + const [deployer] = await ethers.getSigners(); console.log(`Using account ${deployer.address} as KYC provider`); console.log(`Account balance: ${(await deployer.getBalance()).toString()}`); console.log(); diff --git a/test/contracts/RepeatableZKPTest.ts b/test/contracts/RepeatableZKPTest.ts index 9360bd7..e29af13 100644 --- a/test/contracts/RepeatableZKPTest.ts +++ b/test/contracts/RepeatableZKPTest.ts @@ -57,6 +57,7 @@ describe('RepeatableZKPTest', async () => { 'ZkKYCVerifier', deployer ); + zkKYCVerifier = await zkKYCVerifierFactory.deploy() as ZkKYCVerifier;