├── .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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
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 |
--------------------------------------------------------------------------------