├── .commitlintrc.json ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── coverage.yml │ └── test.yml ├── .gitignore ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc.json ├── .solhint.json ├── LICENSE ├── README.md ├── contracts ├── IInterep.sol ├── Interep.sol └── package.json ├── hardhat.config.ts ├── package.json ├── scripts └── deploy.ts ├── static ├── semaphore.wasm └── semaphore.zkey ├── tasks ├── accounts.ts ├── deploy-interep.ts └── deploy-verifier.ts ├── test ├── Interep.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 120 10 | indent_size = 4 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DEFAULT_NETWORK=hardhat 2 | REPORT_GAS=true 3 | BACKEND_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 4 | INFURA_API_KEY=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 5 | COINMARKETCAP_API_KEY= 6 | ETHERSCAN_API_KEY= 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | 6 | # testing 7 | coverage 8 | coverage.json 9 | 10 | # docs 11 | docs 12 | 13 | # hardhat 14 | cache 15 | 16 | # types 17 | types 18 | 19 | # openzeppelin 20 | .openzeppelin 21 | 22 | # circuits 23 | circuits 24 | 25 | # production 26 | dist 27 | build 28 | 29 | # misc 30 | .DS_Store 31 | *.pem 32 | 33 | # debug 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | }, 5 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": "tsconfig.json" 9 | }, 10 | "plugins": ["@typescript-eslint"] 11 | } 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | coveralls: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 16.x 18 | 19 | - run: yarn --frozen-lockfile 20 | - run: yarn compile 21 | - run: yarn test:coverage 22 | 23 | - uses: coverallsapp/github-action@master 24 | with: 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 16.x 19 | 20 | - run: yarn --frozen-lockfile 21 | - run: yarn compile 22 | - run: yarn test:prod 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Testing 20 | coverage 21 | coverage.json 22 | 23 | # Dependency directories 24 | node_modules/ 25 | 26 | # TypeScript cache 27 | *.tsbuildinfo 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional eslint cache 33 | .eslintcache 34 | 35 | # Microbundle cache 36 | .rpt2_cache/ 37 | .rts2_cache_cjs/ 38 | .rts2_cache_es/ 39 | .rts2_cache_umd/ 40 | 41 | # Output of 'npm pack' 42 | *.tgz 43 | 44 | # Yarn Integrity file 45 | .yarn-integrity 46 | 47 | # dotenv environment variable files 48 | .env 49 | .env.development.local 50 | .env.test.local 51 | .env.production.local 52 | .env.local 53 | 54 | # Production 55 | build 56 | dist 57 | 58 | # Hardhat 59 | artifacts 60 | cache 61 | 62 | # Docusaurus cache and generated files 63 | .docusaurus 64 | .cache-loader 65 | 66 | # Stores VSCode versions used for testing VSCode extensions 67 | .vscode-test 68 | 69 | # yarn v2 70 | .yarn/cache 71 | .yarn/unplugged 72 | .yarn/build-state.yml 73 | .yarn/install-state.gz 74 | .pnp.* 75 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{ts,js}": ["prettier --write", "eslint --fix"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | 6 | # testing 7 | coverage 8 | coverage.json 9 | 10 | # hardhat 11 | cache 12 | 13 | # types 14 | types 15 | 16 | # openzeppelin 17 | .openzeppelin 18 | 19 | # circuits 20 | circuits 21 | 22 | # production 23 | dist 24 | build 25 | 26 | # misc 27 | .DS_Store 28 | *.pem 29 | 30 | # debug 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "always", 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "code-complexity": ["error", 7], 6 | "compiler-version": ["error", ">=0.8.0"], 7 | "const-name-snakecase": "off", 8 | "no-empty-blocks": "off", 9 | "constructor-syntax": "error", 10 | "func-visibility": ["error", { "ignoreConstructors": true }], 11 | "max-line-length": ["error", 120], 12 | "not-rely-on-time": "off", 13 | "prettier/prettier": [ 14 | "error", 15 | { 16 | "endOfLine": "auto" 17 | } 18 | ], 19 | "reason-string": ["warn", { "maxLength": 80 }] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Interep 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | Interep contracts 4 |

5 |

Interep Solidity smart contracts.

6 |

7 | 8 |

9 | 10 | 11 | 12 | 13 | Github license 14 | 15 | 16 | GitHub Workflow test 17 | 18 | 19 | Coveralls 20 | 21 | 22 | Linter eslint 23 | 24 | 25 | Code style prettier 26 | 27 | Repository top language 28 |

29 | 30 |
31 |

32 | 33 | 👥 Contributing 34 | 35 |   |   36 | 37 | 🤝 Code of conduct 38 | 39 |   |   40 | 41 | 🗣️ Chat & Support 42 | 43 |

44 |
45 | 46 | --- 47 | 48 | Please, visit our [web app](https://kovan.interep.link) or our [documentation website](https://docs.interep.link) for more details. 49 | 50 | ### Deployed contracts 51 | 52 | | | Kovan | Goerli | Arbitrum One | 53 | | ------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------ | 54 | | Interep | [0xF58D...53De](https://kovan.etherscan.io/address/0xF58D3b710cDD337df432e20a806Ad04f6CfE53De) | [0x9f44...eafb](https://goerli.etherscan.io/address/0x9f44be9F69aF1e049dCeCDb2d9296f36C49Ceafb) | | 55 | 56 | --- 57 | 58 | ## Install 59 | 60 | Clone this repository and install the dependencies: 61 | 62 | ```bash 63 | git clone https://github.com/interep-project/contracts.git 64 | cd contracts 65 | yarn # or `npm i` 66 | ``` 67 | 68 | ## Usage 69 | 70 | Copy the `.env.example` file and rename it `.env`. 71 | 72 | ### Compile 73 | 74 | Compile the smart contracts with Hardhat: 75 | 76 | ```bash 77 | yarn compile 78 | ``` 79 | 80 | ### Lint 81 | 82 | Lint the Solidity or the TypeScript code: 83 | 84 | ```bash 85 | yarn lint:sol 86 | yarn lint:ts 87 | # or yarn lint to lint both. 88 | ``` 89 | 90 | And check if the code is well formatted: 91 | 92 | ```bash 93 | yarn prettier 94 | ``` 95 | 96 | ### Test 97 | 98 | Run the Mocha tests: 99 | 100 | ```bash 101 | yarn test 102 | ``` 103 | 104 | ### Coverage 105 | 106 | Generate the code coverage report: 107 | 108 | ```bash 109 | yarn test:coverage 110 | ``` 111 | 112 | ### Report Gas 113 | 114 | See the gas usage per unit test and average gas per method call: 115 | 116 | ```bash 117 | yarn test:report-gas 118 | ``` 119 | 120 | ### Deploy 121 | 122 | Deploy a Semaphore verifier contract with depth = 20: 123 | 124 | ```bash 125 | yarn deploy:verifier # The resulting address will have to be used in the next step. 126 | ``` 127 | 128 | Deploy the Interep contract with one Semaphore verifier: 129 | 130 | ```bash 131 | yarn deploy:interep --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]' 132 | ``` 133 | 134 | If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, kovan, arbitrum). Or you can specify it as option: 135 | 136 | ```bash 137 | yarn deploy:interep --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3"}]' --network kovan 138 | yarn deploy:interep --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]' --network localhost 139 | ``` 140 | 141 | If you want to deploy the contracts on Goerli, Kovan or Arbitrum remember to provide a valid private key and an Infura API in your `.env` file. 142 | 143 | ### Preparing a local network 144 | 145 | Run a Hardhat Network in a stand-alone fashion: 146 | 147 | ```bash 148 | yarn start 149 | ``` 150 | 151 | Deploy Semaphore verifier and Interep contract: 152 | 153 | ```bash 154 | yarn deploy --network localhost 155 | ``` 156 | 157 | You can omit `--network localhost` if your `DEFAULT_NETWORK` env variable is equal to `localhost`. 158 | -------------------------------------------------------------------------------- /contracts/IInterep.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @title Interep interface. 5 | /// @dev Interface of a Interep contract. 6 | interface IInterep { 7 | struct Verifier { 8 | address contractAddress; 9 | uint8 merkleTreeDepth; 10 | } 11 | 12 | struct Group { 13 | bytes32 provider; 14 | bytes32 name; 15 | uint256 root; 16 | uint8 depth; 17 | } 18 | 19 | /// @dev Emitted when a Semaphore proof is verified. 20 | /// @param groupId: Id of the group. 21 | /// @param signal: Semaphore signal. 22 | event ProofVerified(uint256 indexed groupId, bytes32 signal); 23 | 24 | /// @dev Emitted when an Interep group is updated. 25 | /// @param groupId: Id of the group. 26 | /// @param provider: Provider of the group. 27 | /// @param name: Name of the group. 28 | /// @param root: Root hash of the tree. 29 | /// @param depth: Depth of the tree. 30 | event GroupUpdated( 31 | uint256 groupId, 32 | bytes32 indexed provider, 33 | bytes32 indexed name, 34 | uint256 root, 35 | uint8 indexed depth 36 | ); 37 | 38 | /// @dev Updates the Interep groups. 39 | /// @param groups: List of Interep groups. 40 | function updateGroups(Group[] calldata groups) external; 41 | 42 | /// @dev Saves the nullifier hash to avoid double signaling and emits an event 43 | /// if the zero-knowledge proof is valid. 44 | /// @param groupId: Id of the group. 45 | /// @param signal: Semaphore signal. 46 | /// @param nullifierHash: Nullifier hash. 47 | /// @param externalNullifier: External nullifier. 48 | /// @param proof: Zero-knowledge proof. 49 | function verifyProof( 50 | uint256 groupId, 51 | bytes32 signal, 52 | uint256 nullifierHash, 53 | uint256 externalNullifier, 54 | uint256[8] calldata proof 55 | ) external; 56 | 57 | /// @dev Returns the root hash of an Interep group. 58 | /// @param groupId: Id of the group. 59 | /// @return Root hash of the group. 60 | function getRoot(uint256 groupId) external view returns (uint256); 61 | 62 | /// @dev Returns the tree depth of an Interep group. 63 | /// @param groupId: Id of the group. 64 | /// @return Tree depth of the group. 65 | function getDepth(uint256 groupId) external view returns (uint8); 66 | } 67 | -------------------------------------------------------------------------------- /contracts/Interep.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./IInterep.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@semaphore-protocol/contracts/interfaces/IVerifier.sol"; 7 | import "@semaphore-protocol/contracts/base/SemaphoreCore.sol"; 8 | import "@semaphore-protocol/contracts/base/SemaphoreConstants.sol"; 9 | 10 | /// @title Interep 11 | /// @dev Interep is a collection of reputation Semaphore groups in which members 12 | /// can prove their Web2 reputation (or their membership in a group) without revealing their identity. 13 | /// Each Interep group is actually a Merkle tree, whose leaves represent the members of the group. 14 | /// Interep groups are saved off-chain but the Merkle tree roots of those groups are saved on-chain 15 | /// at regular intervals, so that users can verify their Semaphore ZK proof on-chain with this contract. 16 | contract Interep is IInterep, Ownable, SemaphoreCore { 17 | /// @dev Gets a tree depth and returns its verifier address. 18 | mapping(uint8 => IVerifier) public verifiers; 19 | 20 | /// @dev Gets a group id and returns the group data. 21 | mapping(uint256 => Group) public groups; 22 | 23 | /// @dev Checks if there is a verifier for the given tree depth. 24 | /// @param depth: Depth of the tree. 25 | modifier onlySupportedDepth(uint8 depth) { 26 | require(address(verifiers[depth]) != address(0), "Interep: tree depth is not supported"); 27 | _; 28 | } 29 | 30 | /// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs. 31 | /// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth). 32 | constructor(Verifier[] memory _verifiers) { 33 | for (uint8 i = 0; i < _verifiers.length; i++) { 34 | verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress); 35 | } 36 | } 37 | 38 | /// @dev See {IInterep-updateGroups}. 39 | function updateGroups(Group[] calldata _groups) external override onlyOwner { 40 | for (uint8 i = 0; i < _groups.length; i++) { 41 | uint256 groupId = uint256(keccak256(abi.encodePacked(_groups[i].provider, _groups[i].name))) % 42 | SNARK_SCALAR_FIELD; 43 | 44 | _updateGroup(groupId, _groups[i]); 45 | } 46 | } 47 | 48 | /// @dev See {IInterep-verifyProof}. 49 | function verifyProof( 50 | uint256 groupId, 51 | bytes32 signal, 52 | uint256 nullifierHash, 53 | uint256 externalNullifier, 54 | uint256[8] calldata proof 55 | ) external override { 56 | uint256 root = getRoot(groupId); 57 | uint8 depth = getDepth(groupId); 58 | 59 | require(depth != 0, "Interep: group does not exist"); 60 | 61 | IVerifier verifier = verifiers[depth]; 62 | 63 | _verifyProof(signal, root, nullifierHash, externalNullifier, proof, verifier); 64 | 65 | _saveNullifierHash(nullifierHash); 66 | 67 | emit ProofVerified(groupId, signal); 68 | } 69 | 70 | /// @dev See {IInterep-getRoot}. 71 | function getRoot(uint256 groupId) public view override returns (uint256) { 72 | return groups[groupId].root; 73 | } 74 | 75 | /// @dev See {IInterep-getDepth}. 76 | function getDepth(uint256 groupId) public view override returns (uint8) { 77 | return groups[groupId].depth; 78 | } 79 | 80 | /// @dev Updates an Interep group. 81 | /// @param groupId: Id of the group. 82 | /// @param group: Group data. 83 | function _updateGroup(uint256 groupId, Group calldata group) private onlySupportedDepth(group.depth) { 84 | groups[groupId] = group; 85 | 86 | emit GroupUpdated(groupId, group.provider, group.name, group.root, group.depth); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@interep/contracts", 3 | "version": "0.6.0", 4 | "description": "Interep smart contracts.", 5 | "license": "MIT", 6 | "files": [ 7 | "**/*.sol" 8 | ], 9 | "keywords": [ 10 | "blockchain", 11 | "ethereum", 12 | "hardhat", 13 | "smart-contracts", 14 | "semaphore", 15 | "interep", 16 | "identity", 17 | "solidity", 18 | "zero-knowledge", 19 | "zk-snarks", 20 | "zero-knowledge-proofs", 21 | "circom", 22 | "proof-of-membership" 23 | ], 24 | "homepage": "https://github.com/interep-project/contracts.git#readme", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/interep-project/contracts.git.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/interep-project/contracts.git/issues" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | }, 35 | "dependencies": { 36 | "@openzeppelin/contracts": "^4.5.0", 37 | "@semaphore-protocol/contracts": "2.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomiclabs/hardhat-etherscan" 2 | import "@nomiclabs/hardhat-waffle" 3 | import "@typechain/hardhat" 4 | import { config as dotenvConfig } from "dotenv" 5 | import "hardhat-dependency-compiler" 6 | import "hardhat-gas-reporter" 7 | import { HardhatUserConfig } from "hardhat/config" 8 | import { NetworksUserConfig } from "hardhat/types" 9 | import { resolve } from "path" 10 | import "solidity-coverage" 11 | import { config } from "./package.json" 12 | import "./tasks/accounts" 13 | import "./tasks/deploy-interep" 14 | import "./tasks/deploy-verifier" 15 | 16 | dotenvConfig({ path: resolve(__dirname, "./.env") }) 17 | 18 | function getNetworks(): NetworksUserConfig | undefined { 19 | if (process.env.INFURA_API_KEY && process.env.BACKEND_PRIVATE_KEY) { 20 | const infuraApiKey = process.env.INFURA_API_KEY 21 | const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`] 22 | 23 | return { 24 | goerli: { 25 | url: `https://goerli.infura.io/v3/${infuraApiKey}`, 26 | chainId: 5, 27 | accounts 28 | }, 29 | kovan: { 30 | url: `https://kovan.infura.io/v3/${infuraApiKey}`, 31 | chainId: 42, 32 | accounts 33 | }, 34 | arbitrum: { 35 | url: "https://arb1.arbitrum.io/rpc", 36 | chainId: 42161, 37 | accounts 38 | } 39 | } 40 | } 41 | } 42 | 43 | const hardhatConfig: HardhatUserConfig = { 44 | solidity: config.solidity, 45 | paths: { 46 | sources: config.paths.contracts, 47 | tests: config.paths.tests, 48 | cache: config.paths.cache, 49 | artifacts: config.paths.build.contracts 50 | }, 51 | dependencyCompiler: { 52 | paths: ["@semaphore-protocol/contracts/verifiers/Verifier20.sol"] 53 | }, 54 | defaultNetwork: process.env.DEFAULT_NETWORK || "hardhat", 55 | networks: { 56 | localhost: { 57 | timeout: 50000 58 | }, 59 | ...getNetworks() 60 | }, 61 | gasReporter: { 62 | currency: "USD", 63 | enabled: process.env.REPORT_GAS === "true", 64 | coinmarketcap: process.env.COINMARKETCAP_API_KEY 65 | }, 66 | typechain: { 67 | outDir: config.paths.build.typechain, 68 | target: "ethers-v5" 69 | }, 70 | etherscan: { 71 | apiKey: process.env.ETHERSCAN_API_KEY 72 | } 73 | } 74 | 75 | export default hardhatConfig 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interep-contracts", 3 | "version": "1.0.0", 4 | "description": "Interep smart contracts.", 5 | "license": "MIT", 6 | "homepage": "https://github.com/interep-project/contracts", 7 | "private": "true", 8 | "scripts": { 9 | "start": "hardhat node", 10 | "compile": "hardhat compile", 11 | "deploy": "hardhat run ./scripts/deploy.ts", 12 | "deploy:interep": "hardhat deploy:interep", 13 | "deploy:verifier": "hardhat deploy:verifier", 14 | "test": "hardhat test", 15 | "test:report-gas": "REPORT_GAS=true hardhat test", 16 | "test:coverage": "hardhat coverage", 17 | "test:prod": "yarn lint && yarn test", 18 | "typechain": "hardhat typechain", 19 | "lint": "yarn lint:sol && yarn lint:ts", 20 | "lint:ts": "eslint . --ext .js,.jsx,.ts,.tsx", 21 | "lint:sol": "solhint 'contracts/**/*.sol'", 22 | "prettier": "prettier -c .", 23 | "prettier:fix": "prettier -w .", 24 | "commit": "git-cz", 25 | "precommit": "lint-staged" 26 | }, 27 | "devDependencies": { 28 | "@commitlint/cli": "^12.1.4", 29 | "@commitlint/config-conventional": "^12.1.4", 30 | "@nomiclabs/hardhat-ethers": "^2.0.2", 31 | "@nomiclabs/hardhat-etherscan": "^2.1.5", 32 | "@nomiclabs/hardhat-waffle": "^2.0.1", 33 | "@semaphore-protocol/group": "2.0.0", 34 | "@semaphore-protocol/identity": "2.0.0", 35 | "@semaphore-protocol/proof": "2.2.0", 36 | "@typechain/ethers-v5": "^7.0.0", 37 | "@typechain/hardhat": "^2.0.1", 38 | "@types/chai": "^4.2.18", 39 | "@types/mocha": "^8.2.2", 40 | "@types/node": "^15.6.1", 41 | "@typescript-eslint/eslint-plugin": "^4.25.0", 42 | "@typescript-eslint/parser": "^4.25.0", 43 | "chai": "^4.3.4", 44 | "commitizen": "^4.2.4", 45 | "cz-conventional-changelog": "^3.3.0", 46 | "dotenv": "^10.0.0", 47 | "eslint": "^7.29.0", 48 | "eslint-config-prettier": "^8.3.0", 49 | "ethereum-waffle": "^3.3.0", 50 | "ethers": "^5.2.0", 51 | "hardhat": "^2.3.0", 52 | "hardhat-dependency-compiler": "^1.1.2", 53 | "hardhat-gas-reporter": "^1.0.4", 54 | "lint-staged": "^11.0.0", 55 | "mocha": "^8.4.0", 56 | "prettier": "^2.3.0", 57 | "prettier-plugin-solidity": "^1.0.0-beta.11", 58 | "solhint": "^3.3.6", 59 | "solhint-plugin-prettier": "^0.0.5", 60 | "solidity-coverage": "^0.7.17", 61 | "ts-node": "^10.0.0", 62 | "typechain": "^5.0.0", 63 | "typescript": "~4.2.4" 64 | }, 65 | "dependencies": { 66 | "@openzeppelin/contracts": "^4.5.0", 67 | "@semaphore-protocol/contracts": "2.0.0" 68 | }, 69 | "config": { 70 | "solidity": { 71 | "version": "0.8.4" 72 | }, 73 | "paths": { 74 | "contracts": "./contracts", 75 | "tests": "./test", 76 | "cache": "./cache", 77 | "build": { 78 | "contracts": "./build/contracts", 79 | "typechain": "./build/typechain" 80 | } 81 | }, 82 | "commitizen": { 83 | "path": "./node_modules/cz-conventional-changelog" 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { run } from "hardhat" 2 | 3 | async function main() { 4 | const { address: verifierAddress } = await run("deploy:verifier", { logs: false }) 5 | const { address: interepAddress } = await run("deploy:interep", { 6 | logs: false, 7 | verifiers: [{ merkleTreeDepth: 20, contractAddress: verifierAddress }] 8 | }) 9 | 10 | console.log(`Interep contract has been deployed to: ${interepAddress}`) 11 | } 12 | 13 | main() 14 | .then(() => process.exit(0)) 15 | .catch((error) => { 16 | console.error(error) 17 | process.exit(1) 18 | }) 19 | -------------------------------------------------------------------------------- /static/semaphore.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interep-project/contracts/d8ef68a48215bd7c5bc4490fe2ad3c0cf1d12bea/static/semaphore.wasm -------------------------------------------------------------------------------- /static/semaphore.zkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interep-project/contracts/d8ef68a48215bd7c5bc4490fe2ad3c0cf1d12bea/static/semaphore.zkey -------------------------------------------------------------------------------- /tasks/accounts.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "@ethersproject/abstract-signer" 2 | import { task, types } from "hardhat/config" 3 | 4 | task("accounts", "Prints the list of accounts") 5 | .addOptionalParam("logs", "Print the logs", true, types.boolean) 6 | .setAction(async ({ logs }, { ethers }) => { 7 | const accounts: Signer[] = await ethers.getSigners() 8 | 9 | if (logs) { 10 | for (const account of accounts) { 11 | console.log(await account.getAddress()) 12 | } 13 | } 14 | 15 | return accounts 16 | }) 17 | -------------------------------------------------------------------------------- /tasks/deploy-interep.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers" 2 | import { task, types } from "hardhat/config" 3 | 4 | task("deploy:interep", "Deploy an Interep contract") 5 | .addOptionalParam("logs", "Print the logs", true, types.boolean) 6 | .addParam("verifiers", "Tree depths and verifier addresses", undefined, types.json) 7 | .setAction(async ({ logs, verifiers }, { ethers }): Promise => { 8 | const ContractFactory = await ethers.getContractFactory("Interep") 9 | 10 | const contract = await ContractFactory.deploy(verifiers) 11 | 12 | await contract.deployed() 13 | 14 | logs && console.log(`Interep contract has been deployed to: ${contract.address}`) 15 | 16 | return contract 17 | }) 18 | -------------------------------------------------------------------------------- /tasks/deploy-verifier.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers" 2 | import { task, types } from "hardhat/config" 3 | 4 | task("deploy:verifier", "Deploy a Verifier20 contract") 5 | .addOptionalParam("logs", "Print the logs", true, types.boolean) 6 | .setAction(async ({ logs }, { ethers }): Promise => { 7 | const ContractFactory = await ethers.getContractFactory("Verifier20") 8 | 9 | const contract = await ContractFactory.deploy() 10 | 11 | await contract.deployed() 12 | 13 | logs && console.log(`Verifier20 contract has been deployed to: ${contract.address}`) 14 | 15 | return contract 16 | }) 17 | -------------------------------------------------------------------------------- /test/Interep.ts: -------------------------------------------------------------------------------- 1 | import { Group, Member } from "@semaphore-protocol/group" 2 | import { Identity } from "@semaphore-protocol/identity" 3 | import { FullProof, generateProof, packToSolidityProof, SolidityProof } from "@semaphore-protocol/proof" 4 | import { expect } from "chai" 5 | import { config as dotenvConfig } from "dotenv" 6 | import { utils } from "ethers" 7 | import { run } from "hardhat" 8 | import { resolve } from "path" 9 | import { Interep } from "../build/typechain/Interep" 10 | import { createGroupId, createIdentityCommitments } from "./utils" 11 | 12 | dotenvConfig({ path: resolve(__dirname, "../.env") }) 13 | 14 | describe("Interep", () => { 15 | let contract: Interep 16 | 17 | const groupProvider = utils.formatBytes32String("provider") 18 | const groupName = utils.formatBytes32String("name") 19 | const groupId = createGroupId(groupProvider, groupName) 20 | const group = new Group() 21 | const members = createIdentityCommitments(3) 22 | 23 | const wasmFilePath = "./static/semaphore.wasm" 24 | const zkeyFilePath = "./static/semaphore.zkey" 25 | 26 | group.addMembers(members) 27 | 28 | before(async () => { 29 | const { address: verifierAddress } = await run("deploy:verifier", { logs: false }) 30 | contract = await run("deploy:interep", { 31 | logs: false, 32 | verifiers: [{ merkleTreeDepth: group.depth, contractAddress: verifierAddress }] 33 | }) 34 | }) 35 | 36 | describe("# updateGroups", () => { 37 | it("Should not publish new Interep groups if there is an unsupported tree depth", async () => { 38 | const transaction = contract.updateGroups([ 39 | { provider: groupProvider, name: groupName, root: 1, depth: 10 } 40 | ]) 41 | 42 | await expect(transaction).to.be.revertedWith("Interep: tree depth is not supported") 43 | }) 44 | 45 | it("Should publish 20 new Interep groups", async () => { 46 | const groups: { provider: string; name: string; root: Member; depth: number }[] = [] 47 | 48 | for (let i = 0; i < 20; i++) { 49 | groups.push({ 50 | provider: groupProvider, 51 | name: groupName, 52 | root: group.root, 53 | depth: group.depth 54 | }) 55 | } 56 | 57 | const transaction = contract.updateGroups(groups) 58 | 59 | await expect(transaction) 60 | .to.emit(contract, "GroupUpdated") 61 | .withArgs(groupId, groups[0].provider, groups[0].name, groups[0].root, groups[0].depth) 62 | expect((await (await transaction).wait()).events).to.length(20) 63 | }) 64 | }) 65 | 66 | describe("# getRoot", () => { 67 | it("Should get the tree root of an Interep group", async () => { 68 | const root = await contract.getRoot(groupId) 69 | 70 | expect(root).to.equal("10984560832658664796615188769057321951156990771630419931317114687214058410144") 71 | }) 72 | }) 73 | 74 | describe("# getOffchainDepth", () => { 75 | it("Should get the tree depth of an Interep group", async () => { 76 | const root = await contract.getDepth(groupId) 77 | 78 | expect(root).to.equal(group.depth) 79 | }) 80 | }) 81 | 82 | describe("# verifyProof", () => { 83 | const signal = "Hello world" 84 | const bytes32Signal = utils.formatBytes32String(signal) 85 | const identity = new Identity("0") 86 | 87 | let fullProof: FullProof 88 | let solidityProof: SolidityProof 89 | 90 | before(async () => { 91 | fullProof = await generateProof(identity, group, group.root, signal, { zkeyFilePath, wasmFilePath }) 92 | solidityProof = packToSolidityProof(fullProof.proof) 93 | }) 94 | 95 | it("Should not verify a proof if the group does not exist", async () => { 96 | const transaction = contract.verifyProof(10, bytes32Signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]) 97 | 98 | await expect(transaction).to.be.revertedWith("Interep: group does not exist") 99 | }) 100 | 101 | it("Should throw an exception if the proof is not valid", async () => { 102 | const transaction = contract.verifyProof( 103 | groupId, 104 | bytes32Signal, 105 | fullProof.publicSignals.nullifierHash, 106 | 0, 107 | solidityProof 108 | ) 109 | 110 | await expect(transaction).to.be.revertedWith("InvalidProof()") 111 | }) 112 | 113 | it("Should verify a proof for an onchain group correctly", async () => { 114 | const transaction = contract.verifyProof( 115 | groupId, 116 | bytes32Signal, 117 | fullProof.publicSignals.nullifierHash, 118 | fullProof.publicSignals.merkleRoot, 119 | solidityProof 120 | ) 121 | 122 | await expect(transaction).to.emit(contract, "ProofVerified").withArgs(groupId, bytes32Signal) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { Identity } from "@semaphore-protocol/identity" 2 | import { utils } from "ethers" 3 | 4 | export const SNARK_SCALAR_FIELD = BigInt( 5 | "21888242871839275222246405745257275088548364400416034343698204186575808495617" 6 | ) 7 | 8 | export function createGroupId(provider: string, name: string): bigint { 9 | return BigInt(utils.solidityKeccak256(["bytes32", "bytes32"], [provider, name])) % SNARK_SCALAR_FIELD 10 | } 11 | 12 | export function createIdentityCommitments(n: number): bigint[] { 13 | const identityCommitments: bigint[] = [] 14 | 15 | for (let i = 0; i < n; i++) { 16 | const identity = new Identity(i.toString()) 17 | const identityCommitment = identity.generateCommitment() 18 | 19 | identityCommitments.push(identityCommitment) 20 | } 21 | 22 | return identityCommitments 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "noImplicitAny": true, 5 | "resolveJsonModule": true, 6 | "target": "ES2018", 7 | "module": "CommonJS", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "outDir": "dist" 11 | }, 12 | "include": ["tasks/**/*", "scripts/**/*", "test/**/*", "build/typechain/**/*"], 13 | "files": ["./hardhat.config.ts"] 14 | } 15 | --------------------------------------------------------------------------------