This repository has been archived by the owner on Oct 18, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #26 from Galactica-corp/basicNonRepeatableKYC
Basic non repeatable kyc
- Loading branch information
Showing
9 changed files
with
310 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.0; | ||
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 | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <https://www.gnu.org/licenses/>. */ | ||
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <https://www.gnu.org/licenses/>. */ | ||
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; | ||
}); | ||
}); |
Oops, something went wrong.