mirror of
https://github.com/CryptKeeperZK/semaphore.git
synced 2026-01-08 23:17:58 -05:00
style: format code with prettier
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/----project.md
vendored
17
.github/ISSUE_TEMPLATE/----project.md
vendored
@@ -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
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/---bug.md
vendored
11
.github/ISSUE_TEMPLATE/---bug.md
vendored
@@ -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.
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/---feature.md
vendored
7
.github/ISSUE_TEMPLATE/---feature.md
vendored
@@ -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.**
|
||||
|
||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -14,8 +14,8 @@
|
||||
|
||||
## Does this introduce a breaking change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
|
||||
32
.github/workflows/coverage.yml
vendored
32
.github/workflows/coverage.yml
vendored
@@ -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 }}
|
||||
|
||||
30
.github/workflows/test.yml
vendored
30
.github/workflows/test.yml
vendored
@@ -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
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
|
||||
"**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ types
|
||||
# circuits
|
||||
circuits
|
||||
|
||||
# contracts
|
||||
contracts/verifiers
|
||||
|
||||
# docusaurus
|
||||
.docusaurus
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
"semi": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
||||
|
||||
@@ -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 }]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,4 @@
|
||||
"name": "Semaphore",
|
||||
"address": "0x99aAb52e60f40AAC0BFE53e003De847bBDbC9611"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -71,4 +71,4 @@
|
||||
"name": "Semaphore",
|
||||
"address": "0x9e4080e133384d2D09b593C003DCaF3c5a0C53A6"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
200
package.json
200
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -2,15 +2,15 @@ import { Signer } from "@ethersproject/abstract-signer"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("accounts", "Prints the list of accounts")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers }) => {
|
||||
const accounts: Signer[] = await ethers.getSigners()
|
||||
.addOptionalParam<boolean>("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
|
||||
})
|
||||
|
||||
@@ -3,43 +3,43 @@ import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.addParam<boolean>("verifier", "Verifier contract address", undefined, types.string)
|
||||
.setAction(async ({ logs, verifier }, { ethers }): Promise<Contract> => {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.addParam<boolean>("verifier", "Verifier contract address", undefined, types.string)
|
||||
.setAction(async ({ logs, verifier }, { ethers }): Promise<Contract> => {
|
||||
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
|
||||
})
|
||||
|
||||
@@ -3,43 +3,43 @@ import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contract")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.addParam<boolean>("verifier", "Verifier contract address", undefined, types.string)
|
||||
.setAction(async ({ logs, verifier }, { ethers }): Promise<Contract> => {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.addParam<boolean>("verifier", "Verifier contract address", undefined, types.string)
|
||||
.setAction(async ({ logs, verifier }, { ethers }): Promise<Contract> => {
|
||||
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
|
||||
})
|
||||
|
||||
@@ -3,43 +3,43 @@ import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore", "Deploy a Semaphore contract")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.addParam("verifiers", "Tree depths and verifier addresses", undefined, types.json)
|
||||
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.addParam("verifiers", "Tree depths and verifier addresses", undefined, types.json)
|
||||
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
|
||||
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
|
||||
})
|
||||
|
||||
@@ -2,16 +2,16 @@ import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:verifier", "Deploy a Verifier contract")
|
||||
.addOptionalParam<number>("depth", "Tree depth", 20, types.int)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ depth, logs }, { ethers }): Promise<Contract> => {
|
||||
const ContractFactory = await ethers.getContractFactory(`Verifier${depth}`)
|
||||
.addOptionalParam<number>("depth", "Tree depth", 20, types.int)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ depth, logs }, { ethers }): Promise<Contract> => {
|
||||
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
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user