Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pubkey check #83

Merged
merged 7 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/zk-certificates/contracts/GuardianRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.0;

import './Ownable.sol';
import './interfaces/IGuardianRegistry.sol';

/**
* @title GuardianInfo struct containing data about a guardian's registration
Expand All @@ -26,7 +27,7 @@ struct GuardianIssuer {

/// @author Galactica dev team
/// @title Smart contract storing whitelist of GNET guardians, for example KYC provider guardians
contract GuardianRegistry is Ownable {
contract GuardianRegistry is Ownable, IGuardianRegistry {
// a short description to describe which type of zkCertificate is managed by Guardians in this Registry

string public description;
Expand Down Expand Up @@ -74,7 +75,6 @@ contract GuardianRegistry is Ownable {
string calldata metadataURL
) public onlyOwner {
guardians[guardian].whitelisted = true;
// dev: do we need to check that the pubkey here indeed relates to the guardian?
guardians[guardian].pubKey = pubKey;
guardians[guardian].metadataURL = metadataURL;

Expand Down
11 changes: 6 additions & 5 deletions packages/zk-certificates/contracts/ZkCertificateRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {SNARK_SCALAR_FIELD} from './helpers/Globals.sol';

import {PoseidonT3} from './helpers/Poseidon.sol';

import {GuardianRegistry, GuardianInfo} from './GuardianRegistry.sol';
import {GuardianInfo} from './GuardianRegistry.sol';

import {IGuardianRegistry} from './interfaces/IGuardianRegistry.sol';
import {IZkCertificateRegistry} from './interfaces/IZkCertificateRegistry.sol';

import {Ownable} from './Ownable.sol';
Expand Down Expand Up @@ -67,7 +68,7 @@ contract ZkCertificateRegistry is
mapping(bytes32 => uint) public ZkCertificateHashToQueueTime;
mapping(bytes32 => address) public ZkCertificateHashToCommitedGuardian;

GuardianRegistry public _GuardianRegistry;
IGuardianRegistry public guardianRegistry;
event zkCertificateAddition(
bytes32 indexed zkCertificateLeafHash,
address indexed Guardian,
Expand Down Expand Up @@ -133,7 +134,7 @@ contract ZkCertificateRegistry is

// Set merkle root
merkleRoots.push(currentZero);
_GuardianRegistry = GuardianRegistry(GuardianRegistry_);
guardianRegistry = IGuardianRegistry(GuardianRegistry_);

// Set the block height at which the contract was initialized
initBlockHeight = block.number;
Expand All @@ -160,7 +161,7 @@ contract ZkCertificateRegistry is
// since we are adding a new zkCertificate record, we assume that the leaf is of zero value
bytes32 currentLeafHash = ZERO_VALUE;
require(
_GuardianRegistry.isWhitelisted(msg.sender),
guardianRegistry.isWhitelisted(msg.sender),
'ZkCertificateRegistry: not a Guardian'
);

Expand Down Expand Up @@ -233,7 +234,7 @@ contract ZkCertificateRegistry is
*/
function registerToQueue(bytes32 zkCertificateHash) public {
require(
_GuardianRegistry.isWhitelisted(msg.sender),
guardianRegistry.isWhitelisted(msg.sender),
'ZkCertificateRegistry: not a Guardian'
);
// we need to determine the time until which the Guardian needs to add/revoke the zkCertificate after registration to the queue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

interface IGuardianRegistry {
function isWhitelisted(address issuer) external view returns (bool);

function pubKeyToAddress(
uint256 pubKeyX,
uint256 pubKeyY
) external view returns (address);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {IGuardianRegistry} from './IGuardianRegistry.sol';

/// @author Galactica dev team
interface IZkCertificateRegistry {
function merkleRoot() external view returns (bytes32);
function merkleRootIndex(bytes32) external view returns (uint);
function merkleRootValidIndex() external view returns (uint);
function verifyMerkleRoot(bytes32) external view returns (bool);
function guardianRegistry() external view returns (IGuardianRegistry);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
pragma solidity ^0.8.0;

import {IZkCertificateRegistry} from "../interfaces/IZkCertificateRegistry.sol";
import {IGuardianRegistry} from "../interfaces/IGuardianRegistry.sol";

/// @author Galactica dev team
contract MockZkCertificateRegistry is IZkCertificateRegistry {
bytes32 public constant MERKLE_ROOT_INITIAL_VALUE = bytes32(0);
bytes32[] public merkleRoots = [MERKLE_ROOT_INITIAL_VALUE];
uint256 public merkleRootValidIndex = 1;
IGuardianRegistry public guardianRegistry;

mapping (bytes32 => uint256) public merkleRootIndex;

Expand All @@ -28,4 +30,8 @@ contract MockZkCertificateRegistry is IZkCertificateRegistry {
uint _merkleRootIndex = merkleRootIndex[_merkleRoot];
return _merkleRootIndex >= merkleRootValidIndex;
}

function setGuardianRegistry(address _guardianRegistry) public {
guardianRegistry = IGuardianRegistry(_guardianRegistry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IZkCertificateRegistry} from '../interfaces/IZkCertificateRegistry.sol';
import {BokkyPooBahsDateTimeLibrary} from '../libraries/BokkyPooBahsDateTimeLibrary.sol';
import {IGalacticaInstitution} from '../interfaces/IGalacticaInstitution.sol';
import {ICircomVerifier} from '../interfaces/ICircomVerifier.sol';
import {IGuardianRegistry} from '../interfaces/IGuardianRegistry.sol';

/**
* @title Smart contract demo for Galactica zkKYC requirements
Expand Down Expand Up @@ -215,6 +216,17 @@ contract AgeCitizenshipKYC is Ownable, IAgeCitizenshipKYCVerifier {
'the age threshold is not proven'
);

// check that the pubkey belongs to a whitelisted provider
IGuardianRegistry guardianRegistry = KYCRegistry.guardianRegistry();
address guardianAddress = guardianRegistry.pubKeyToAddress(
input[INDEX_PROVIDER_PUBKEY_AX],
input[INDEX_PROVIDER_PUBKEY_AY]
);
require(
guardianRegistry.isWhitelisted(guardianAddress),
'the provider is not whitelisted'
);

// check that the institution public key corresponds to the onchain one;
for (uint i = 0; i < fraudInvestigationInstitutions.length; i++) {
require(
Expand Down
13 changes: 13 additions & 0 deletions packages/zk-certificates/contracts/verifierWrappers/ZkKYC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import '../Ownable.sol';
import '../interfaces/IZkKYCVerifier.sol';
import '../interfaces/IZkCertificateRegistry.sol';
import '../interfaces/IGalacticaInstitution.sol';
import '../interfaces/IGuardianRegistry.sol';

/// @author Galactica dev team
/// @title a wrapper for verifier of ZkKYC record existence
Expand Down Expand Up @@ -139,6 +140,18 @@ contract ZkKYC is Ownable {
timeDiff <= timeDifferenceTolerance,
'the current time is incorrect'
);

// check that the pubkey belongs to a whitelisted provider
IGuardianRegistry guardianRegistry = KYCRegistry.guardianRegistry();
address guardianAddress = guardianRegistry.pubKeyToAddress(
input[INDEX_PROVIDER_PUBKEY_AX],
input[INDEX_PROVIDER_PUBKEY_AY]
);
require(
guardianRegistry.isWhitelisted(guardianAddress),
'the provider is not whitelisted'
);

// check that the institution public keys corresponds to the onchain ones;
for (uint i = 0; i < fraudInvestigationInstitutions.length; i++) {
require(
Expand Down
53 changes: 53 additions & 0 deletions packages/zk-certificates/test/contracts/AgeCitizenshipKYC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '../../scripts/generateZkKYCInput';
import type { AgeCitizenshipKYC } from '../../typechain-types/contracts/AgeCitizenshipKYC';
import type { AgeCitizenshipKYCVerifier } from '../../typechain-types/contracts/AgeCitizenshipKYCVerifier';
import type { GuardianRegistry } from '../../typechain-types/contracts/GuardianRegistry';
import type { KYCRequirementsDemoDApp } from '../../typechain-types/contracts/KYCRequirementsDemoDApp';
import type { MockZkCertificateRegistry } from '../../typechain-types/contracts/mock/MockZkCertificateRegistry';
import type { VerificationSBT } from '../../typechain-types/contracts/VerificationSBT';
Expand Down Expand Up @@ -94,9 +95,28 @@ describe('AgeCitizenshipKYCVerifier SC', () => {
ageCitizenshipKYC.address,
)) as KYCRequirementsDemoDApp;

const guardianRegistryFactory = await ethers.getContractFactory(
'GuardianRegistry',
deployer,
);
const guardianRegistry = (await guardianRegistryFactory.deploy(
'',
)) as GuardianRegistry;
await mockZkCertificateRegistry.setGuardianRegistry(
guardianRegistry.address,
);

// default zkKYC
const zkKYC = await generateSampleZkKYC();

// Assuming zkKYC is your sample ZkCertificate
const { providerData } = zkKYC;
await guardianRegistry.grantGuardianRole(
deployer.address,
[providerData.ax, providerData.ay],
'',
);

// default inputs to create proof
const sampleInput = await generateZkKYCProofInput(
zkKYC,
Expand Down Expand Up @@ -149,6 +169,7 @@ describe('AgeCitizenshipKYCVerifier SC', () => {
mockZkCertificateRegistry,
verificationSBT,
kycRequirementsDemoDApp,
guardianRegistry, // Add this to the returned object
},
poseidon,
zkKYC,
Expand Down Expand Up @@ -463,4 +484,36 @@ describe('AgeCitizenshipKYCVerifier SC', () => {
.checkRequirements(proof.piA, proof.piB, proof.piC, proof.publicInputs),
).to.be.revertedWith('the age threshold is not proven');
});

it('revert if provider is not whitelisted', async () => {
const { acc, sc, proof } = await loadFixture(deploy);

const publicRoot =
proof.publicInputs[await sc.ageCitizenshipKYC.INDEX_ROOT()];

// set the merkle root to the correct one
await sc.mockZkCertificateRegistry.setMerkleRoot(
fromHexToBytes32(fromDecToHex(publicRoot)),
);

const publicTime = parseInt(
proof.publicInputs[await sc.ageCitizenshipKYC.INDEX_CURRENT_TIME()],
16,
);

// set time to the public time
await hre.network.provider.send('evm_setNextBlockTimestamp', [
publicTime + 10,
]);
await hre.network.provider.send('evm_mine');

// Revoke the guardian role
await sc.guardianRegistry.revokeGuardianRole(acc.deployer.address);

await expect(
sc.ageCitizenshipKYC
.connect(acc.user)
.verifyProof(proof.piA, proof.piB, proof.piC, proof.publicInputs),
).to.be.revertedWith('the provider is not whitelisted');
});
});
25 changes: 24 additions & 1 deletion packages/zk-certificates/test/contracts/AirdropGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
generateSampleZkKYC,
generateZkKYCProofInput,
} from '../../scripts/generateZkKYCInput';
import type { GuardianRegistry } from '../../typechain-types';
import type { AirdropGateway } from '../../typechain-types/contracts/AirdropGateway';
import type { GalacticaOfficialSBT } from '../../typechain-types/contracts/GalacticaOfficialSBT';
import type { MockGalacticaInstitution } from '../../typechain-types/contracts/mock/MockGalacticaInstitution';
Expand Down Expand Up @@ -50,6 +51,7 @@ describe('AirdropGateway', () => {
let defaultAdminRole: string;
let sampleInput: any, circuitWasmPath: string, circuitZkeyPath: string;
let sampleInput2: any;
let guardianRegistry: GuardianRegistry;

beforeEach(async () => {
// reset the testing chain so we can perform time related tests
Expand Down Expand Up @@ -149,6 +151,27 @@ describe('AirdropGateway', () => {

const tokenFactory = await ethers.getContractFactory('MockToken', deployer);
rewardToken = (await tokenFactory.deploy(deployer.address)) as MockToken;

// Deploy GuardianRegistry
const GuardianRegistryFactory =
await ethers.getContractFactory('GuardianRegistry');
guardianRegistry = (await GuardianRegistryFactory.deploy(
'https://example.com/metadata',
)) as GuardianRegistry;
await guardianRegistry.deployed();

// Set GuardianRegistry in MockZkCertificateRegistry
await mockZkCertificateRegistry.setGuardianRegistry(
guardianRegistry.address,
);

// Grant guardian role to owner
const { providerData } = zkKYC;
await guardianRegistry.grantGuardianRole(
deployer.address,
[providerData.ax, providerData.ay],
'https://example.com/guardian-metadata',
);
});

it('only owner can whitelist or dewhitelist clients', async () => {
Expand Down Expand Up @@ -315,7 +338,7 @@ describe('AirdropGateway', () => {

// distribution parameters
const requiredSBTs = [GalaSBT.address, GalaSBT2.address];
const registrationStartTime = publicTime + 10;
const registrationStartTime = publicTime + 100;
const registrationEndTime = registrationStartTime + 10000;
const claimStartTime = registrationEndTime + 10000;
const claimEndTime = claimStartTime + 10000;
Expand Down
24 changes: 24 additions & 0 deletions packages/zk-certificates/test/contracts/BasicKYCExampleDAppTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
generateZkKYCProofInput,
} from '../../scripts/generateZkKYCInput';
import type { BasicKYCExampleDApp } from '../../typechain-types/contracts/BasicKYCExampleDApp';
import type { GuardianRegistry } from '../../typechain-types/contracts/GuardianRegistry';
import type { MockZkCertificateRegistry } from '../../typechain-types/contracts/mock/MockZkCertificateRegistry';
import type { VerificationSBT } from '../../typechain-types/contracts/VerificationSBT';
import type { ZkKYC } from '../../typechain-types/contracts/ZkKYC';
Expand All @@ -32,6 +33,7 @@ describe('BasicKYCExampleDApp', () => {
let mockZkCertificateRegistry: MockZkCertificateRegistry;
let verificationSBT: VerificationSBT;
let basicExampleDApp: BasicKYCExampleDApp;
let guardianRegistry: GuardianRegistry;

let deployer: SignerWithAddress;
let user: SignerWithAddress;
Expand Down Expand Up @@ -104,6 +106,27 @@ describe('BasicKYCExampleDApp', () => {

circuitWasmPath = './circuits/build/zkKYC.wasm';
circuitZkeyPath = './circuits/build/zkKYC.zkey';

// Deploy GuardianRegistry
const GuardianRegistryFactory =
await ethers.getContractFactory('GuardianRegistry');
guardianRegistry = (await GuardianRegistryFactory.deploy(
'https://example.com/metadata',
)) as GuardianRegistry;
await guardianRegistry.deployed();

// Set GuardianRegistry in MockZkCertificateRegistry
await mockZkCertificateRegistry.setGuardianRegistry(
guardianRegistry.address,
);

// Approve the provider's public key
const providerPubKey = [zkKYC.providerData.ax, zkKYC.providerData.ay];
await guardianRegistry.grantGuardianRole(
deployer.address,
providerPubKey,
'https://example.com/provider-metadata',
);
});

it('should issue VerificationSBT on correct proof and refuse to re-register before expiration', async () => {
Expand All @@ -130,6 +153,7 @@ describe('BasicKYCExampleDApp', () => {
let [piA, piB, piC] = processProof(proof);

let publicInputs = processPublicSignals(publicSignals);

await basicExampleDApp
.connect(user)
.registerKYC(piA, piB, piC, publicInputs);
Expand Down
Loading
Loading