style: format code with prettier

This commit is contained in:
cedoor
2022-05-31 18:22:30 +02:00
parent f5bc809010
commit 491ec8b864
46 changed files with 1496 additions and 1471 deletions

View File

@@ -1,3 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
"extends": ["@commitlint/config-conventional"]
}

View File

@@ -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

View File

@@ -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"]
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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.**

View File

@@ -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. -->

View File

@@ -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 }}

View File

@@ -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

View File

@@ -1,3 +1,3 @@
{
"**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
"**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
}

View File

@@ -19,6 +19,9 @@ types
# circuits
circuits
# contracts
contracts/verifiers
# docusaurus
.docusaurus

View File

@@ -1,5 +1,5 @@
{
"semi": false,
"arrowParens": "always",
"trailingComma": "none"
"semi": false,
"arrowParens": "always",
"trailingComma": "none"
}

View File

@@ -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 }]
}
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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"
}
}

View File

@@ -71,4 +71,4 @@
"name": "Semaphore",
"address": "0x99aAb52e60f40AAC0BFE53e003De847bBDbC9611"
}
]
]

View File

@@ -71,4 +71,4 @@
"name": "Semaphore",
"address": "0x9e4080e133384d2D09b593C003DCaF3c5a0C53A6"
}
]
]

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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)
})

View File

@@ -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)
})

View File

@@ -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)
})

View File

@@ -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
})

View File

@@ -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
})

View File

@@ -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
})

View File

@@ -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
})

View File

@@ -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
})

View File

@@ -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)
})
})
})
})

View File

@@ -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")
})
})
})

View File

@@ -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)
})
})
})
})

View File

@@ -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
}

View File

@@ -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"]
}