From 0b287c40c204ccfd33de550705d2df815146d1dc Mon Sep 17 00:00:00 2001 From: rymnc <43716372+rymnc@users.noreply.github.com> Date: Thu, 24 Nov 2022 12:09:44 +0530 Subject: [PATCH] feat(rln): store valid group ids from interep --- contracts/InterepTest.sol | 60 +++++++++++++++++++ contracts/ValidGroupStorage.sol | 36 ++++++++++++ hardhat.config.ts | 8 ++- package-lock.json | 100 ++++++++++++++++++++++++++++++++ package.json | 4 +- test/validGroupStorage.ts | 86 +++++++++++++++++++++++++++ 6 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 contracts/InterepTest.sol create mode 100644 contracts/ValidGroupStorage.sol create mode 100644 test/validGroupStorage.ts diff --git a/contracts/InterepTest.sol b/contracts/InterepTest.sol new file mode 100644 index 0000000..55912bb --- /dev/null +++ b/contracts/InterepTest.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@interep/contracts/IInterep.sol"; +import "@semaphore-protocol/contracts/interfaces/IVerifier.sol"; +import "@semaphore-protocol/contracts/base/SemaphoreCore.sol"; +import "@semaphore-protocol/contracts/base/SemaphoreConstants.sol"; + +contract InterepTest is IInterep, SemaphoreCore { + mapping(uint256 => Group) public groups; + + /// @dev mimics https://github.com/interep-project/contracts/blob/main/contracts/Interep.sol but ignores the verification mechanism + constructor() {} + + /// @dev See {IInterep-updateGroups}. + function updateGroups(Group[] calldata _groups) external override { + for (uint8 i = 0; i < _groups.length; i++) { + uint256 groupId = uint256( + keccak256( + abi.encodePacked(_groups[i].provider, _groups[i].name) + ) + ) % SNARK_SCALAR_FIELD; + + _updateGroup(groupId, _groups[i]); + } + } + + /// @dev See {IInterep-getRoot}. + function getRoot(uint256 groupId) public view override returns (uint256) { + return groups[groupId].root; + } + + /// @dev See {IInterep-getDepth}. + function getDepth(uint256 groupId) public view override returns (uint8) { + return groups[groupId].depth; + } + + /// @dev Updates an Interep group. + /// @param groupId: Id of the group. + /// @param group: Group data. + function _updateGroup(uint256 groupId, Group calldata group) private { + groups[groupId] = group; + + emit GroupUpdated( + groupId, + group.provider, + group.name, + group.root, + group.depth + ); + } + + function verifyProof( + uint256 groupId, + bytes32 signal, + uint256 nullifierHash, + uint256 externalNullifier, + uint256[8] calldata proof + ) external override {} +} diff --git a/contracts/ValidGroupStorage.sol b/contracts/ValidGroupStorage.sol new file mode 100644 index 0000000..80509e6 --- /dev/null +++ b/contracts/ValidGroupStorage.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@interep/contracts/Interep.sol"; + +contract ValidGroupStorage { + mapping(uint256 => bool) public validGroups; + + Interep public interep; + + struct Group { + bytes32 provider; + bytes32 name; + } + + constructor(address _interep, Group[] memory _groups) { + interep = Interep(_interep); + for (uint8 i = 0; i < _groups.length; i++) { + uint256 groupId = uint256( + keccak256( + abi.encodePacked(_groups[i].provider, _groups[i].name) + ) + ) % SNARK_SCALAR_FIELD; + (bytes32 provider, bytes32 name, , ) = interep.groups(groupId); + if (provider == _groups[i].provider && name == _groups[i].name) { + validGroups[groupId] = true; + } else { + revert("[ValidGroupStorage] Invalid group"); + } + } + } + + function isValidGroup(uint256 _groupId) public view returns (bool) { + return validGroups[_groupId]; + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index e4911f5..b0ef733 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -23,7 +23,13 @@ task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { // Go to https://hardhat.org/config/ to learn more const config: HardhatUserConfig = { - solidity: "0.8.15", + solidity: { + compilers: [{ + version: "0.8.4", + }, { + version: "0.8.15" + }], + }, networks: { goerli: { url: GOERLI_URL, diff --git a/package-lock.json b/package-lock.json index d269f8f..883e9de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "dotenv": "^16.0.1" }, "devDependencies": { + "@interep/contracts": "0.6.0", "@nomiclabs/hardhat-ethers": "^2.0.6", "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-waffle": "^2.0.3", + "@semaphore-protocol/contracts": "2.6.1", "@types/mocha": "^9.1.1", "chai": "^4.3.6", "ethereum-waffle": "^3.4.4", @@ -23,6 +25,22 @@ "typescript": "^4.7.4" } }, + "node_modules/@appliedzkp/semaphore-contracts": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@appliedzkp/semaphore-contracts/-/semaphore-contracts-0.8.1.tgz", + "integrity": "sha512-pIaTPgsxbNEXBcoGjt3qjh/+Uhl/tlfQSDR7261GYRGWdgO0T3/F/ewv/8U2WtlnnUmy83LQ1Y50+Iys9X8E6Q==", + "dev": true, + "dependencies": { + "@openzeppelin/contracts": "^4.4.2", + "@zk-kit/incremental-merkle-tree.sol": "^0.3.1" + } + }, + "node_modules/@appliedzkp/semaphore-contracts/node_modules/@zk-kit/incremental-merkle-tree.sol": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@zk-kit/incremental-merkle-tree.sol/-/incremental-merkle-tree.sol-0.3.1.tgz", + "integrity": "sha512-85rZpeSJGeR0yEiIbJwWrZgA68ovc62XiOHcMvAZmJtISsYMVHqzN6w4ax9Ke6bE70sj5V0OSPZ89vgJZ0zxgg==", + "dev": true + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1178,6 +1196,16 @@ "@ethersproject/strings": "^5.6.1" } }, + "node_modules/@interep/contracts": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@interep/contracts/-/contracts-0.6.0.tgz", + "integrity": "sha512-Tot6yy6dHCJJsM+2b7sBMN03TEu1sPW2Qzymu2ItbDXh3MmrbUaWblwZ+U5x/UE3ZpeSfP+ZFzGO1d1b35kJug==", + "dev": true, + "dependencies": { + "@appliedzkp/semaphore-contracts": "^0.8.0", + "@openzeppelin/contracts": "^4.5.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", @@ -1355,6 +1383,12 @@ "hardhat": "^2.0.0" } }, + "node_modules/@openzeppelin/contracts": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz", + "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==", + "dev": true + }, "node_modules/@resolver-engine/core": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz", @@ -1481,6 +1515,16 @@ "@scure/base": "~1.1.0" } }, + "node_modules/@semaphore-protocol/contracts": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@semaphore-protocol/contracts/-/contracts-2.6.1.tgz", + "integrity": "sha512-USEw3eZT4im7n8PpsoYyEEKiabmEtUCLMKIuZkMdjSA8k3jg25Y7WR07XFWqKur0OEb4n1nCX3+/ROTWKWSlJA==", + "dev": true, + "dependencies": { + "@openzeppelin/contracts": "4.7.3", + "@zk-kit/incremental-merkle-tree.sol": "1.3.1" + } + }, "node_modules/@sentry/core": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", @@ -1937,6 +1981,12 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "node_modules/@zk-kit/incremental-merkle-tree.sol": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@zk-kit/incremental-merkle-tree.sol/-/incremental-merkle-tree.sol-1.3.1.tgz", + "integrity": "sha512-KdGPoEooSX5MJH7oBE7MCRt/Aej+6n7U6v81w9fIzorSbykNuKH5lMbjkDAsis5xhp2qBC+y1V0xYBA2SNMt8A==", + "dev": true + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -20662,6 +20712,24 @@ } }, "dependencies": { + "@appliedzkp/semaphore-contracts": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@appliedzkp/semaphore-contracts/-/semaphore-contracts-0.8.1.tgz", + "integrity": "sha512-pIaTPgsxbNEXBcoGjt3qjh/+Uhl/tlfQSDR7261GYRGWdgO0T3/F/ewv/8U2WtlnnUmy83LQ1Y50+Iys9X8E6Q==", + "dev": true, + "requires": { + "@openzeppelin/contracts": "^4.4.2", + "@zk-kit/incremental-merkle-tree.sol": "^0.3.1" + }, + "dependencies": { + "@zk-kit/incremental-merkle-tree.sol": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@zk-kit/incremental-merkle-tree.sol/-/incremental-merkle-tree.sol-0.3.1.tgz", + "integrity": "sha512-85rZpeSJGeR0yEiIbJwWrZgA68ovc62XiOHcMvAZmJtISsYMVHqzN6w4ax9Ke6bE70sj5V0OSPZ89vgJZ0zxgg==", + "dev": true + } + } + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -21450,6 +21518,16 @@ "@ethersproject/strings": "^5.6.1" } }, + "@interep/contracts": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@interep/contracts/-/contracts-0.6.0.tgz", + "integrity": "sha512-Tot6yy6dHCJJsM+2b7sBMN03TEu1sPW2Qzymu2ItbDXh3MmrbUaWblwZ+U5x/UE3ZpeSfP+ZFzGO1d1b35kJug==", + "dev": true, + "requires": { + "@appliedzkp/semaphore-contracts": "^0.8.0", + "@openzeppelin/contracts": "^4.5.0" + } + }, "@jridgewell/resolve-uri": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", @@ -21590,6 +21668,12 @@ "@types/web3": "1.0.19" } }, + "@openzeppelin/contracts": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz", + "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==", + "dev": true + }, "@resolver-engine/core": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz", @@ -21706,6 +21790,16 @@ "@scure/base": "~1.1.0" } }, + "@semaphore-protocol/contracts": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@semaphore-protocol/contracts/-/contracts-2.6.1.tgz", + "integrity": "sha512-USEw3eZT4im7n8PpsoYyEEKiabmEtUCLMKIuZkMdjSA8k3jg25Y7WR07XFWqKur0OEb4n1nCX3+/ROTWKWSlJA==", + "dev": true, + "requires": { + "@openzeppelin/contracts": "4.7.3", + "@zk-kit/incremental-merkle-tree.sol": "1.3.1" + } + }, "@sentry/core": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", @@ -22134,6 +22228,12 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "@zk-kit/incremental-merkle-tree.sol": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@zk-kit/incremental-merkle-tree.sol/-/incremental-merkle-tree.sol-1.3.1.tgz", + "integrity": "sha512-KdGPoEooSX5MJH7oBE7MCRt/Aej+6n7U6v81w9fIzorSbykNuKH5lMbjkDAsis5xhp2qBC+y1V0xYBA2SNMt8A==", + "dev": true + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", diff --git a/package.json b/package.json index aa2e152..861e055 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "name": "hardhat-project", "devDependencies": { + "@interep/contracts": "0.6.0", + "@semaphore-protocol/contracts": "2.6.1", "@nomiclabs/hardhat-ethers": "^2.0.6", "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-waffle": "^2.0.3", @@ -17,4 +19,4 @@ "dependencies": { "dotenv": "^16.0.1" } -} +} \ No newline at end of file diff --git a/test/validGroupStorage.ts b/test/validGroupStorage.ts new file mode 100644 index 0000000..0264c68 --- /dev/null +++ b/test/validGroupStorage.ts @@ -0,0 +1,86 @@ +import {expect} from "chai"; +import { Contract } from "ethers"; +import {ethers} from "hardhat"; + +const sToBytes32 = (str: string): string => { + return ethers.utils.formatBytes32String(str); +} + +const SNARK_SCALAR_FIELD = BigInt( + "21888242871839275222246405745257275088548364400416034343698204186575808495617" +) + +const createGroupId = (provider: string, name: string): bigint => { + const providerBytes = sToBytes32(provider); + const nameBytes = sToBytes32(name); + return BigInt(ethers.utils.solidityKeccak256(["bytes32", "bytes32"], [providerBytes, nameBytes])) % SNARK_SCALAR_FIELD +} + +const providers = ['github', 'twitter', 'reddit']; +const tiers = ['bronze', 'silver', 'gold']; + +const scaffoldInterep = async () => { + // Deploy interep + const InterepTest = await ethers.getContractFactory("InterepTest"); + const interepTest = await InterepTest.deploy(); + await interepTest.deployed(); + const interepAddress = interepTest.address; + + // add all combinations of providers and tiers into an array + const groups = providers.flatMap(provider => tiers.map(tier => { + return { + provider: sToBytes32(provider), + name: sToBytes32(tier), + root: 1, + depth: 10, + } + })); + // insert groups into interep membership contract + const groupInsertionTx = await interepTest.updateGroups(groups); + await groupInsertionTx.wait(); + + return {interepTest, groups} +} + +const scaffold = async () => { + const {interepTest, groups} = await scaffoldInterep(); + const interepAddress = interepTest.address; + + // create valid group storage contract for rln + const ValidGroupStorage = await ethers.getContractFactory("ValidGroupStorage"); + const filteredGroups = groups + .filter(group => group.name !== sToBytes32('bronze')); + const validGroupStorage = await ValidGroupStorage.deploy(interepAddress, filteredGroups); + await validGroupStorage.deployed(); + expect(validGroupStorage.address).to.not.equal(0); + + return validGroupStorage +} + +describe("Valid Group Storage", () => { + let validGroupStorage: Contract; + beforeEach(async () => { + validGroupStorage = await scaffold(); + }) + + it('should not deploy if an invalid group is passed in constructor', async () => { + const {interepTest} = await scaffoldInterep(); + const interepAddress = interepTest.address; + + const ValidGroupStorage = await ethers.getContractFactory("ValidGroupStorage"); + expect(ValidGroupStorage.deploy(interepAddress, [{ + provider: sToBytes32('github'), + name: sToBytes32('diamond'), + }])).to.be.revertedWith("[ValidGroupStorage] Invalid group"); + }) + + it("should return true for valid group", async () => { + const valid = await validGroupStorage.isValidGroup(createGroupId('github', 'silver')); + expect(valid).to.be.true; + }); + + it("should return false for invalid group", async () => { + const valid = await validGroupStorage.isValidGroup(createGroupId('github', 'bronze')); + expect(valid).to.be.false; + }); +}) \ No newline at end of file