diff --git a/.commitlintrc.json b/.commitlintrc.json index c30e5a9..f4fbb7d 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -1,3 +1,3 @@ { - "extends": ["@commitlint/config-conventional"] + "extends": ["@commitlint/config-conventional"] } diff --git a/.editorconfig b/.editorconfig index a8c26d5..c94bae0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 120 -indent_size = 2 +indent_size = 4 [*.md] trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json index c3c3824..ace239f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,11 @@ { - "env": { - "es6": true - }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.json" - }, - "plugins": ["@typescript-eslint"] + "env": { + "es6": true + }, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json" + }, + "plugins": ["@typescript-eslint"] } diff --git a/.github/ISSUE_TEMPLATE/----project.md b/.github/ISSUE_TEMPLATE/----project.md index ec8730c..0553ecf 100644 --- a/.github/ISSUE_TEMPLATE/----project.md +++ b/.github/ISSUE_TEMPLATE/----project.md @@ -1,20 +1,21 @@ --- name: " \U0001F4A0 Project" about: If you are using Semaphore we can help you share your project -title: '' +title: "" labels: "documentation \U0001F4D6" -assignees: '' - +assignees: "" --- **Describe your project** A brief description of your project. In what way have you used Semaphore? **Other info** -- Name -- Icon + +- Name +- Icon **Links** -- Website -- Github -- Socials + +- Website +- Github +- Socials diff --git a/.github/ISSUE_TEMPLATE/---bug.md b/.github/ISSUE_TEMPLATE/---bug.md index 593029c..7ff70b1 100644 --- a/.github/ISSUE_TEMPLATE/---bug.md +++ b/.github/ISSUE_TEMPLATE/---bug.md @@ -1,10 +1,9 @@ --- name: "\U0001F41E Bug" about: Create a report to help us improve -title: '' +title: "" labels: "bug \U0001F41B" -assignees: '' - +assignees: "" --- **Describe the bug** @@ -26,9 +25,9 @@ If applicable, add screenshots to help explain your problem. **Technologies (please complete the following information):** -- Node.js version -- NPM version -- Solidity version +- Node.js version +- NPM version +- Solidity version **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/---feature.md b/.github/ISSUE_TEMPLATE/---feature.md index 527a97f..b8546de 100644 --- a/.github/ISSUE_TEMPLATE/---feature.md +++ b/.github/ISSUE_TEMPLATE/---feature.md @@ -1,10 +1,9 @@ --- name: "\U0001F680 Feature" about: Suggest an idea for Semaphore -title: '' -labels: 'feature :rocket:' -assignees: '' - +title: "" +labels: "feature :rocket:" +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b3b0d63..504dd4a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,8 +14,8 @@ ## Does this introduce a breaking change? -- [ ] Yes -- [ ] No +- [ ] Yes +- [ ] No diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8b3b0ca..f8c0bee 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,25 +1,25 @@ name: coverage on: - push: - branches: - - main + push: + branches: + - main jobs: - coveralls: - runs-on: ubuntu-latest + coveralls: + runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + steps: + - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16.x + - uses: actions/setup-node@v1 + with: + node-version: 16.x - - run: yarn --frozen-lockfile - - run: yarn compile - - run: yarn test:coverage + - run: yarn --frozen-lockfile + - run: yarn compile + - run: yarn test:coverage - - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6265db2..b33ad88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,23 +1,23 @@ name: test on: - pull_request: - push: - branches: - - main + pull_request: + push: + branches: + - main jobs: - test_all: - runs-on: ubuntu-latest + test_all: + runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + steps: + - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16.x + - uses: actions/setup-node@v1 + with: + node-version: 16.x - - run: yarn --frozen-lockfile - - run: yarn download:zk-files - - run: yarn compile - - run: yarn test:prod + - run: yarn --frozen-lockfile + - run: yarn download:zk-files + - run: yarn compile + - run: yarn test:prod diff --git a/.lintstagedrc.json b/.lintstagedrc.json index acbb84b..9afe637 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,3 +1,3 @@ { - "**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"] + "**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"] } diff --git a/.prettierignore b/.prettierignore index a30bdd9..abd301f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -19,6 +19,9 @@ types # circuits circuits +# contracts +contracts/verifiers + # docusaurus .docusaurus diff --git a/.prettierrc.json b/.prettierrc.json index a6d13f8..27140f2 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,5 @@ { - "semi": false, - "arrowParens": "always", - "trailingComma": "none" + "semi": false, + "arrowParens": "always", + "trailingComma": "none" } diff --git a/.solhint.json b/.solhint.json index 472d0ad..5330a4e 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,21 +1,21 @@ { - "extends": "solhint:recommended", - "plugins": ["prettier"], - "rules": { - "code-complexity": ["error", 7], - "compiler-version": ["error", ">=0.8.0"], - "const-name-snakecase": "off", - "no-empty-blocks": "off", - "constructor-syntax": "error", - "func-visibility": ["error", { "ignoreConstructors": true }], - "max-line-length": ["error", 120], - "not-rely-on-time": "off", - "prettier/prettier": [ - "error", - { - "endOfLine": "auto" - } - ], - "reason-string": ["warn", { "maxLength": 80 }] - } + "extends": "solhint:recommended", + "plugins": ["prettier"], + "rules": { + "code-complexity": ["error", 7], + "compiler-version": ["error", ">=0.8.0"], + "const-name-snakecase": "off", + "no-empty-blocks": "off", + "constructor-syntax": "error", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "not-rely-on-time": "off", + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + } + ], + "reason-string": ["warn", { "maxLength": 80 }] + } } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8014579..0cb59ea 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,24 +17,24 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community Examples of unacceptable behavior include: -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Enforcement Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 653e66d..4d19b3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ We're really glad you're reading this, because we need volunteer developers to h ## Issues -The best way to contribute to our projects is by opening a [new issue](https://github.com/semaphore-protocol/semaphore/issues/new/choose) or tackling one of the issues listed [here](https://github.com/semaphore-protocol/semaphore/contribute). +The best way to contribute to our projects is by opening a [new issue](https://github.com/semaphore-protocol/semaphore/issues/new/choose) or tackling one of the issues listed [here](https://github.com/semaphore-protocol/semaphore/contribute). ## Pull Requests @@ -62,17 +62,17 @@ The **header** is mandatory and the **scope** of the header is optional. The type must be one of the following: -- feat: A new feature -- fix: A bug fix -- docs: Documentation only changes -- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) -- refactor: A code change that neither fixes a bug nor adds a feature (improvements of the code structure) -- perf: A code change that improves the performance -- test: Adding missing or correcting existing tests -- build: Changes that affect the build system or external dependencies (example scopes: gulp, npm) -- ci: Changes to CI configuration files and scripts (example scopes: travis, circle) -- chore: Other changes that don't modify src or test files -- revert: Reverts a previous commit +- feat: A new feature +- fix: A bug fix +- docs: Documentation only changes +- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) +- refactor: A code change that neither fixes a bug nor adds a feature (improvements of the code structure) +- perf: A code change that improves the performance +- test: Adding missing or correcting existing tests +- build: Changes that affect the build system or external dependencies (example scopes: gulp, npm) +- ci: Changes to CI configuration files and scripts (example scopes: travis, circle) +- chore: Other changes that don't modify src or test files +- revert: Reverts a previous commit #### Scope @@ -82,9 +82,9 @@ The scope should be the name of the npm package affected (as perceived by the pe The subject contains a succinct description of the change: -- Use the imperative, present tense: "change" not "changed" nor "changes" -- Don't capitalize the first letter -- No dot (.) at the end +- Use the imperative, present tense: "change" not "changed" nor "changes" +- Don't capitalize the first letter +- No dot (.) at the end #### Body @@ -92,14 +92,14 @@ Just as in the subject, use the imperative, present tense: "change" not "changed ### Branch rules -- There must be a `main` branch, used only for the releases. -- There must be a `dev` branch, used to merge all the branches under it. -- Avoid long descriptive names for long-lived branches. -- Use kebab-case (no CamelCase). -- Use grouping tokens (words) at the beginning of your branch names (in a similar way to the `type` of commit). -- Define and use short lead tokens to differentiate branches in a way that is meaningful to your workflow. -- Use slashes to separate parts of your branch names. -- Remove branch after merge if it is not important. +- There must be a `main` branch, used only for the releases. +- There must be a `dev` branch, used to merge all the branches under it. +- Avoid long descriptive names for long-lived branches. +- Use kebab-case (no CamelCase). +- Use grouping tokens (words) at the beginning of your branch names (in a similar way to the `type` of commit). +- Define and use short lead tokens to differentiate branches in a way that is meaningful to your workflow. +- Use slashes to separate parts of your branch names. +- Remove branch after merge if it is not important. Examples: diff --git a/README.md b/README.md index 656aadd..c878b0a 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ The core of the Semaphore protocol is in the [circuit logic](/circuits/scheme.pn ⚠️ Semaphore V2 has not yet been audited. Please do not use it in production. You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-protocol/semaphore/tree/version/1.0.0). -___ +--- ## Install diff --git a/contracts/Semaphore.sol b/contracts/Semaphore.sol index 78cd517..abaaef2 100644 --- a/contracts/Semaphore.sol +++ b/contracts/Semaphore.sol @@ -8,89 +8,89 @@ import "./base/SemaphoreGroups.sol"; /// @title Semaphore contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups { - /// @dev Gets a tree depth and returns its verifier address. - mapping(uint8 => IVerifier) public verifiers; + /// @dev Gets a tree depth and returns its verifier address. + mapping(uint8 => IVerifier) public verifiers; - /// @dev Gets a group id and returns the group admin address. - mapping(uint256 => address) public groupAdmins; + /// @dev Gets a group id and returns the group admin address. + mapping(uint256 => address) public groupAdmins; - /// @dev Checks if the group admin is the transaction sender. - /// @param groupId: Id of the group. - modifier onlyGroupAdmin(uint256 groupId) { - require(groupAdmins[groupId] == _msgSender(), "Semaphore: caller is not the group admin"); - _; - } - - /// @dev Checks if there is a verifier for the given tree depth. - /// @param depth: Depth of the tree. - modifier onlySupportedDepth(uint8 depth) { - require(address(verifiers[depth]) != address(0), "Semaphore: tree depth is not supported"); - _; - } - - /// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs. - /// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth). - constructor(Verifier[] memory _verifiers) { - for (uint8 i = 0; i < _verifiers.length; i++) { - verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress); + /// @dev Checks if the group admin is the transaction sender. + /// @param groupId: Id of the group. + modifier onlyGroupAdmin(uint256 groupId) { + require(groupAdmins[groupId] == _msgSender(), "Semaphore: caller is not the group admin"); + _; } - } - /// @dev See {ISemaphore-createGroup}. - function createGroup( - uint256 groupId, - uint8 depth, - uint256 zeroValue, - address admin - ) external override onlySupportedDepth(depth) { - _createGroup(groupId, depth, zeroValue); + /// @dev Checks if there is a verifier for the given tree depth. + /// @param depth: Depth of the tree. + modifier onlySupportedDepth(uint8 depth) { + require(address(verifiers[depth]) != address(0), "Semaphore: tree depth is not supported"); + _; + } - groupAdmins[groupId] = admin; + /// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs. + /// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth). + constructor(Verifier[] memory _verifiers) { + for (uint8 i = 0; i < _verifiers.length; i++) { + verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress); + } + } - emit GroupAdminUpdated(groupId, address(0), admin); - } + /// @dev See {ISemaphore-createGroup}. + function createGroup( + uint256 groupId, + uint8 depth, + uint256 zeroValue, + address admin + ) external override onlySupportedDepth(depth) { + _createGroup(groupId, depth, zeroValue); - /// @dev See {ISemaphore-updateGroupAdmin}. - function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) { - groupAdmins[groupId] = newAdmin; + groupAdmins[groupId] = admin; - emit GroupAdminUpdated(groupId, _msgSender(), newAdmin); - } + emit GroupAdminUpdated(groupId, address(0), admin); + } - /// @dev See {ISemaphore-addMember}. - function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) { - _addMember(groupId, identityCommitment); - } + /// @dev See {ISemaphore-updateGroupAdmin}. + function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) { + groupAdmins[groupId] = newAdmin; - /// @dev See {ISemaphore-removeMember}. - function removeMember( - uint256 groupId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external override onlyGroupAdmin(groupId) { - _removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices); - } + emit GroupAdminUpdated(groupId, _msgSender(), newAdmin); + } - /// @dev See {ISemaphore-verifyProof}. - function verifyProof( - uint256 groupId, - bytes32 signal, - uint256 nullifierHash, - uint256 externalNullifier, - uint256[8] calldata proof - ) external override { - uint256 root = getRoot(groupId); - uint8 depth = getDepth(groupId); + /// @dev See {ISemaphore-addMember}. + function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) { + _addMember(groupId, identityCommitment); + } - require(depth != 0, "Semaphore: group does not exist"); + /// @dev See {ISemaphore-removeMember}. + function removeMember( + uint256 groupId, + uint256 identityCommitment, + uint256[] calldata proofSiblings, + uint8[] calldata proofPathIndices + ) external override onlyGroupAdmin(groupId) { + _removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices); + } - IVerifier verifier = verifiers[depth]; + /// @dev See {ISemaphore-verifyProof}. + function verifyProof( + uint256 groupId, + bytes32 signal, + uint256 nullifierHash, + uint256 externalNullifier, + uint256[8] calldata proof + ) external override { + uint256 root = getRoot(groupId); + uint8 depth = getDepth(groupId); - _verifyProof(signal, root, nullifierHash, externalNullifier, proof, verifier); + require(depth != 0, "Semaphore: group does not exist"); - _saveNullifierHash(nullifierHash); + IVerifier verifier = verifiers[depth]; - emit ProofVerified(groupId, signal); - } + _verifyProof(signal, root, nullifierHash, externalNullifier, proof, verifier); + + _saveNullifierHash(nullifierHash); + + emit ProofVerified(groupId, signal); + } } diff --git a/contracts/base/SemaphoreCore.sol b/contracts/base/SemaphoreCore.sol index 8466955..47d1041 100644 --- a/contracts/base/SemaphoreCore.sol +++ b/contracts/base/SemaphoreCore.sol @@ -10,50 +10,50 @@ import "../interfaces/IVerifier.sol"; /// nullifier to prevent double-signaling. External nullifier and Merkle trees (i.e. groups) must be /// managed externally. contract SemaphoreCore is ISemaphoreCore { - /// @dev Gets a nullifier hash and returns true or false. - /// It is used to prevent double-signaling. - mapping(uint256 => bool) internal nullifierHashes; + /// @dev Gets a nullifier hash and returns true or false. + /// It is used to prevent double-signaling. + mapping(uint256 => bool) internal nullifierHashes; - /// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid. - /// Otherwise it reverts. - /// @param signal: Semaphore signal. - /// @param root: Root of the Merkle tree. - /// @param nullifierHash: Nullifier hash. - /// @param externalNullifier: External nullifier. - /// @param proof: Zero-knowledge proof. - /// @param verifier: Verifier address. - function _verifyProof( - bytes32 signal, - uint256 root, - uint256 nullifierHash, - uint256 externalNullifier, - uint256[8] calldata proof, - IVerifier verifier - ) internal view { - require(!nullifierHashes[nullifierHash], "SemaphoreCore: you cannot use the same nullifier twice"); + /// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid. + /// Otherwise it reverts. + /// @param signal: Semaphore signal. + /// @param root: Root of the Merkle tree. + /// @param nullifierHash: Nullifier hash. + /// @param externalNullifier: External nullifier. + /// @param proof: Zero-knowledge proof. + /// @param verifier: Verifier address. + function _verifyProof( + bytes32 signal, + uint256 root, + uint256 nullifierHash, + uint256 externalNullifier, + uint256[8] calldata proof, + IVerifier verifier + ) internal view { + require(!nullifierHashes[nullifierHash], "SemaphoreCore: you cannot use the same nullifier twice"); - uint256 signalHash = _hashSignal(signal); + uint256 signalHash = _hashSignal(signal); - verifier.verifyProof( - [proof[0], proof[1]], - [[proof[2], proof[3]], [proof[4], proof[5]]], - [proof[6], proof[7]], - [root, nullifierHash, signalHash, externalNullifier] - ); - } + verifier.verifyProof( + [proof[0], proof[1]], + [[proof[2], proof[3]], [proof[4], proof[5]]], + [proof[6], proof[7]], + [root, nullifierHash, signalHash, externalNullifier] + ); + } - /// @dev Stores the nullifier hash to prevent double-signaling. - /// Attention! Remember to call it when you verify a proof if you - /// need to prevent double-signaling. - /// @param nullifierHash: Semaphore nullifier hash. - function _saveNullifierHash(uint256 nullifierHash) internal { - nullifierHashes[nullifierHash] = true; - } + /// @dev Stores the nullifier hash to prevent double-signaling. + /// Attention! Remember to call it when you verify a proof if you + /// need to prevent double-signaling. + /// @param nullifierHash: Semaphore nullifier hash. + function _saveNullifierHash(uint256 nullifierHash) internal { + nullifierHashes[nullifierHash] = true; + } - /// @dev Creates a keccak256 hash of the signal. - /// @param signal: Semaphore signal. - /// @return Hash of the signal. - function _hashSignal(bytes32 signal) private pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(signal))) >> 8; - } + /// @dev Creates a keccak256 hash of the signal. + /// @param signal: Semaphore signal. + /// @return Hash of the signal. + function _hashSignal(bytes32 signal) private pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(signal))) >> 8; + } } diff --git a/contracts/base/SemaphoreGroups.sol b/contracts/base/SemaphoreGroups.sol index 89d28ca..38d25db 100644 --- a/contracts/base/SemaphoreGroups.sol +++ b/contracts/base/SemaphoreGroups.sol @@ -10,74 +10,74 @@ import "@openzeppelin/contracts/utils/Context.sol"; /// @dev The following code allows you to create groups, add and remove members. /// You can use getters to obtain informations about groups (root, depth, number of leaves). abstract contract SemaphoreGroups is Context, ISemaphoreGroups { - using IncrementalBinaryTree for IncrementalTreeData; + using IncrementalBinaryTree for IncrementalTreeData; - /// @dev Gets a group id and returns the group/tree data. - mapping(uint256 => IncrementalTreeData) internal groups; + /// @dev Gets a group id and returns the group/tree data. + mapping(uint256 => IncrementalTreeData) internal groups; - /// @dev Creates a new group by initializing the associated tree. - /// @param groupId: Id of the group. - /// @param depth: Depth of the tree. - /// @param zeroValue: Zero value of the tree. - function _createGroup( - uint256 groupId, - uint8 depth, - uint256 zeroValue - ) internal virtual { - require(groupId < SNARK_SCALAR_FIELD, "SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD"); - require(getDepth(groupId) == 0, "SemaphoreGroups: group already exists"); + /// @dev Creates a new group by initializing the associated tree. + /// @param groupId: Id of the group. + /// @param depth: Depth of the tree. + /// @param zeroValue: Zero value of the tree. + function _createGroup( + uint256 groupId, + uint8 depth, + uint256 zeroValue + ) internal virtual { + require(groupId < SNARK_SCALAR_FIELD, "SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD"); + require(getDepth(groupId) == 0, "SemaphoreGroups: group already exists"); - groups[groupId].init(depth, zeroValue); + groups[groupId].init(depth, zeroValue); - emit GroupCreated(groupId, depth, zeroValue); - } + emit GroupCreated(groupId, depth, zeroValue); + } - /// @dev Adds an identity commitment to an existing group. - /// @param groupId: Id of the group. - /// @param identityCommitment: New identity commitment. - function _addMember(uint256 groupId, uint256 identityCommitment) internal virtual { - require(getDepth(groupId) != 0, "SemaphoreGroups: group does not exist"); + /// @dev Adds an identity commitment to an existing group. + /// @param groupId: Id of the group. + /// @param identityCommitment: New identity commitment. + function _addMember(uint256 groupId, uint256 identityCommitment) internal virtual { + require(getDepth(groupId) != 0, "SemaphoreGroups: group does not exist"); - groups[groupId].insert(identityCommitment); + groups[groupId].insert(identityCommitment); - uint256 root = getRoot(groupId); + uint256 root = getRoot(groupId); - emit MemberAdded(groupId, identityCommitment, root); - } + emit MemberAdded(groupId, identityCommitment, root); + } - /// @dev Removes an identity commitment from an existing group. A proof of membership is - /// needed to check if the node to be deleted is part of the tree. - /// @param groupId: Id of the group. - /// @param identityCommitment: Existing identity commitment to be deleted. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function _removeMember( - uint256 groupId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal virtual { - require(getDepth(groupId) != 0, "SemaphoreGroups: group does not exist"); + /// @dev Removes an identity commitment from an existing group. A proof of membership is + /// needed to check if the node to be deleted is part of the tree. + /// @param groupId: Id of the group. + /// @param identityCommitment: Existing identity commitment to be deleted. + /// @param proofSiblings: Array of the sibling nodes of the proof of membership. + /// @param proofPathIndices: Path of the proof of membership. + function _removeMember( + uint256 groupId, + uint256 identityCommitment, + uint256[] calldata proofSiblings, + uint8[] calldata proofPathIndices + ) internal virtual { + require(getDepth(groupId) != 0, "SemaphoreGroups: group does not exist"); - groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices); + groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices); - uint256 root = getRoot(groupId); + uint256 root = getRoot(groupId); - emit MemberRemoved(groupId, identityCommitment, root); - } + emit MemberRemoved(groupId, identityCommitment, root); + } - /// @dev See {ISemaphoreGroups-getRoot}. - function getRoot(uint256 groupId) public view virtual override returns (uint256) { - return groups[groupId].root; - } + /// @dev See {ISemaphoreGroups-getRoot}. + function getRoot(uint256 groupId) public view virtual override returns (uint256) { + return groups[groupId].root; + } - /// @dev See {ISemaphoreGroups-getDepth}. - function getDepth(uint256 groupId) public view virtual override returns (uint8) { - return groups[groupId].depth; - } + /// @dev See {ISemaphoreGroups-getDepth}. + function getDepth(uint256 groupId) public view virtual override returns (uint8) { + return groups[groupId].depth; + } - /// @dev See {ISemaphoreGroups-getNumberOfLeaves}. - function getNumberOfLeaves(uint256 groupId) public view virtual override returns (uint256) { - return groups[groupId].numberOfLeaves; - } + /// @dev See {ISemaphoreGroups-getNumberOfLeaves}. + function getNumberOfLeaves(uint256 groupId) public view virtual override returns (uint256) { + return groups[groupId].numberOfLeaves; + } } diff --git a/contracts/extensions/SemaphoreVoting.sol b/contracts/extensions/SemaphoreVoting.sol index fc55a43..2540151 100644 --- a/contracts/extensions/SemaphoreVoting.sol +++ b/contracts/extensions/SemaphoreVoting.sol @@ -8,100 +8,100 @@ import "../base/SemaphoreGroups.sol"; /// @title Semaphore voting contract. /// @dev The following code allows you to create polls, add voters and allow them to vote anonymously. contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups { - /// @dev Gets a tree depth and returns its verifier address. - mapping(uint8 => IVerifier) internal verifiers; + /// @dev Gets a tree depth and returns its verifier address. + mapping(uint8 => IVerifier) internal verifiers; - /// @dev Gets a poll id and returns the poll data. - mapping(uint256 => Poll) internal polls; + /// @dev Gets a poll id and returns the poll data. + mapping(uint256 => Poll) internal polls; - /// @dev Since there can be multiple verifier contracts (each associated with a certain tree depth), - /// it is necessary to pass the addresses of the previously deployed contracts with the associated - /// tree depth. Depending on the depth chosen when creating the poll, a certain verifier will be - /// used to verify that the proof is correct. - /// @param depths: Three depths used in verifiers. - /// @param verifierAddresses: Verifier addresses. - constructor(uint8[] memory depths, address[] memory verifierAddresses) { - require( - depths.length == verifierAddresses.length, - "SemaphoreVoting: parameters lists does not have the same length" - ); + /// @dev Since there can be multiple verifier contracts (each associated with a certain tree depth), + /// it is necessary to pass the addresses of the previously deployed contracts with the associated + /// tree depth. Depending on the depth chosen when creating the poll, a certain verifier will be + /// used to verify that the proof is correct. + /// @param depths: Three depths used in verifiers. + /// @param verifierAddresses: Verifier addresses. + constructor(uint8[] memory depths, address[] memory verifierAddresses) { + require( + depths.length == verifierAddresses.length, + "SemaphoreVoting: parameters lists does not have the same length" + ); - for (uint8 i = 0; i < depths.length; i++) { - verifiers[depths[i]] = IVerifier(verifierAddresses[i]); + for (uint8 i = 0; i < depths.length; i++) { + verifiers[depths[i]] = IVerifier(verifierAddresses[i]); + } } - } - /// @dev Checks if the poll coordinator is the transaction sender. - /// @param pollId: Id of the poll. - modifier onlyCoordinator(uint256 pollId) { - require(polls[pollId].coordinator == _msgSender(), "SemaphoreVoting: caller is not the poll coordinator"); - _; - } + /// @dev Checks if the poll coordinator is the transaction sender. + /// @param pollId: Id of the poll. + modifier onlyCoordinator(uint256 pollId) { + require(polls[pollId].coordinator == _msgSender(), "SemaphoreVoting: caller is not the poll coordinator"); + _; + } - /// @dev See {ISemaphoreVoting-createPoll}. - function createPoll( - uint256 pollId, - address coordinator, - uint8 depth - ) public override { - require(address(verifiers[depth]) != address(0), "SemaphoreVoting: depth value is not supported"); + /// @dev See {ISemaphoreVoting-createPoll}. + function createPoll( + uint256 pollId, + address coordinator, + uint8 depth + ) public override { + require(address(verifiers[depth]) != address(0), "SemaphoreVoting: depth value is not supported"); - _createGroup(pollId, depth, 0); + _createGroup(pollId, depth, 0); - Poll memory poll; + Poll memory poll; - poll.coordinator = coordinator; + poll.coordinator = coordinator; - polls[pollId] = poll; + polls[pollId] = poll; - emit PollCreated(pollId, coordinator); - } + emit PollCreated(pollId, coordinator); + } - /// @dev See {ISemaphoreVoting-addVoter}. - function addVoter(uint256 pollId, uint256 identityCommitment) public override onlyCoordinator(pollId) { - require(polls[pollId].state == PollState.Created, "SemaphoreVoting: voters can only be added before voting"); + /// @dev See {ISemaphoreVoting-addVoter}. + function addVoter(uint256 pollId, uint256 identityCommitment) public override onlyCoordinator(pollId) { + require(polls[pollId].state == PollState.Created, "SemaphoreVoting: voters can only be added before voting"); - _addMember(pollId, identityCommitment); - } + _addMember(pollId, identityCommitment); + } - /// @dev See {ISemaphoreVoting-addVoter}. - function startPoll(uint256 pollId, uint256 encryptionKey) public override onlyCoordinator(pollId) { - require(polls[pollId].state == PollState.Created, "SemaphoreVoting: poll has already been started"); + /// @dev See {ISemaphoreVoting-addVoter}. + function startPoll(uint256 pollId, uint256 encryptionKey) public override onlyCoordinator(pollId) { + require(polls[pollId].state == PollState.Created, "SemaphoreVoting: poll has already been started"); - polls[pollId].state = PollState.Ongoing; + polls[pollId].state = PollState.Ongoing; - emit PollStarted(pollId, _msgSender(), encryptionKey); - } + emit PollStarted(pollId, _msgSender(), encryptionKey); + } - /// @dev See {ISemaphoreVoting-castVote}. - function castVote( - bytes32 vote, - uint256 nullifierHash, - uint256 pollId, - uint256[8] calldata proof - ) public override onlyCoordinator(pollId) { - Poll memory poll = polls[pollId]; + /// @dev See {ISemaphoreVoting-castVote}. + function castVote( + bytes32 vote, + uint256 nullifierHash, + uint256 pollId, + uint256[8] calldata proof + ) public override onlyCoordinator(pollId) { + Poll memory poll = polls[pollId]; - require(poll.state == PollState.Ongoing, "SemaphoreVoting: vote can only be cast in an ongoing poll"); + require(poll.state == PollState.Ongoing, "SemaphoreVoting: vote can only be cast in an ongoing poll"); - uint8 depth = getDepth(pollId); - uint256 root = getRoot(pollId); - IVerifier verifier = verifiers[depth]; + uint8 depth = getDepth(pollId); + uint256 root = getRoot(pollId); + IVerifier verifier = verifiers[depth]; - _verifyProof(vote, root, nullifierHash, pollId, proof, verifier); + _verifyProof(vote, root, nullifierHash, pollId, proof, verifier); - // Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)). - _saveNullifierHash(nullifierHash); + // Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)). + _saveNullifierHash(nullifierHash); - emit VoteAdded(pollId, vote); - } + emit VoteAdded(pollId, vote); + } - /// @dev See {ISemaphoreVoting-publishDecryptionKey}. - function endPoll(uint256 pollId, uint256 decryptionKey) public override onlyCoordinator(pollId) { - require(polls[pollId].state == PollState.Ongoing, "SemaphoreVoting: poll is not ongoing"); + /// @dev See {ISemaphoreVoting-publishDecryptionKey}. + function endPoll(uint256 pollId, uint256 decryptionKey) public override onlyCoordinator(pollId) { + require(polls[pollId].state == PollState.Ongoing, "SemaphoreVoting: poll is not ongoing"); - polls[pollId].state = PollState.Ended; + polls[pollId].state = PollState.Ended; - emit PollEnded(pollId, _msgSender(), decryptionKey); - } + emit PollEnded(pollId, _msgSender(), decryptionKey); + } } diff --git a/contracts/extensions/SemaphoreWhistleblowing.sol b/contracts/extensions/SemaphoreWhistleblowing.sol index 4564860..15557b5 100644 --- a/contracts/extensions/SemaphoreWhistleblowing.sol +++ b/contracts/extensions/SemaphoreWhistleblowing.sol @@ -10,79 +10,79 @@ import "../base/SemaphoreGroups.sol"; /// organization, newspaper) and to allow them to publish news leaks anonymously. /// Leaks can be IPFS hashes, permanent links or other kinds of reference. contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, SemaphoreGroups { - /// @dev Gets a tree depth and returns its verifier address. - mapping(uint8 => IVerifier) internal verifiers; + /// @dev Gets a tree depth and returns its verifier address. + mapping(uint8 => IVerifier) internal verifiers; - /// @dev Gets an editor address and return their entity. - mapping(address => uint256) private entities; + /// @dev Gets an editor address and return their entity. + mapping(address => uint256) private entities; - /// @dev Since there can be multiple verifier contracts (each associated with a certain tree depth), - /// it is necessary to pass the addresses of the previously deployed contracts with the associated - /// tree depth. Depending on the depth chosen when creating the entity, a certain verifier will be - /// used to verify that the proof is correct. - /// @param depths: Three depths used in verifiers. - /// @param verifierAddresses: Verifier addresses. - constructor(uint8[] memory depths, address[] memory verifierAddresses) { - require( - depths.length == verifierAddresses.length, - "SemaphoreWhistleblowing: parameters lists does not have the same length" - ); + /// @dev Since there can be multiple verifier contracts (each associated with a certain tree depth), + /// it is necessary to pass the addresses of the previously deployed contracts with the associated + /// tree depth. Depending on the depth chosen when creating the entity, a certain verifier will be + /// used to verify that the proof is correct. + /// @param depths: Three depths used in verifiers. + /// @param verifierAddresses: Verifier addresses. + constructor(uint8[] memory depths, address[] memory verifierAddresses) { + require( + depths.length == verifierAddresses.length, + "SemaphoreWhistleblowing: parameters lists does not have the same length" + ); - for (uint8 i = 0; i < depths.length; i++) { - verifiers[depths[i]] = IVerifier(verifierAddresses[i]); + for (uint8 i = 0; i < depths.length; i++) { + verifiers[depths[i]] = IVerifier(verifierAddresses[i]); + } } - } - /// @dev Checks if the editor is the transaction sender. - /// @param entityId: Id of the entity. - modifier onlyEditor(uint256 entityId) { - require(entityId == entities[_msgSender()], "SemaphoreWhistleblowing: caller is not the editor"); - _; - } + /// @dev Checks if the editor is the transaction sender. + /// @param entityId: Id of the entity. + modifier onlyEditor(uint256 entityId) { + require(entityId == entities[_msgSender()], "SemaphoreWhistleblowing: caller is not the editor"); + _; + } - /// @dev See {ISemaphoreWhistleblowing-createEntity}. - function createEntity( - uint256 entityId, - address editor, - uint8 depth - ) public override { - require(address(verifiers[depth]) != address(0), "SemaphoreWhistleblowing: depth value is not supported"); + /// @dev See {ISemaphoreWhistleblowing-createEntity}. + function createEntity( + uint256 entityId, + address editor, + uint8 depth + ) public override { + require(address(verifiers[depth]) != address(0), "SemaphoreWhistleblowing: depth value is not supported"); - _createGroup(entityId, depth, 0); + _createGroup(entityId, depth, 0); - entities[editor] = entityId; + entities[editor] = entityId; - emit EntityCreated(entityId, editor); - } + emit EntityCreated(entityId, editor); + } - /// @dev See {ISemaphoreWhistleblowing-addWhistleblower}. - function addWhistleblower(uint256 entityId, uint256 identityCommitment) public override onlyEditor(entityId) { - _addMember(entityId, identityCommitment); - } + /// @dev See {ISemaphoreWhistleblowing-addWhistleblower}. + function addWhistleblower(uint256 entityId, uint256 identityCommitment) public override onlyEditor(entityId) { + _addMember(entityId, identityCommitment); + } - /// @dev See {ISemaphoreWhistleblowing-removeWhistleblower}. - function removeWhistleblower( - uint256 entityId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) public override onlyEditor(entityId) { - _removeMember(entityId, identityCommitment, proofSiblings, proofPathIndices); - } + /// @dev See {ISemaphoreWhistleblowing-removeWhistleblower}. + function removeWhistleblower( + uint256 entityId, + uint256 identityCommitment, + uint256[] calldata proofSiblings, + uint8[] calldata proofPathIndices + ) public override onlyEditor(entityId) { + _removeMember(entityId, identityCommitment, proofSiblings, proofPathIndices); + } - /// @dev See {ISemaphoreWhistleblowing-publishLeak}. - function publishLeak( - bytes32 leak, - uint256 nullifierHash, - uint256 entityId, - uint256[8] calldata proof - ) public override onlyEditor(entityId) { - uint8 depth = getDepth(entityId); - uint256 root = getRoot(entityId); - IVerifier verifier = verifiers[depth]; + /// @dev See {ISemaphoreWhistleblowing-publishLeak}. + function publishLeak( + bytes32 leak, + uint256 nullifierHash, + uint256 entityId, + uint256[8] calldata proof + ) public override onlyEditor(entityId) { + uint8 depth = getDepth(entityId); + uint256 root = getRoot(entityId); + IVerifier verifier = verifiers[depth]; - _verifyProof(leak, root, nullifierHash, entityId, proof, verifier); + _verifyProof(leak, root, nullifierHash, entityId, proof, verifier); - emit LeakPublished(entityId, leak); - } + emit LeakPublished(entityId, leak); + } } diff --git a/contracts/interfaces/ISemaphore.sol b/contracts/interfaces/ISemaphore.sol index 9ff4676..4d2a08c 100644 --- a/contracts/interfaces/ISemaphore.sol +++ b/contracts/interfaces/ISemaphore.sol @@ -4,69 +4,69 @@ pragma solidity ^0.8.4; /// @title Semaphore interface. /// @dev Interface of a Semaphore contract. interface ISemaphore { - struct Verifier { - address contractAddress; - uint8 merkleTreeDepth; - } + struct Verifier { + address contractAddress; + uint8 merkleTreeDepth; + } - /// @dev Emitted when an admin is assigned to a group. - /// @param groupId: Id of the group. - /// @param oldAdmin: Old admin of the group. - /// @param newAdmin: New admin of the group. - event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin); + /// @dev Emitted when an admin is assigned to a group. + /// @param groupId: Id of the group. + /// @param oldAdmin: Old admin of the group. + /// @param newAdmin: New admin of the group. + event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin); - /// @dev Emitted when a Semaphore proof is verified. - /// @param groupId: Id of the group. - /// @param signal: Semaphore signal. - event ProofVerified(uint256 indexed groupId, bytes32 signal); + /// @dev Emitted when a Semaphore proof is verified. + /// @param groupId: Id of the group. + /// @param signal: Semaphore signal. + event ProofVerified(uint256 indexed groupId, bytes32 signal); - /// @dev Saves the nullifier hash to avoid double signaling and emits an event - /// if the zero-knowledge proof is valid. - /// @param groupId: Id of the group. - /// @param signal: Semaphore signal. - /// @param nullifierHash: Nullifier hash. - /// @param externalNullifier: External nullifier. - /// @param proof: Zero-knowledge proof. - function verifyProof( - uint256 groupId, - bytes32 signal, - uint256 nullifierHash, - uint256 externalNullifier, - uint256[8] calldata proof - ) external; + /// @dev Saves the nullifier hash to avoid double signaling and emits an event + /// if the zero-knowledge proof is valid. + /// @param groupId: Id of the group. + /// @param signal: Semaphore signal. + /// @param nullifierHash: Nullifier hash. + /// @param externalNullifier: External nullifier. + /// @param proof: Zero-knowledge proof. + function verifyProof( + uint256 groupId, + bytes32 signal, + uint256 nullifierHash, + uint256 externalNullifier, + uint256[8] calldata proof + ) external; - /// @dev Creates a new group. Only the admin will be able to add or remove members. - /// @param groupId: Id of the group. - /// @param depth: Depth of the tree. - /// @param zeroValue: Zero value of the tree. - /// @param admin: Admin of the group. - function createGroup( - uint256 groupId, - uint8 depth, - uint256 zeroValue, - address admin - ) external; + /// @dev Creates a new group. Only the admin will be able to add or remove members. + /// @param groupId: Id of the group. + /// @param depth: Depth of the tree. + /// @param zeroValue: Zero value of the tree. + /// @param admin: Admin of the group. + function createGroup( + uint256 groupId, + uint8 depth, + uint256 zeroValue, + address admin + ) external; - /// @dev Updates the group admin. - /// @param groupId: Id of the group. - /// @param newAdmin: New admin of the group. - function updateGroupAdmin(uint256 groupId, address newAdmin) external; + /// @dev Updates the group admin. + /// @param groupId: Id of the group. + /// @param newAdmin: New admin of the group. + function updateGroupAdmin(uint256 groupId, address newAdmin) external; - /// @dev Adds a new member to an existing group. - /// @param groupId: Id of the group. - /// @param identityCommitment: New identity commitment. - function addMember(uint256 groupId, uint256 identityCommitment) external; + /// @dev Adds a new member to an existing group. + /// @param groupId: Id of the group. + /// @param identityCommitment: New identity commitment. + function addMember(uint256 groupId, uint256 identityCommitment) external; - /// @dev Removes a member from an existing group. A proof of membership is - /// needed to check if the node to be removed is part of the tree. - /// @param groupId: Id of the group. - /// @param identityCommitment: Identity commitment to be deleted. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function removeMember( - uint256 groupId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external; + /// @dev Removes a member from an existing group. A proof of membership is + /// needed to check if the node to be removed is part of the tree. + /// @param groupId: Id of the group. + /// @param identityCommitment: Identity commitment to be deleted. + /// @param proofSiblings: Array of the sibling nodes of the proof of membership. + /// @param proofPathIndices: Path of the proof of membership. + function removeMember( + uint256 groupId, + uint256 identityCommitment, + uint256[] calldata proofSiblings, + uint8[] calldata proofPathIndices + ) external; } diff --git a/contracts/interfaces/ISemaphoreCore.sol b/contracts/interfaces/ISemaphoreCore.sol index ef86f4c..a3a1cda 100644 --- a/contracts/interfaces/ISemaphoreCore.sol +++ b/contracts/interfaces/ISemaphoreCore.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.4; /// @title SemaphoreCore interface. /// @dev Interface of SemaphoreCore contract. interface ISemaphoreCore { - /// @notice Emitted when a proof is verified correctly and a new nullifier hash is added. - /// @param nullifierHash: Hash of external and identity nullifiers. - event NullifierHashAdded(uint256 nullifierHash); + /// @notice Emitted when a proof is verified correctly and a new nullifier hash is added. + /// @param nullifierHash: Hash of external and identity nullifiers. + event NullifierHashAdded(uint256 nullifierHash); } diff --git a/contracts/interfaces/ISemaphoreGroups.sol b/contracts/interfaces/ISemaphoreGroups.sol index 1242fcd..1a0bf65 100644 --- a/contracts/interfaces/ISemaphoreGroups.sol +++ b/contracts/interfaces/ISemaphoreGroups.sol @@ -4,36 +4,36 @@ pragma solidity ^0.8.4; /// @title SemaphoreGroups interface. /// @dev Interface of a SemaphoreGroups contract. interface ISemaphoreGroups { - /// @dev Emitted when a new group is created. - /// @param groupId: Id of the group. - /// @param depth: Depth of the tree. - /// @param zeroValue: Zero value of the tree. - event GroupCreated(uint256 indexed groupId, uint8 depth, uint256 zeroValue); + /// @dev Emitted when a new group is created. + /// @param groupId: Id of the group. + /// @param depth: Depth of the tree. + /// @param zeroValue: Zero value of the tree. + event GroupCreated(uint256 indexed groupId, uint8 depth, uint256 zeroValue); - /// @dev Emitted when a new identity commitment is added. - /// @param groupId: Group id of the group. - /// @param identityCommitment: New identity commitment. - /// @param root: New root hash of the tree. - event MemberAdded(uint256 indexed groupId, uint256 identityCommitment, uint256 root); + /// @dev Emitted when a new identity commitment is added. + /// @param groupId: Group id of the group. + /// @param identityCommitment: New identity commitment. + /// @param root: New root hash of the tree. + event MemberAdded(uint256 indexed groupId, uint256 identityCommitment, uint256 root); - /// @dev Emitted when a new identity commitment is removed. - /// @param groupId: Group id of the group. - /// @param identityCommitment: New identity commitment. - /// @param root: New root hash of the tree. - event MemberRemoved(uint256 indexed groupId, uint256 identityCommitment, uint256 root); + /// @dev Emitted when a new identity commitment is removed. + /// @param groupId: Group id of the group. + /// @param identityCommitment: New identity commitment. + /// @param root: New root hash of the tree. + event MemberRemoved(uint256 indexed groupId, uint256 identityCommitment, uint256 root); - /// @dev Returns the last root hash of a group. - /// @param groupId: Id of the group. - /// @return Root hash of the group. - function getRoot(uint256 groupId) external view returns (uint256); + /// @dev Returns the last root hash of a group. + /// @param groupId: Id of the group. + /// @return Root hash of the group. + function getRoot(uint256 groupId) external view returns (uint256); - /// @dev Returns the depth of the tree of a group. - /// @param groupId: Id of the group. - /// @return Depth of the group tree. - function getDepth(uint256 groupId) external view returns (uint8); + /// @dev Returns the depth of the tree of a group. + /// @param groupId: Id of the group. + /// @return Depth of the group tree. + function getDepth(uint256 groupId) external view returns (uint8); - /// @dev Returns the number of tree leaves of a group. - /// @param groupId: Id of the group. - /// @return Number of tree leaves. - function getNumberOfLeaves(uint256 groupId) external view returns (uint256); + /// @dev Returns the number of tree leaves of a group. + /// @param groupId: Id of the group. + /// @return Number of tree leaves. + function getNumberOfLeaves(uint256 groupId) external view returns (uint256); } diff --git a/contracts/interfaces/ISemaphoreNullifiers.sol b/contracts/interfaces/ISemaphoreNullifiers.sol index 0687e4f..9373297 100644 --- a/contracts/interfaces/ISemaphoreNullifiers.sol +++ b/contracts/interfaces/ISemaphoreNullifiers.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.4; /// @title SemaphoreNullifiers interface. /// @dev Interface of SemaphoreNullifiers contract. interface ISemaphoreNullifiers { - /// @dev Emitted when a external nullifier is added. - /// @param externalNullifier: External Semaphore nullifier. - event ExternalNullifierAdded(uint256 externalNullifier); + /// @dev Emitted when a external nullifier is added. + /// @param externalNullifier: External Semaphore nullifier. + event ExternalNullifierAdded(uint256 externalNullifier); - /// @dev Emitted when a external nullifier is removed. - /// @param externalNullifier: External Semaphore nullifier. - event ExternalNullifierRemoved(uint256 externalNullifier); + /// @dev Emitted when a external nullifier is removed. + /// @param externalNullifier: External Semaphore nullifier. + event ExternalNullifierRemoved(uint256 externalNullifier); } diff --git a/contracts/interfaces/ISemaphoreVoting.sol b/contracts/interfaces/ISemaphoreVoting.sol index 64a39c3..7566830 100644 --- a/contracts/interfaces/ISemaphoreVoting.sol +++ b/contracts/interfaces/ISemaphoreVoting.sol @@ -4,73 +4,73 @@ pragma solidity ^0.8.4; /// @title SemaphoreVoting interface. /// @dev Interface of SemaphoreVoting contract. interface ISemaphoreVoting { - enum PollState { - Created, - Ongoing, - Ended - } + enum PollState { + Created, + Ongoing, + Ended + } - struct Poll { - address coordinator; - PollState state; - } + struct Poll { + address coordinator; + PollState state; + } - /// @dev Emitted when a new poll is created. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - event PollCreated(uint256 pollId, address indexed coordinator); + /// @dev Emitted when a new poll is created. + /// @param pollId: Id of the poll. + /// @param coordinator: Coordinator of the poll. + event PollCreated(uint256 pollId, address indexed coordinator); - /// @dev Emitted when a poll is started. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - /// @param encryptionKey: Key to encrypt the poll votes. - event PollStarted(uint256 pollId, address indexed coordinator, uint256 encryptionKey); + /// @dev Emitted when a poll is started. + /// @param pollId: Id of the poll. + /// @param coordinator: Coordinator of the poll. + /// @param encryptionKey: Key to encrypt the poll votes. + event PollStarted(uint256 pollId, address indexed coordinator, uint256 encryptionKey); - /// @dev Emitted when a user votes on a poll. - /// @param pollId: Id of the poll. - /// @param vote: User encrypted vote. - event VoteAdded(uint256 indexed pollId, bytes32 vote); + /// @dev Emitted when a user votes on a poll. + /// @param pollId: Id of the poll. + /// @param vote: User encrypted vote. + event VoteAdded(uint256 indexed pollId, bytes32 vote); - /// @dev Emitted when a poll is ended. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - /// @param decryptionKey: Key to decrypt the poll votes. - event PollEnded(uint256 pollId, address indexed coordinator, uint256 decryptionKey); + /// @dev Emitted when a poll is ended. + /// @param pollId: Id of the poll. + /// @param coordinator: Coordinator of the poll. + /// @param decryptionKey: Key to decrypt the poll votes. + event PollEnded(uint256 pollId, address indexed coordinator, uint256 decryptionKey); - /// @dev Creates a poll and the associated Merkle tree/group. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - /// @param depth: Depth of the tree. - function createPoll( - uint256 pollId, - address coordinator, - uint8 depth - ) external; + /// @dev Creates a poll and the associated Merkle tree/group. + /// @param pollId: Id of the poll. + /// @param coordinator: Coordinator of the poll. + /// @param depth: Depth of the tree. + function createPoll( + uint256 pollId, + address coordinator, + uint8 depth + ) external; - /// @dev Adds a voter to a poll. - /// @param pollId: Id of the poll. - /// @param identityCommitment: Identity commitment of the group member. - function addVoter(uint256 pollId, uint256 identityCommitment) external; + /// @dev Adds a voter to a poll. + /// @param pollId: Id of the poll. + /// @param identityCommitment: Identity commitment of the group member. + function addVoter(uint256 pollId, uint256 identityCommitment) external; - /// @dev Starts a pull and publishes the key to encrypt the votes. - /// @param pollId: Id of the poll. - /// @param encryptionKey: Key to encrypt poll votes. - function startPoll(uint256 pollId, uint256 encryptionKey) external; + /// @dev Starts a pull and publishes the key to encrypt the votes. + /// @param pollId: Id of the poll. + /// @param encryptionKey: Key to encrypt poll votes. + function startPoll(uint256 pollId, uint256 encryptionKey) external; - /// @dev Casts an anonymous vote in a poll. - /// @param vote: Encrypted vote. - /// @param nullifierHash: Nullifier hash. - /// @param pollId: Id of the poll. - /// @param proof: Private zk-proof parameters. - function castVote( - bytes32 vote, - uint256 nullifierHash, - uint256 pollId, - uint256[8] calldata proof - ) external; + /// @dev Casts an anonymous vote in a poll. + /// @param vote: Encrypted vote. + /// @param nullifierHash: Nullifier hash. + /// @param pollId: Id of the poll. + /// @param proof: Private zk-proof parameters. + function castVote( + bytes32 vote, + uint256 nullifierHash, + uint256 pollId, + uint256[8] calldata proof + ) external; - /// @dev Ends a pull and publishes the key to decrypt the votes. - /// @param pollId: Id of the poll. - /// @param decryptionKey: Key to decrypt poll votes. - function endPoll(uint256 pollId, uint256 decryptionKey) external; + /// @dev Ends a pull and publishes the key to decrypt the votes. + /// @param pollId: Id of the poll. + /// @param decryptionKey: Key to decrypt poll votes. + function endPoll(uint256 pollId, uint256 decryptionKey) external; } diff --git a/contracts/interfaces/ISemaphoreWhistleblowing.sol b/contracts/interfaces/ISemaphoreWhistleblowing.sol index 251183a..afd558e 100644 --- a/contracts/interfaces/ISemaphoreWhistleblowing.sol +++ b/contracts/interfaces/ISemaphoreWhistleblowing.sol @@ -4,52 +4,52 @@ pragma solidity ^0.8.4; /// @title SemaphoreWhistleblowing interface. /// @dev Interface of SemaphoreWhistleblowing contract. interface ISemaphoreWhistleblowing { - /// @dev Emitted when a new entity is created. - /// @param entityId: Id of the entity. - /// @param editor: Editor of the entity. - event EntityCreated(uint256 entityId, address indexed editor); + /// @dev Emitted when a new entity is created. + /// @param entityId: Id of the entity. + /// @param editor: Editor of the entity. + event EntityCreated(uint256 entityId, address indexed editor); - /// @dev Emitted when a whistleblower publish a new leak. - /// @param entityId: Id of the entity. - /// @param leak: News leak. - event LeakPublished(uint256 indexed entityId, bytes32 leak); + /// @dev Emitted when a whistleblower publish a new leak. + /// @param entityId: Id of the entity. + /// @param leak: News leak. + event LeakPublished(uint256 indexed entityId, bytes32 leak); - /// @dev Creates an entity and the associated Merkle tree/group. - /// @param entityId: Id of the entity. - /// @param editor: Editor of the entity. - /// @param depth: Depth of the tree. - function createEntity( - uint256 entityId, - address editor, - uint8 depth - ) external; + /// @dev Creates an entity and the associated Merkle tree/group. + /// @param entityId: Id of the entity. + /// @param editor: Editor of the entity. + /// @param depth: Depth of the tree. + function createEntity( + uint256 entityId, + address editor, + uint8 depth + ) external; - /// @dev Adds a whistleblower to an entity. - /// @param entityId: Id of the entity. - /// @param identityCommitment: Identity commitment of the group member. - function addWhistleblower(uint256 entityId, uint256 identityCommitment) external; + /// @dev Adds a whistleblower to an entity. + /// @param entityId: Id of the entity. + /// @param identityCommitment: Identity commitment of the group member. + function addWhistleblower(uint256 entityId, uint256 identityCommitment) external; - /// @dev Removes a whistleblower from an entity. - /// @param entityId: Id of the entity. - /// @param identityCommitment: Identity commitment of the group member. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function removeWhistleblower( - uint256 entityId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external; + /// @dev Removes a whistleblower from an entity. + /// @param entityId: Id of the entity. + /// @param identityCommitment: Identity commitment of the group member. + /// @param proofSiblings: Array of the sibling nodes of the proof of membership. + /// @param proofPathIndices: Path of the proof of membership. + function removeWhistleblower( + uint256 entityId, + uint256 identityCommitment, + uint256[] calldata proofSiblings, + uint8[] calldata proofPathIndices + ) external; - /// @dev Allows whistleblowers to publish leaks anonymously. - /// @param leak: News leak. - /// @param nullifierHash: Nullifier hash. - /// @param entityId: Id of the entity. - /// @param proof: Private zk-proof parameters. - function publishLeak( - bytes32 leak, - uint256 nullifierHash, - uint256 entityId, - uint256[8] calldata proof - ) external; + /// @dev Allows whistleblowers to publish leaks anonymously. + /// @param leak: News leak. + /// @param nullifierHash: Nullifier hash. + /// @param entityId: Id of the entity. + /// @param proof: Private zk-proof parameters. + function publishLeak( + bytes32 leak, + uint256 nullifierHash, + uint256 entityId, + uint256[8] calldata proof + ) external; } diff --git a/contracts/interfaces/IVerifier.sol b/contracts/interfaces/IVerifier.sol index 6ffd114..2f9fd6b 100644 --- a/contracts/interfaces/IVerifier.sol +++ b/contracts/interfaces/IVerifier.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.4; /// @title Verifier interface. /// @dev Interface of Verifier contract. interface IVerifier { - function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, - uint256[4] memory input - ) external view; + function verifyProof( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[4] memory input + ) external view; } diff --git a/contracts/package.json b/contracts/package.json index f009fd6..3ed2654 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,38 +1,38 @@ { - "name": "@semaphore-protocol/contracts", - "version": "0.1.0", - "description": "A privacy gadget for creating anonymous proof of membership on Ethereum.", - "license": "MIT", - "files": [ - "**/*.sol" - ], - "keywords": [ - "blockchain", - "ethereum", - "hardhat", - "smart-contracts", - "semaphore", - "identity", - "solidity", - "zero-knowledge", - "zk-snarks", - "zero-knowledge-proofs", - "circom", - "proof-of-membership" - ], - "homepage": "https://github.com/semaphore-protocol/semaphore.git#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/semaphore-protocol/semaphore.git.git" - }, - "bugs": { - "url": "https://github.com/semaphore-protocol/semaphore.git/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.4.2", - "@zk-kit/incremental-merkle-tree.sol": "^0.3.1" - } + "name": "@semaphore-protocol/contracts", + "version": "0.1.0", + "description": "A privacy gadget for creating anonymous proof of membership on Ethereum.", + "license": "MIT", + "files": [ + "**/*.sol" + ], + "keywords": [ + "blockchain", + "ethereum", + "hardhat", + "smart-contracts", + "semaphore", + "identity", + "solidity", + "zero-knowledge", + "zk-snarks", + "zero-knowledge-proofs", + "circom", + "proof-of-membership" + ], + "homepage": "https://github.com/semaphore-protocol/semaphore.git#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/semaphore-protocol/semaphore.git.git" + }, + "bugs": { + "url": "https://github.com/semaphore-protocol/semaphore.git/issues" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@openzeppelin/contracts": "^4.4.2", + "@zk-kit/incremental-merkle-tree.sol": "^0.3.1" + } } diff --git a/deployed-contracts/goerli.json b/deployed-contracts/goerli.json index 98726b4..7a53187 100644 --- a/deployed-contracts/goerli.json +++ b/deployed-contracts/goerli.json @@ -71,4 +71,4 @@ "name": "Semaphore", "address": "0x99aAb52e60f40AAC0BFE53e003De847bBDbC9611" } -] \ No newline at end of file +] diff --git a/deployed-contracts/kovan.json b/deployed-contracts/kovan.json index 7dab3ac..8bfb868 100644 --- a/deployed-contracts/kovan.json +++ b/deployed-contracts/kovan.json @@ -71,4 +71,4 @@ "name": "Semaphore", "address": "0x9e4080e133384d2D09b593C003DCaF3c5a0C53A6" } -] \ No newline at end of file +] diff --git a/hardhat.config.ts b/hardhat.config.ts index 535cc9d..0247a50 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -17,54 +17,54 @@ import "./tasks/deploy-verifier" dotenvConfig({ path: resolve(__dirname, "./.env") }) function getNetworks(): NetworksUserConfig | undefined { - if (process.env.INFURA_API_KEY && process.env.BACKEND_PRIVATE_KEY) { - const infuraApiKey = process.env.INFURA_API_KEY - const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`] + if (process.env.INFURA_API_KEY && process.env.BACKEND_PRIVATE_KEY) { + const infuraApiKey = process.env.INFURA_API_KEY + const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`] - return { - goerli: { - url: `https://goerli.infura.io/v3/${infuraApiKey}`, - chainId: 5, - accounts - }, - kovan: { - url: `https://kovan.infura.io/v3/${infuraApiKey}`, - chainId: 42, - accounts - }, - arbitrum: { - url: "https://arb1.arbitrum.io/rpc", - chainId: 42161, - accounts - } + return { + goerli: { + url: `https://goerli.infura.io/v3/${infuraApiKey}`, + chainId: 5, + accounts + }, + kovan: { + url: `https://kovan.infura.io/v3/${infuraApiKey}`, + chainId: 42, + accounts + }, + arbitrum: { + url: "https://arb1.arbitrum.io/rpc", + chainId: 42161, + accounts + } + } } - } } const hardhatConfig: HardhatUserConfig = { - solidity: config.solidity, - paths: { - sources: config.paths.contracts, - tests: config.paths.tests, - cache: config.paths.cache, - artifacts: config.paths.build.contracts - }, - networks: { - hardhat: { - chainId: 1337, - allowUnlimitedContractSize: true + solidity: config.solidity, + paths: { + sources: config.paths.contracts, + tests: config.paths.tests, + cache: config.paths.cache, + artifacts: config.paths.build.contracts }, - ...getNetworks() - }, - gasReporter: { - currency: "USD", - enabled: process.env.REPORT_GAS === "true", - coinmarketcap: process.env.COINMARKETCAP_API_KEY - }, - typechain: { - outDir: config.paths.build.typechain, - target: "ethers-v5" - } + networks: { + hardhat: { + chainId: 1337, + allowUnlimitedContractSize: true + }, + ...getNetworks() + }, + gasReporter: { + currency: "USD", + enabled: process.env.REPORT_GAS === "true", + coinmarketcap: process.env.COINMARKETCAP_API_KEY + }, + typechain: { + outDir: config.paths.build.typechain, + target: "ethers-v5" + } } export default hardhatConfig diff --git a/package.json b/package.json index 5eebbda..0bd3557 100644 --- a/package.json +++ b/package.json @@ -1,105 +1,105 @@ { - "name": "semaphore", - "version": "2.0.0", - "description": "A privacy gadget for creating anonymous proof of membership on Ethereum.", - "license": "MIT", - "homepage": "https://github.com/semaphore-protocol/semaphore.git#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/semaphore-protocol/semaphore.git.git" - }, - "bugs": { - "url": "https://github.com/semaphore-protocol/semaphore.git/issues" - }, - "private": true, - "scripts": { - "start": "hardhat node", - "compile": "hardhat compile", - "download:zk-files": "ts-node scripts/download-zk-files.ts", - "generate:verifiers": "ts-node scripts/generate-verifiers.ts", - "deploy:all": "hardhat run scripts/deploy-all.ts", - "deploy:verifier": "hardhat deploy:verifier", - "deploy:semaphore": "hardhat deploy:semaphore", - "deploy:semaphore-voting": "hardhat deploy:semaphore-voting", - "deploy:semaphore-whistleblowing": "hardhat deploy:semaphore-whistleblowing", - "test": "hardhat test", - "test:report-gas": "REPORT_GAS=true hardhat test", - "test:coverage": "hardhat coverage", - "test:prod": "yarn lint && yarn test", - "typechain": "hardhat typechain", - "lint": "yarn lint:sol && yarn lint:ts", - "lint:ts": "eslint . --ext .js,.jsx,.ts,.tsx", - "lint:sol": "solhint 'contracts/**/*.sol'", - "prettier": "prettier -c .", - "prettier:fix": "prettier -w .", - "commit": "cz", - "precommit": "lint-staged" - }, - "devDependencies": { - "@commitlint/cli": "^16.1.0", - "@commitlint/config-conventional": "^16.0.0", - "@nomiclabs/hardhat-ethers": "^2.0.4", - "@nomiclabs/hardhat-waffle": "^2.0.2", - "@typechain/ethers-v5": "^9.0.0", - "@typechain/hardhat": "^4.0.0", - "@types/chai": "^4.3.0", - "@types/download": "^8.0.1", - "@types/mocha": "^9.1.0", - "@types/node": "^17.0.12", - "@types/rimraf": "^3.0.2", - "@typescript-eslint/eslint-plugin": "^5.10.1", - "@typescript-eslint/parser": "^5.10.1", - "@zk-kit/identity": "^1.3.1", - "@zk-kit/incremental-merkle-tree": "^0.4.3", - "@zk-kit/protocols": "^1.11.0", - "chai": "^4.3.5", - "circomlib": "^2.0.2", - "circomlibjs": "^0.0.8", - "commitizen": "^4.2.4", - "cz-conventional-changelog": "^3.3.0", - "dotenv": "^14.3.2", - "download": "^8.0.0", - "eslint": "^8.7.0", - "eslint-config-prettier": "^8.3.0", - "ethereum-waffle": "^3.4.0", - "ethers": "^5.5.3", - "hardhat": "^2.8.3", - "hardhat-gas-reporter": "^1.0.7", - "js-logger": "^1.6.1", - "lint-staged": "^12.3.2", - "prettier": "^2.5.1", - "prettier-plugin-solidity": "^1.0.0-beta.19", - "rimraf": "^3.0.2", - "snarkjs": "^0.4.13", - "solhint": "^3.3.6", - "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.7.18", - "ts-node": "^10.4.0", - "typechain": "^7.0.0", - "typescript": "^4.5.5" - }, - "config": { - "solidity": { - "version": "0.8.4" + "name": "semaphore", + "version": "2.0.0", + "description": "A privacy gadget for creating anonymous proof of membership on Ethereum.", + "license": "MIT", + "homepage": "https://github.com/semaphore-protocol/semaphore.git#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/semaphore-protocol/semaphore.git.git" }, - "paths": { - "contracts": "./contracts", - "circuit": "./circuit", - "tests": "./test", - "cache": "./cache", - "snarkjs-templates": "./snarkjs-templates", - "build": { - "zk-files": "./build/zk-files", - "contracts": "./build/contracts", - "typechain": "./build/typechain" - } + "bugs": { + "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, - "commitizen": { - "path": "./node_modules/cz-conventional-changelog" + "private": true, + "scripts": { + "start": "hardhat node", + "compile": "hardhat compile", + "download:zk-files": "ts-node scripts/download-zk-files.ts", + "generate:verifiers": "ts-node scripts/generate-verifiers.ts", + "deploy:all": "hardhat run scripts/deploy-all.ts", + "deploy:verifier": "hardhat deploy:verifier", + "deploy:semaphore": "hardhat deploy:semaphore", + "deploy:semaphore-voting": "hardhat deploy:semaphore-voting", + "deploy:semaphore-whistleblowing": "hardhat deploy:semaphore-whistleblowing", + "test": "hardhat test", + "test:report-gas": "REPORT_GAS=true hardhat test", + "test:coverage": "hardhat coverage", + "test:prod": "yarn lint && yarn test", + "typechain": "hardhat typechain", + "lint": "yarn lint:sol && yarn lint:ts", + "lint:ts": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:sol": "solhint 'contracts/**/*.sol'", + "prettier": "prettier -c .", + "prettier:fix": "prettier -w .", + "commit": "cz", + "precommit": "lint-staged" + }, + "devDependencies": { + "@commitlint/cli": "^16.1.0", + "@commitlint/config-conventional": "^16.0.0", + "@nomiclabs/hardhat-ethers": "^2.0.4", + "@nomiclabs/hardhat-waffle": "^2.0.2", + "@typechain/ethers-v5": "^9.0.0", + "@typechain/hardhat": "^4.0.0", + "@types/chai": "^4.3.0", + "@types/download": "^8.0.1", + "@types/mocha": "^9.1.0", + "@types/node": "^17.0.12", + "@types/rimraf": "^3.0.2", + "@typescript-eslint/eslint-plugin": "^5.10.1", + "@typescript-eslint/parser": "^5.10.1", + "@zk-kit/identity": "^1.3.1", + "@zk-kit/incremental-merkle-tree": "^0.4.3", + "@zk-kit/protocols": "^1.11.0", + "chai": "^4.3.5", + "circomlib": "^2.0.2", + "circomlibjs": "^0.0.8", + "commitizen": "^4.2.4", + "cz-conventional-changelog": "^3.3.0", + "dotenv": "^14.3.2", + "download": "^8.0.0", + "eslint": "^8.7.0", + "eslint-config-prettier": "^8.3.0", + "ethereum-waffle": "^3.4.0", + "ethers": "^5.5.3", + "hardhat": "^2.8.3", + "hardhat-gas-reporter": "^1.0.7", + "js-logger": "^1.6.1", + "lint-staged": "^12.3.2", + "prettier": "^2.5.1", + "prettier-plugin-solidity": "^1.0.0-beta.19", + "rimraf": "^3.0.2", + "snarkjs": "^0.4.13", + "solhint": "^3.3.6", + "solhint-plugin-prettier": "^0.0.5", + "solidity-coverage": "^0.7.18", + "ts-node": "^10.4.0", + "typechain": "^7.0.0", + "typescript": "^4.5.5" + }, + "config": { + "solidity": { + "version": "0.8.4" + }, + "paths": { + "contracts": "./contracts", + "circuit": "./circuit", + "tests": "./test", + "cache": "./cache", + "snarkjs-templates": "./snarkjs-templates", + "build": { + "zk-files": "./build/zk-files", + "contracts": "./build/contracts", + "typechain": "./build/typechain" + } + }, + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, + "dependencies": { + "@openzeppelin/contracts": "^4.4.2", + "@zk-kit/incremental-merkle-tree.sol": "^0.3.1" } - }, - "dependencies": { - "@openzeppelin/contracts": "^4.4.2", - "@zk-kit/incremental-merkle-tree.sol": "^0.3.1" - } } diff --git a/scripts/deploy-all.ts b/scripts/deploy-all.ts index fbbaadb..b3ed68c 100644 --- a/scripts/deploy-all.ts +++ b/scripts/deploy-all.ts @@ -2,34 +2,37 @@ import fs from "fs" import { run, hardhatArguments } from "hardhat" async function main() { - const deployedContracts: { name: string; address: string }[] = [] + const deployedContracts: { name: string; address: string }[] = [] - // Deploy verifiers. - for (let treeDepth = 16; treeDepth <= 32; treeDepth++) { - const { address } = await run("deploy:verifier", { depth: treeDepth }) + // Deploy verifiers. + for (let treeDepth = 16; treeDepth <= 32; treeDepth++) { + const { address } = await run("deploy:verifier", { depth: treeDepth }) + + deployedContracts.push({ + name: `Verifier${treeDepth}`, + address + }) + } + + // Deploy Semaphore. + const { address } = await run("deploy:semaphore", { + verifiers: deployedContracts.map((c) => ({ merkleTreeDepth: c.name.substring(8), contractAddress: c.address })) + }) deployedContracts.push({ - name: `Verifier${treeDepth}`, - address + name: `Semaphore`, + address }) - } - // Deploy Semaphore. - const { address } = await run("deploy:semaphore", { - verifiers: deployedContracts.map((c) => ({ merkleTreeDepth: c.name.substring(8), contractAddress: c.address })) - }) - - deployedContracts.push({ - name: `Semaphore`, - address - }) - - fs.writeFileSync(`./deployed-contracts/${hardhatArguments.network}.json`, JSON.stringify(deployedContracts, null, 4)) + fs.writeFileSync( + `./deployed-contracts/${hardhatArguments.network}.json`, + JSON.stringify(deployedContracts, null, 4) + ) } main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/download-zk-files.ts b/scripts/download-zk-files.ts index 6177325..209390e 100644 --- a/scripts/download-zk-files.ts +++ b/scripts/download-zk-files.ts @@ -3,23 +3,23 @@ import fs from "fs" import { config } from "../package.json" async function main() { - const buildPath = config.paths.build["zk-files"] - const url = "http://www.trusted-setup-pse.org/semaphore/semaphore.zip" + const buildPath = config.paths.build["zk-files"] + const url = "http://www.trusted-setup-pse.org/semaphore/semaphore.zip" - if (!fs.existsSync(buildPath)) { - fs.mkdirSync(buildPath, { recursive: true }) - } + if (!fs.existsSync(buildPath)) { + fs.mkdirSync(buildPath, { recursive: true }) + } - if (!fs.existsSync(`${buildPath}/16/semaphore.zkey`)) { - await download(url, buildPath, { - extract: true - }) - } + if (!fs.existsSync(`${buildPath}/16/semaphore.zkey`)) { + await download(url, buildPath, { + extract: true + }) + } } main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/generate-verifiers.ts b/scripts/generate-verifiers.ts index 6561334..89e3bf6 100644 --- a/scripts/generate-verifiers.ts +++ b/scripts/generate-verifiers.ts @@ -6,29 +6,32 @@ import { config } from "../package.json" logger.useDefaults() async function main() { - const buildPath = config.paths.build["zk-files"] - const contractsPath = config.paths.contracts - const templatesPath = config.paths["snarkjs-templates"] - const solidityVersion = config.solidity.version + const buildPath = config.paths.build["zk-files"] + const contractsPath = config.paths.contracts + const templatesPath = config.paths["snarkjs-templates"] + const solidityVersion = config.solidity.version - if (fs.existsSync(`${buildPath}/16/semaphore.zkey`)) { - for (let treeDepth = 16; treeDepth <= 32; treeDepth++) { - let verifierCode = await zKey.exportSolidityVerifier( - `${buildPath}/${treeDepth}/semaphore.zkey`, - { groth16: fs.readFileSync(`${templatesPath}/verifier_groth16.sol.ejs`, "utf8") }, - logger - ) - verifierCode = verifierCode.replace(/pragma solidity \^\d+\.\d+\.\d+/, `pragma solidity ^${solidityVersion}`) - verifierCode = verifierCode.replace(/Verifier/, `Verifier${treeDepth}`) + if (fs.existsSync(`${buildPath}/16/semaphore.zkey`)) { + for (let treeDepth = 16; treeDepth <= 32; treeDepth++) { + let verifierCode = await zKey.exportSolidityVerifier( + `${buildPath}/${treeDepth}/semaphore.zkey`, + { groth16: fs.readFileSync(`${templatesPath}/verifier_groth16.sol.ejs`, "utf8") }, + logger + ) + verifierCode = verifierCode.replace( + /pragma solidity \^\d+\.\d+\.\d+/, + `pragma solidity ^${solidityVersion}` + ) + verifierCode = verifierCode.replace(/Verifier/, `Verifier${treeDepth}`) - fs.writeFileSync(`${contractsPath}/verifiers/Verifier${treeDepth}.sol`, verifierCode, "utf-8") + fs.writeFileSync(`${contractsPath}/verifiers/Verifier${treeDepth}.sol`, verifierCode, "utf-8") + } } - } } main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/tasks/accounts.ts b/tasks/accounts.ts index 164949b..332514b 100644 --- a/tasks/accounts.ts +++ b/tasks/accounts.ts @@ -2,15 +2,15 @@ import { Signer } from "@ethersproject/abstract-signer" import { task, types } from "hardhat/config" task("accounts", "Prints the list of accounts") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ logs }, { ethers }) => { - const accounts: Signer[] = await ethers.getSigners() + .addOptionalParam("logs", "Print the logs", true, types.boolean) + .setAction(async ({ logs }, { ethers }) => { + const accounts: Signer[] = await ethers.getSigners() - if (logs) { - for (const account of accounts) { - console.log(await account.getAddress()) - } - } + if (logs) { + for (const account of accounts) { + console.log(await account.getAddress()) + } + } - return accounts - }) + return accounts + }) diff --git a/tasks/deploy-semaphore-voting.ts b/tasks/deploy-semaphore-voting.ts index 2a2448c..e3f44c9 100644 --- a/tasks/deploy-semaphore-voting.ts +++ b/tasks/deploy-semaphore-voting.ts @@ -3,43 +3,43 @@ import { Contract } from "ethers" import { task, types } from "hardhat/config" task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .addParam("verifier", "Verifier contract address", undefined, types.string) - .setAction(async ({ logs, verifier }, { ethers }): Promise => { - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) + .addOptionalParam("logs", "Print the logs", true, types.boolean) + .addParam("verifier", "Verifier contract address", undefined, types.string) + .setAction(async ({ logs, verifier }, { ethers }): Promise => { + const poseidonABI = poseidonContract.generateABI(2) + const poseidonBytecode = poseidonContract.createCode(2) - const [signer] = await ethers.getSigners() + const [signer] = await ethers.getSigners() - const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidonLib = await PoseidonLibFactory.deploy() + const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) + const poseidonLib = await PoseidonLibFactory.deploy() - await poseidonLib.deployed() + await poseidonLib.deployed() - logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`) + logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`) - const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidonLib.address - } + const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", { + libraries: { + PoseidonT3: poseidonLib.address + } + }) + const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy() + + await incrementalBinaryTreeLib.deployed() + + logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`) + + const ContractFactory = await ethers.getContractFactory("SemaphoreVoting", { + libraries: { + IncrementalBinaryTree: incrementalBinaryTreeLib.address + } + }) + + const contract = await ContractFactory.deploy([20], [verifier]) + + await contract.deployed() + + logs && console.log(`SemaphoreVoting contract has been deployed to: ${contract.address}`) + + return contract }) - const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy() - - await incrementalBinaryTreeLib.deployed() - - logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`) - - const ContractFactory = await ethers.getContractFactory("SemaphoreVoting", { - libraries: { - IncrementalBinaryTree: incrementalBinaryTreeLib.address - } - }) - - const contract = await ContractFactory.deploy([20], [verifier]) - - await contract.deployed() - - logs && console.log(`SemaphoreVoting contract has been deployed to: ${contract.address}`) - - return contract - }) diff --git a/tasks/deploy-semaphore-whistleblowing.ts b/tasks/deploy-semaphore-whistleblowing.ts index 9bb66ab..5fff1b8 100644 --- a/tasks/deploy-semaphore-whistleblowing.ts +++ b/tasks/deploy-semaphore-whistleblowing.ts @@ -3,43 +3,43 @@ import { Contract } from "ethers" import { task, types } from "hardhat/config" task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contract") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .addParam("verifier", "Verifier contract address", undefined, types.string) - .setAction(async ({ logs, verifier }, { ethers }): Promise => { - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) + .addOptionalParam("logs", "Print the logs", true, types.boolean) + .addParam("verifier", "Verifier contract address", undefined, types.string) + .setAction(async ({ logs, verifier }, { ethers }): Promise => { + const poseidonABI = poseidonContract.generateABI(2) + const poseidonBytecode = poseidonContract.createCode(2) - const [signer] = await ethers.getSigners() + const [signer] = await ethers.getSigners() - const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidonLib = await PoseidonLibFactory.deploy() + const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) + const poseidonLib = await PoseidonLibFactory.deploy() - await poseidonLib.deployed() + await poseidonLib.deployed() - logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`) + logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`) - const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidonLib.address - } + const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", { + libraries: { + PoseidonT3: poseidonLib.address + } + }) + const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy() + + await incrementalBinaryTreeLib.deployed() + + logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`) + + const ContractFactory = await ethers.getContractFactory("SemaphoreWhistleblowing", { + libraries: { + IncrementalBinaryTree: incrementalBinaryTreeLib.address + } + }) + + const contract = await ContractFactory.deploy([20], [verifier]) + + await contract.deployed() + + logs && console.log(`SemaphoreWhistleblowing contract has been deployed to: ${contract.address}`) + + return contract }) - const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy() - - await incrementalBinaryTreeLib.deployed() - - logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`) - - const ContractFactory = await ethers.getContractFactory("SemaphoreWhistleblowing", { - libraries: { - IncrementalBinaryTree: incrementalBinaryTreeLib.address - } - }) - - const contract = await ContractFactory.deploy([20], [verifier]) - - await contract.deployed() - - logs && console.log(`SemaphoreWhistleblowing contract has been deployed to: ${contract.address}`) - - return contract - }) diff --git a/tasks/deploy-semaphore.ts b/tasks/deploy-semaphore.ts index bfa6cdf..d61b29d 100644 --- a/tasks/deploy-semaphore.ts +++ b/tasks/deploy-semaphore.ts @@ -3,43 +3,43 @@ import { Contract } from "ethers" import { task, types } from "hardhat/config" task("deploy:semaphore", "Deploy a Semaphore contract") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .addParam("verifiers", "Tree depths and verifier addresses", undefined, types.json) - .setAction(async ({ logs, verifiers }, { ethers }): Promise => { - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) + .addOptionalParam("logs", "Print the logs", true, types.boolean) + .addParam("verifiers", "Tree depths and verifier addresses", undefined, types.json) + .setAction(async ({ logs, verifiers }, { ethers }): Promise => { + const poseidonABI = poseidonContract.generateABI(2) + const poseidonBytecode = poseidonContract.createCode(2) - const [signer] = await ethers.getSigners() + const [signer] = await ethers.getSigners() - const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidonLib = await PoseidonLibFactory.deploy() + const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) + const poseidonLib = await PoseidonLibFactory.deploy() - await poseidonLib.deployed() + await poseidonLib.deployed() - logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`) + logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`) - const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidonLib.address - } + const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", { + libraries: { + PoseidonT3: poseidonLib.address + } + }) + const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy() + + await incrementalBinaryTreeLib.deployed() + + logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`) + + const ContractFactory = await ethers.getContractFactory("Semaphore", { + libraries: { + IncrementalBinaryTree: incrementalBinaryTreeLib.address + } + }) + + const contract = await ContractFactory.deploy(verifiers) + + await contract.deployed() + + logs && console.log(`Semaphore contract has been deployed to: ${contract.address}`) + + return contract }) - const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy() - - await incrementalBinaryTreeLib.deployed() - - logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`) - - const ContractFactory = await ethers.getContractFactory("Semaphore", { - libraries: { - IncrementalBinaryTree: incrementalBinaryTreeLib.address - } - }) - - const contract = await ContractFactory.deploy(verifiers) - - await contract.deployed() - - logs && console.log(`Semaphore contract has been deployed to: ${contract.address}`) - - return contract - }) diff --git a/tasks/deploy-verifier.ts b/tasks/deploy-verifier.ts index 2a2cc99..c6a88cc 100644 --- a/tasks/deploy-verifier.ts +++ b/tasks/deploy-verifier.ts @@ -2,16 +2,16 @@ import { Contract } from "ethers" import { task, types } from "hardhat/config" task("deploy:verifier", "Deploy a Verifier contract") - .addOptionalParam("depth", "Tree depth", 20, types.int) - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ depth, logs }, { ethers }): Promise => { - const ContractFactory = await ethers.getContractFactory(`Verifier${depth}`) + .addOptionalParam("depth", "Tree depth", 20, types.int) + .addOptionalParam("logs", "Print the logs", true, types.boolean) + .setAction(async ({ depth, logs }, { ethers }): Promise => { + const ContractFactory = await ethers.getContractFactory(`Verifier${depth}`) - const contract = await ContractFactory.deploy() + const contract = await ContractFactory.deploy() - await contract.deployed() + await contract.deployed() - logs && console.log(`Verifier${depth} contract has been deployed to: ${contract.address}`) + logs && console.log(`Verifier${depth} contract has been deployed to: ${contract.address}`) - return contract - }) + return contract + }) diff --git a/test/Semaphore.ts b/test/Semaphore.ts index 32f35cf..ba7b964 100644 --- a/test/Semaphore.ts +++ b/test/Semaphore.ts @@ -12,161 +12,165 @@ import { config } from "../package.json" dotenvConfig({ path: resolve(__dirname, "../.env") }) describe("Semaphore", () => { - let contract: SemaphoreContract - let signers: Signer[] - let accounts: string[] + let contract: SemaphoreContract + let signers: Signer[] + let accounts: string[] - const depth = 20 - const groupId = 1 - const members = createIdentityCommitments(3) + const depth = 20 + const groupId = 1 + const members = createIdentityCommitments(3) - const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm` - const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey` - - before(async () => { - const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth }) - contract = await run("deploy:semaphore", { - logs: false, - verifiers: [{ merkleTreeDepth: depth, contractAddress: verifierAddress }] - }) - - signers = await run("accounts", { logs: false }) - accounts = await Promise.all(signers.map((signer: Signer) => signer.getAddress())) - }) - - describe("# createGroup", () => { - it("Should not create a group if the tree depth is not supported", async () => { - const transaction = contract.createGroup(groupId, 10, 0, accounts[0]) - - await expect(transaction).to.be.revertedWith("Semaphore: tree depth is not supported") - }) - - it("Should create a group", async () => { - const transaction = contract.connect(signers[1]).createGroup(groupId, depth, 0, accounts[1]) - - await expect(transaction).to.emit(contract, "GroupCreated").withArgs(groupId, depth, 0) - await expect(transaction) - .to.emit(contract, "GroupAdminUpdated") - .withArgs(groupId, constants.AddressZero, accounts[1]) - }) - }) - - describe("# updateGroupAdmin", () => { - it("Should not update a group admin if the caller is not the group admin", async () => { - const transaction = contract.updateGroupAdmin(groupId, accounts[0]) - - await expect(transaction).to.be.revertedWith("Semaphore: caller is not the group admin") - }) - - it("Should update the group admin", async () => { - const transaction = contract.connect(signers[1]).updateGroupAdmin(groupId, accounts[0]) - - await expect(transaction).to.emit(contract, "GroupAdminUpdated").withArgs(groupId, accounts[1], accounts[0]) - }) - }) - - describe("# addMember", () => { - it("Should not add a member if the caller is not the group admin", async () => { - const member = BigInt(2) - - const transaction = contract.connect(signers[1]).addMember(groupId, member) - - await expect(transaction).to.be.revertedWith("Semaphore: caller is not the group admin") - }) - - it("Should add a new member in an existing group", async () => { - const transaction = contract.addMember(groupId, members[0]) - - await expect(transaction) - .to.emit(contract, "MemberAdded") - .withArgs(groupId, members[0], "18951329906296061785889394467312334959162736293275411745101070722914184798221") - }) - }) - - describe("# removeMember", () => { - it("Should not remove a member if the caller is not the group admin", async () => { - const transaction = contract.connect(signers[1]).removeMember(groupId, members[0], [0, 1], [0, 1]) - - await expect(transaction).to.be.revertedWith("Semaphore: caller is not the group admin") - }) - - it("Should remove a member from an existing group", async () => { - const groupId = 100 - const tree = createTree(depth, 3) - - tree.delete(0) - - await contract.createGroup(groupId, depth, 0, accounts[0]) - await contract.addMember(groupId, BigInt(1)) - await contract.addMember(groupId, BigInt(2)) - await contract.addMember(groupId, BigInt(3)) - - const { siblings, pathIndices, root } = tree.createProof(0) - - const transaction = contract.removeMember( - groupId, - BigInt(1), - siblings.map((s) => s[0]), - pathIndices - ) - - await expect(transaction).to.emit(contract, "MemberRemoved").withArgs(groupId, BigInt(1), root) - }) - }) - - describe("# verifyProof", () => { - const signal = "Hello world" - const bytes32Signal = utils.formatBytes32String(signal) - const identity = new ZkIdentity(Strategy.MESSAGE, "0") - const identityCommitment = identity.genIdentityCommitment() - const merkleProof = generateMerkleProof(depth, BigInt(0), members, identityCommitment) - const witness = Semaphore.genWitness( - identity.getTrapdoor(), - identity.getNullifier(), - merkleProof, - merkleProof.root, - signal - ) - - let fullProof: SemaphoreFullProof - let solidityProof: SemaphoreSolidityProof + const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm` + const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey` before(async () => { - await contract.addMember(groupId, members[1]) - await contract.addMember(groupId, members[2]) + const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth }) + contract = await run("deploy:semaphore", { + logs: false, + verifiers: [{ merkleTreeDepth: depth, contractAddress: verifierAddress }] + }) - fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath) - solidityProof = Semaphore.packToSolidityProof(fullProof.proof) + signers = await run("accounts", { logs: false }) + accounts = await Promise.all(signers.map((signer: Signer) => signer.getAddress())) }) - it("Should not verify a proof if the group does not exist", async () => { - const transaction = contract.verifyProof(10, bytes32Signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]) + describe("# createGroup", () => { + it("Should not create a group if the tree depth is not supported", async () => { + const transaction = contract.createGroup(groupId, 10, 0, accounts[0]) - await expect(transaction).to.be.revertedWith("Semaphore: group does not exist") + await expect(transaction).to.be.revertedWith("Semaphore: tree depth is not supported") + }) + + it("Should create a group", async () => { + const transaction = contract.connect(signers[1]).createGroup(groupId, depth, 0, accounts[1]) + + await expect(transaction).to.emit(contract, "GroupCreated").withArgs(groupId, depth, 0) + await expect(transaction) + .to.emit(contract, "GroupAdminUpdated") + .withArgs(groupId, constants.AddressZero, accounts[1]) + }) }) - it("Should throw an exception if the proof is not valid", async () => { - const transaction = contract.verifyProof( - groupId, - bytes32Signal, - fullProof.publicSignals.nullifierHash, - 0, - solidityProof - ) + describe("# updateGroupAdmin", () => { + it("Should not update a group admin if the caller is not the group admin", async () => { + const transaction = contract.updateGroupAdmin(groupId, accounts[0]) - await expect(transaction).to.be.revertedWith("InvalidProof()") + await expect(transaction).to.be.revertedWith("Semaphore: caller is not the group admin") + }) + + it("Should update the group admin", async () => { + const transaction = contract.connect(signers[1]).updateGroupAdmin(groupId, accounts[0]) + + await expect(transaction).to.emit(contract, "GroupAdminUpdated").withArgs(groupId, accounts[1], accounts[0]) + }) }) - it("Should verify a proof for an onchain group correctly", async () => { - const transaction = contract.verifyProof( - groupId, - bytes32Signal, - fullProof.publicSignals.nullifierHash, - fullProof.publicSignals.merkleRoot, - solidityProof - ) + describe("# addMember", () => { + it("Should not add a member if the caller is not the group admin", async () => { + const member = BigInt(2) - await expect(transaction).to.emit(contract, "ProofVerified").withArgs(groupId, bytes32Signal) + const transaction = contract.connect(signers[1]).addMember(groupId, member) + + await expect(transaction).to.be.revertedWith("Semaphore: caller is not the group admin") + }) + + it("Should add a new member in an existing group", async () => { + const transaction = contract.addMember(groupId, members[0]) + + await expect(transaction) + .to.emit(contract, "MemberAdded") + .withArgs( + groupId, + members[0], + "18951329906296061785889394467312334959162736293275411745101070722914184798221" + ) + }) + }) + + describe("# removeMember", () => { + it("Should not remove a member if the caller is not the group admin", async () => { + const transaction = contract.connect(signers[1]).removeMember(groupId, members[0], [0, 1], [0, 1]) + + await expect(transaction).to.be.revertedWith("Semaphore: caller is not the group admin") + }) + + it("Should remove a member from an existing group", async () => { + const groupId = 100 + const tree = createTree(depth, 3) + + tree.delete(0) + + await contract.createGroup(groupId, depth, 0, accounts[0]) + await contract.addMember(groupId, BigInt(1)) + await contract.addMember(groupId, BigInt(2)) + await contract.addMember(groupId, BigInt(3)) + + const { siblings, pathIndices, root } = tree.createProof(0) + + const transaction = contract.removeMember( + groupId, + BigInt(1), + siblings.map((s) => s[0]), + pathIndices + ) + + await expect(transaction).to.emit(contract, "MemberRemoved").withArgs(groupId, BigInt(1), root) + }) + }) + + describe("# verifyProof", () => { + const signal = "Hello world" + const bytes32Signal = utils.formatBytes32String(signal) + const identity = new ZkIdentity(Strategy.MESSAGE, "0") + const identityCommitment = identity.genIdentityCommitment() + const merkleProof = generateMerkleProof(depth, BigInt(0), members, identityCommitment) + const witness = Semaphore.genWitness( + identity.getTrapdoor(), + identity.getNullifier(), + merkleProof, + merkleProof.root, + signal + ) + + let fullProof: SemaphoreFullProof + let solidityProof: SemaphoreSolidityProof + + before(async () => { + await contract.addMember(groupId, members[1]) + await contract.addMember(groupId, members[2]) + + fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath) + solidityProof = Semaphore.packToSolidityProof(fullProof.proof) + }) + + it("Should not verify a proof if the group does not exist", async () => { + const transaction = contract.verifyProof(10, bytes32Signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]) + + await expect(transaction).to.be.revertedWith("Semaphore: group does not exist") + }) + + it("Should throw an exception if the proof is not valid", async () => { + const transaction = contract.verifyProof( + groupId, + bytes32Signal, + fullProof.publicSignals.nullifierHash, + 0, + solidityProof + ) + + await expect(transaction).to.be.revertedWith("InvalidProof()") + }) + + it("Should verify a proof for an onchain group correctly", async () => { + const transaction = contract.verifyProof( + groupId, + bytes32Signal, + fullProof.publicSignals.nullifierHash, + fullProof.publicSignals.merkleRoot, + solidityProof + ) + + await expect(transaction).to.emit(contract, "ProofVerified").withArgs(groupId, bytes32Signal) + }) }) - }) }) diff --git a/test/SemaphoreVoting.ts b/test/SemaphoreVoting.ts index b8de565..16d5045 100644 --- a/test/SemaphoreVoting.ts +++ b/test/SemaphoreVoting.ts @@ -8,199 +8,207 @@ import { createMerkleProof } from "./utils" import { config } from "../package.json" describe("SemaphoreVoting", () => { - let contract: SemaphoreVoting - let accounts: Signer[] - let coordinator: string + let contract: SemaphoreVoting + let accounts: Signer[] + let coordinator: string - const depth = 20 - const pollIds = [BigInt(1), BigInt(2), BigInt(3)] - const encryptionKey = BigInt(0) - const decryptionKey = BigInt(0) + const depth = 20 + const pollIds = [BigInt(1), BigInt(2), BigInt(3)] + const encryptionKey = BigInt(0) + const decryptionKey = BigInt(0) - const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm` - const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey` + const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm` + const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey` - before(async () => { - const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth }) - contract = await run("deploy:semaphore-voting", { logs: false, verifier: verifierAddress }) - accounts = await ethers.getSigners() - coordinator = await accounts[1].getAddress() - }) - - describe("# createPoll", () => { - it("Should not create a poll with a wrong depth", async () => { - const transaction = contract.createPoll(pollIds[0], coordinator, 10) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: depth value is not supported") - }) - - it("Should not create a poll greater than the snark scalar field", async () => { - const transaction = contract.createPoll( - BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"), - coordinator, - depth - ) - - await expect(transaction).to.be.revertedWith("SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD") - }) - - it("Should create a poll", async () => { - const transaction = contract.createPoll(pollIds[0], coordinator, depth) - - await expect(transaction).to.emit(contract, "PollCreated").withArgs(pollIds[0], coordinator) - }) - - it("Should not create a poll if it already exists", async () => { - const transaction = contract.createPoll(pollIds[0], coordinator, depth) - - await expect(transaction).to.be.revertedWith("SemaphoreGroups: group already exists") - }) - }) - - describe("# startPoll", () => { - it("Should not start the poll if the caller is not the coordinator", async () => { - const transaction = contract.startPoll(pollIds[0], encryptionKey) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") - }) - - it("Should start the poll", async () => { - const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey) - - await expect(transaction).to.emit(contract, "PollStarted").withArgs(pollIds[0], coordinator, encryptionKey) - }) - - it("Should not start a poll if it has already been started", async () => { - const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: poll has already been started") - }) - }) - - describe("# addVoter", () => { before(async () => { - await contract.createPoll(pollIds[1], coordinator, depth) + const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth }) + contract = await run("deploy:semaphore-voting", { logs: false, verifier: verifierAddress }) + accounts = await ethers.getSigners() + coordinator = await accounts[1].getAddress() }) - it("Should not add a voter if the caller is not the coordinator", async () => { - const identity = new ZkIdentity() - const identityCommitment = identity.genIdentityCommitment() + describe("# createPoll", () => { + it("Should not create a poll with a wrong depth", async () => { + const transaction = contract.createPoll(pollIds[0], coordinator, 10) - const transaction = contract.addVoter(pollIds[0], identityCommitment) + await expect(transaction).to.be.revertedWith("SemaphoreVoting: depth value is not supported") + }) - await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") + it("Should not create a poll greater than the snark scalar field", async () => { + const transaction = contract.createPoll( + BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"), + coordinator, + depth + ) + + await expect(transaction).to.be.revertedWith("SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD") + }) + + it("Should create a poll", async () => { + const transaction = contract.createPoll(pollIds[0], coordinator, depth) + + await expect(transaction).to.emit(contract, "PollCreated").withArgs(pollIds[0], coordinator) + }) + + it("Should not create a poll if it already exists", async () => { + const transaction = contract.createPoll(pollIds[0], coordinator, depth) + + await expect(transaction).to.be.revertedWith("SemaphoreGroups: group already exists") + }) }) - it("Should not add a voter if the poll has already been started", async () => { - const identity = new ZkIdentity() - const identityCommitment = identity.genIdentityCommitment() + describe("# startPoll", () => { + it("Should not start the poll if the caller is not the coordinator", async () => { + const transaction = contract.startPoll(pollIds[0], encryptionKey) - const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], identityCommitment) + await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") + }) - await expect(transaction).to.be.revertedWith("SemaphoreVoting: voters can only be added before voting") + it("Should start the poll", async () => { + const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey) + + await expect(transaction).to.emit(contract, "PollStarted").withArgs(pollIds[0], coordinator, encryptionKey) + }) + + it("Should not start a poll if it has already been started", async () => { + const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey) + + await expect(transaction).to.be.revertedWith("SemaphoreVoting: poll has already been started") + }) }) - it("Should add a voter to an existing poll", async () => { - const identity = new ZkIdentity(Strategy.MESSAGE, "test") - const identityCommitment = identity.genIdentityCommitment() + describe("# addVoter", () => { + before(async () => { + await contract.createPoll(pollIds[1], coordinator, depth) + }) - const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], identityCommitment) + it("Should not add a voter if the caller is not the coordinator", async () => { + const identity = new ZkIdentity() + const identityCommitment = identity.genIdentityCommitment() - await expect(transaction) - .to.emit(contract, "MemberAdded") - .withArgs( - pollIds[1], - identityCommitment, - "14787813191318312920980352979830075893203307366494541177071234930769373297362" + const transaction = contract.addVoter(pollIds[0], identityCommitment) + + await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") + }) + + it("Should not add a voter if the poll has already been started", async () => { + const identity = new ZkIdentity() + const identityCommitment = identity.genIdentityCommitment() + + const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], identityCommitment) + + await expect(transaction).to.be.revertedWith("SemaphoreVoting: voters can only be added before voting") + }) + + it("Should add a voter to an existing poll", async () => { + const identity = new ZkIdentity(Strategy.MESSAGE, "test") + const identityCommitment = identity.genIdentityCommitment() + + const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], identityCommitment) + + await expect(transaction) + .to.emit(contract, "MemberAdded") + .withArgs( + pollIds[1], + identityCommitment, + "14787813191318312920980352979830075893203307366494541177071234930769373297362" + ) + }) + + it("Should return the correct number of poll voters", async () => { + const size = await contract.getNumberOfLeaves(pollIds[1]) + + expect(size).to.be.eq(1) + }) + }) + + describe("# castVote", () => { + const identity = new ZkIdentity(Strategy.MESSAGE, "test") + const identityCommitment = identity.genIdentityCommitment() + const merkleProof = createMerkleProof([identityCommitment, BigInt(1)], identityCommitment) + const vote = "1" + const bytes32Vote = utils.formatBytes32String(vote) + + const witness = Semaphore.genWitness( + identity.getTrapdoor(), + identity.getNullifier(), + merkleProof, + pollIds[1], + vote ) + + let solidityProof: SemaphoreSolidityProof + let publicSignals: SemaphorePublicSignals + + before(async () => { + await contract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1)) + await contract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey) + await contract.createPoll(pollIds[2], coordinator, depth) + + const fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath) + + publicSignals = fullProof.publicSignals + solidityProof = Semaphore.packToSolidityProof(fullProof.proof) + }) + + it("Should not cast a vote if the caller is not the coordinator", async () => { + const transaction = contract.castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[0], solidityProof) + + await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") + }) + + it("Should not cast a vote if the poll is not ongoing", async () => { + const transaction = contract + .connect(accounts[1]) + .castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[2], solidityProof) + + await expect(transaction).to.be.revertedWith("SemaphoreVoting: vote can only be cast in an ongoing poll") + }) + + it("Should not cast a vote if the proof is not valid", async () => { + const nullifierHash = Semaphore.genNullifierHash(pollIds[0], identity.getNullifier()) + + const transaction = contract + .connect(accounts[1]) + .castVote(bytes32Vote, nullifierHash, pollIds[1], solidityProof) + + await expect(transaction).to.be.revertedWith("InvalidProof()") + }) + + it("Should cast a vote", async () => { + const transaction = contract + .connect(accounts[1]) + .castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[1], solidityProof) + + await expect(transaction).to.emit(contract, "VoteAdded").withArgs(pollIds[1], bytes32Vote) + }) + + it("Should not cast a vote twice", async () => { + const transaction = contract + .connect(accounts[1]) + .castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[1], solidityProof) + + await expect(transaction).to.be.revertedWith("SemaphoreCore: you cannot use the same nullifier twice") + }) }) - it("Should return the correct number of poll voters", async () => { - const size = await contract.getNumberOfLeaves(pollIds[1]) + describe("# endPoll", () => { + it("Should not end the poll if the caller is not the coordinator", async () => { + const transaction = contract.endPoll(pollIds[1], decryptionKey) - expect(size).to.be.eq(1) + await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") + }) + + it("Should end the poll", async () => { + const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey) + + await expect(transaction).to.emit(contract, "PollEnded").withArgs(pollIds[1], coordinator, decryptionKey) + }) + + it("Should not end a poll if it has already been ended", async () => { + const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey) + + await expect(transaction).to.be.revertedWith("SemaphoreVoting: poll is not ongoing") + }) }) - }) - - describe("# castVote", () => { - const identity = new ZkIdentity(Strategy.MESSAGE, "test") - const identityCommitment = identity.genIdentityCommitment() - const merkleProof = createMerkleProof([identityCommitment, BigInt(1)], identityCommitment) - const vote = "1" - const bytes32Vote = utils.formatBytes32String(vote) - - const witness = Semaphore.genWitness(identity.getTrapdoor(), identity.getNullifier(), merkleProof, pollIds[1], vote) - - let solidityProof: SemaphoreSolidityProof - let publicSignals: SemaphorePublicSignals - - before(async () => { - await contract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1)) - await contract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey) - await contract.createPoll(pollIds[2], coordinator, depth) - - const fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath) - - publicSignals = fullProof.publicSignals - solidityProof = Semaphore.packToSolidityProof(fullProof.proof) - }) - - it("Should not cast a vote if the caller is not the coordinator", async () => { - const transaction = contract.castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[0], solidityProof) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") - }) - - it("Should not cast a vote if the poll is not ongoing", async () => { - const transaction = contract - .connect(accounts[1]) - .castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[2], solidityProof) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: vote can only be cast in an ongoing poll") - }) - - it("Should not cast a vote if the proof is not valid", async () => { - const nullifierHash = Semaphore.genNullifierHash(pollIds[0], identity.getNullifier()) - - const transaction = contract.connect(accounts[1]).castVote(bytes32Vote, nullifierHash, pollIds[1], solidityProof) - - await expect(transaction).to.be.revertedWith("InvalidProof()") - }) - - it("Should cast a vote", async () => { - const transaction = contract - .connect(accounts[1]) - .castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[1], solidityProof) - - await expect(transaction).to.emit(contract, "VoteAdded").withArgs(pollIds[1], bytes32Vote) - }) - - it("Should not cast a vote twice", async () => { - const transaction = contract - .connect(accounts[1]) - .castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[1], solidityProof) - - await expect(transaction).to.be.revertedWith("SemaphoreCore: you cannot use the same nullifier twice") - }) - }) - - describe("# endPoll", () => { - it("Should not end the poll if the caller is not the coordinator", async () => { - const transaction = contract.endPoll(pollIds[1], decryptionKey) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator") - }) - - it("Should end the poll", async () => { - const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey) - - await expect(transaction).to.emit(contract, "PollEnded").withArgs(pollIds[1], coordinator, decryptionKey) - }) - - it("Should not end a poll if it has already been ended", async () => { - const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey) - - await expect(transaction).to.be.revertedWith("SemaphoreVoting: poll is not ongoing") - }) - }) }) diff --git a/test/SemaphoreWhistleblowing.ts b/test/SemaphoreWhistleblowing.ts index cad67ce..1538bdd 100644 --- a/test/SemaphoreWhistleblowing.ts +++ b/test/SemaphoreWhistleblowing.ts @@ -8,166 +8,171 @@ import { createMerkleProof } from "./utils" import { config } from "../package.json" describe("SemaphoreWhistleblowing", () => { - let contract: SemaphoreWhistleblowing - let accounts: Signer[] - let editor: string + let contract: SemaphoreWhistleblowing + let accounts: Signer[] + let editor: string - const depth = 20 - const entityIds = [BigInt(1), BigInt(2)] + const depth = 20 + const entityIds = [BigInt(1), BigInt(2)] - const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm` - const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey` - - before(async () => { - const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth }) - contract = await run("deploy:semaphore-whistleblowing", { logs: false, verifier: verifierAddress }) - accounts = await ethers.getSigners() - editor = await accounts[1].getAddress() - }) - - describe("# createEntity", () => { - it("Should not create an entity with a wrong depth", async () => { - const transaction = contract.createEntity(entityIds[0], editor, 10) - - await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: depth value is not supported") - }) - - it("Should not create an entity greater than the snark scalar field", async () => { - const transaction = contract.createEntity( - BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"), - editor, - depth - ) - - await expect(transaction).to.be.revertedWith("SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD") - }) - - it("Should create an entity", async () => { - const transaction = contract.createEntity(entityIds[0], editor, depth) - - await expect(transaction).to.emit(contract, "EntityCreated").withArgs(entityIds[0], editor) - }) - - it("Should not create a entity if it already exists", async () => { - const transaction = contract.createEntity(entityIds[0], editor, depth) - - await expect(transaction).to.be.revertedWith("SemaphoreGroups: group already exists") - }) - }) - - describe("# addWhistleblower", () => { - it("Should not add a whistleblower if the caller is not the editor", async () => { - const identity = new ZkIdentity() - const identityCommitment = identity.genIdentityCommitment() - - const transaction = contract.addWhistleblower(entityIds[0], identityCommitment) - - await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor") - }) - - it("Should add a whistleblower to an existing entity", async () => { - const identity = new ZkIdentity(Strategy.MESSAGE, "test") - const identityCommitment = identity.genIdentityCommitment() - - const transaction = contract.connect(accounts[1]).addWhistleblower(entityIds[0], identityCommitment) - - await expect(transaction) - .to.emit(contract, "MemberAdded") - .withArgs( - entityIds[0], - identityCommitment, - "14787813191318312920980352979830075893203307366494541177071234930769373297362" - ) - }) - - it("Should return the correct number of whistleblowers of an entity", async () => { - const size = await contract.getNumberOfLeaves(entityIds[0]) - - expect(size).to.be.eq(1) - }) - }) - - describe("# removeWhistleblower", () => { - it("Should not remove a whistleblower if the caller is not the editor", async () => { - const identity = new ZkIdentity() - const identityCommitment = identity.genIdentityCommitment() - const { siblings, pathIndices } = createMerkleProof([identityCommitment], identityCommitment) - - const transaction = contract.removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices) - - await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor") - }) - - it("Should remove a whistleblower from an existing entity", async () => { - const identity = new ZkIdentity(Strategy.MESSAGE, "test") - const identityCommitment = identity.genIdentityCommitment() - const { siblings, pathIndices } = createMerkleProof([identityCommitment], identityCommitment) - - const transaction = contract - .connect(accounts[1]) - .removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices) - - await expect(transaction) - .to.emit(contract, "MemberRemoved") - .withArgs( - entityIds[0], - identityCommitment, - "15019797232609675441998260052101280400536945603062888308240081994073687793470" - ) - }) - }) - - describe("# publishLeak", () => { - const identity = new ZkIdentity(Strategy.MESSAGE, "test") - const identityCommitment = identity.genIdentityCommitment() - const merkleProof = createMerkleProof([identityCommitment, BigInt(1)], identityCommitment) - const leak = "leak" - const bytes32Leak = utils.formatBytes32String(leak) - - const witness = Semaphore.genWitness( - identity.getTrapdoor(), - identity.getNullifier(), - merkleProof, - entityIds[1], - leak - ) - - let solidityProof: SemaphoreSolidityProof - let publicSignals: SemaphorePublicSignals + const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm` + const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey` before(async () => { - await contract.createEntity(entityIds[1], editor, depth) - await contract.connect(accounts[1]).addWhistleblower(entityIds[1], identityCommitment) - await contract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1)) - - const fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath) - - publicSignals = fullProof.publicSignals - solidityProof = Semaphore.packToSolidityProof(fullProof.proof) + const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth }) + contract = await run("deploy:semaphore-whistleblowing", { logs: false, verifier: verifierAddress }) + accounts = await ethers.getSigners() + editor = await accounts[1].getAddress() }) - it("Should not publish a leak if the caller is not the editor", async () => { - const transaction = contract.publishLeak(bytes32Leak, publicSignals.nullifierHash, entityIds[0], solidityProof) + describe("# createEntity", () => { + it("Should not create an entity with a wrong depth", async () => { + const transaction = contract.createEntity(entityIds[0], editor, 10) - await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor") + await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: depth value is not supported") + }) + + it("Should not create an entity greater than the snark scalar field", async () => { + const transaction = contract.createEntity( + BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"), + editor, + depth + ) + + await expect(transaction).to.be.revertedWith("SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD") + }) + + it("Should create an entity", async () => { + const transaction = contract.createEntity(entityIds[0], editor, depth) + + await expect(transaction).to.emit(contract, "EntityCreated").withArgs(entityIds[0], editor) + }) + + it("Should not create a entity if it already exists", async () => { + const transaction = contract.createEntity(entityIds[0], editor, depth) + + await expect(transaction).to.be.revertedWith("SemaphoreGroups: group already exists") + }) }) - it("Should not publish a leak if the proof is not valid", async () => { - const nullifierHash = Semaphore.genNullifierHash(entityIds[0], identity.getNullifier()) + describe("# addWhistleblower", () => { + it("Should not add a whistleblower if the caller is not the editor", async () => { + const identity = new ZkIdentity() + const identityCommitment = identity.genIdentityCommitment() - const transaction = contract - .connect(accounts[1]) - .publishLeak(bytes32Leak, nullifierHash, entityIds[1], solidityProof) + const transaction = contract.addWhistleblower(entityIds[0], identityCommitment) - await expect(transaction).to.be.revertedWith("InvalidProof()") + await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor") + }) + + it("Should add a whistleblower to an existing entity", async () => { + const identity = new ZkIdentity(Strategy.MESSAGE, "test") + const identityCommitment = identity.genIdentityCommitment() + + const transaction = contract.connect(accounts[1]).addWhistleblower(entityIds[0], identityCommitment) + + await expect(transaction) + .to.emit(contract, "MemberAdded") + .withArgs( + entityIds[0], + identityCommitment, + "14787813191318312920980352979830075893203307366494541177071234930769373297362" + ) + }) + + it("Should return the correct number of whistleblowers of an entity", async () => { + const size = await contract.getNumberOfLeaves(entityIds[0]) + + expect(size).to.be.eq(1) + }) }) - it("Should publish a leak", async () => { - const transaction = contract - .connect(accounts[1]) - .publishLeak(bytes32Leak, publicSignals.nullifierHash, entityIds[1], solidityProof) + describe("# removeWhistleblower", () => { + it("Should not remove a whistleblower if the caller is not the editor", async () => { + const identity = new ZkIdentity() + const identityCommitment = identity.genIdentityCommitment() + const { siblings, pathIndices } = createMerkleProof([identityCommitment], identityCommitment) - await expect(transaction).to.emit(contract, "LeakPublished").withArgs(entityIds[1], bytes32Leak) + const transaction = contract.removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices) + + await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor") + }) + + it("Should remove a whistleblower from an existing entity", async () => { + const identity = new ZkIdentity(Strategy.MESSAGE, "test") + const identityCommitment = identity.genIdentityCommitment() + const { siblings, pathIndices } = createMerkleProof([identityCommitment], identityCommitment) + + const transaction = contract + .connect(accounts[1]) + .removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices) + + await expect(transaction) + .to.emit(contract, "MemberRemoved") + .withArgs( + entityIds[0], + identityCommitment, + "15019797232609675441998260052101280400536945603062888308240081994073687793470" + ) + }) + }) + + describe("# publishLeak", () => { + const identity = new ZkIdentity(Strategy.MESSAGE, "test") + const identityCommitment = identity.genIdentityCommitment() + const merkleProof = createMerkleProof([identityCommitment, BigInt(1)], identityCommitment) + const leak = "leak" + const bytes32Leak = utils.formatBytes32String(leak) + + const witness = Semaphore.genWitness( + identity.getTrapdoor(), + identity.getNullifier(), + merkleProof, + entityIds[1], + leak + ) + + let solidityProof: SemaphoreSolidityProof + let publicSignals: SemaphorePublicSignals + + before(async () => { + await contract.createEntity(entityIds[1], editor, depth) + await contract.connect(accounts[1]).addWhistleblower(entityIds[1], identityCommitment) + await contract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1)) + + const fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath) + + publicSignals = fullProof.publicSignals + solidityProof = Semaphore.packToSolidityProof(fullProof.proof) + }) + + it("Should not publish a leak if the caller is not the editor", async () => { + const transaction = contract.publishLeak( + bytes32Leak, + publicSignals.nullifierHash, + entityIds[0], + solidityProof + ) + + await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor") + }) + + it("Should not publish a leak if the proof is not valid", async () => { + const nullifierHash = Semaphore.genNullifierHash(entityIds[0], identity.getNullifier()) + + const transaction = contract + .connect(accounts[1]) + .publishLeak(bytes32Leak, nullifierHash, entityIds[1], solidityProof) + + await expect(transaction).to.be.revertedWith("InvalidProof()") + }) + + it("Should publish a leak", async () => { + const transaction = contract + .connect(accounts[1]) + .publishLeak(bytes32Leak, publicSignals.nullifierHash, entityIds[1], solidityProof) + + await expect(transaction).to.emit(contract, "LeakPublished").withArgs(entityIds[1], bytes32Leak) + }) }) - }) }) diff --git a/test/utils.ts b/test/utils.ts index 2ad2843..9858734 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -6,28 +6,28 @@ import { poseidon } from "circomlibjs" export const SnarkScalarField = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617") export function createMerkleProof(leaves: bigint[], leaf: bigint) { - return generateMerkleProof(20, BigInt(0), leaves, leaf) + return generateMerkleProof(20, BigInt(0), leaves, leaf) } export function createTree(depth: number, n = 0): IncrementalMerkleTree { - const tree = new IncrementalMerkleTree(poseidon, depth, BigInt(0), 2) + const tree = new IncrementalMerkleTree(poseidon, depth, BigInt(0), 2) - for (let i = 0; i < n; i++) { - tree.insert(BigInt(i + 1)) - } + for (let i = 0; i < n; i++) { + tree.insert(BigInt(i + 1)) + } - return tree + return tree } export function createIdentityCommitments(n: number): bigint[] { - const identityCommitments: bigint[] = [] + const identityCommitments: bigint[] = [] - for (let i = 0; i < n; i++) { - const identity = new ZkIdentity(Strategy.MESSAGE, i.toString()) - const identityCommitment = identity.genIdentityCommitment() + for (let i = 0; i < n; i++) { + const identity = new ZkIdentity(Strategy.MESSAGE, i.toString()) + const identityCommitment = identity.genIdentityCommitment() - identityCommitments.push(identityCommitment) - } + identityCommitments.push(identityCommitment) + } - return identityCommitments + return identityCommitments } diff --git a/tsconfig.json b/tsconfig.json index d68de35..df842c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "moduleResolution": "Node", - "noImplicitAny": true, - "resolveJsonModule": true, - "target": "ES2018", - "module": "CommonJS", - "strict": true, - "esModuleInterop": true, - "outDir": "dist", - "typeRoots": ["node_modules/@types", "types"] - }, - "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"], - "files": ["hardhat.config.ts"] + "compilerOptions": { + "moduleResolution": "Node", + "noImplicitAny": true, + "resolveJsonModule": true, + "target": "ES2018", + "module": "CommonJS", + "strict": true, + "esModuleInterop": true, + "outDir": "dist", + "typeRoots": ["node_modules/@types", "types"] + }, + "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"], + "files": ["hardhat.config.ts"] }