Merge pull request #129 from semaphore-protocol/refactor/extensions

Refactoring of extensions contracts

Former-commit-id: 2c2c02f82b
This commit is contained in:
cedoor
2022-09-09 16:34:23 +02:00
committed by GitHub
12 changed files with 94 additions and 76 deletions

View File

@@ -30,7 +30,7 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
/// @param merkleTreeDepth: Depth of the tree.
modifier onlySupportedMerkleTreeDepth(uint256 merkleTreeDepth) {
if (address(verifiers[merkleTreeDepth]) == address(0)) {
revert Semaphore__TreeDepthIsNotSupported();
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
_;
}

View File

@@ -14,20 +14,11 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
/// @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 merkleTreeDepths: Three depths used in verifiers.
/// @param verifierAddresses: Verifier addresses.
constructor(uint256[] memory merkleTreeDepths, address[] memory verifierAddresses) {
require(
merkleTreeDepths.length == verifierAddresses.length,
"SemaphoreVoting: parameters lists does not have the same length"
);
for (uint8 i = 0; i < merkleTreeDepths.length; ) {
verifiers[merkleTreeDepths[i]] = IVerifier(verifierAddresses[i]);
/// @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; ) {
verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);
unchecked {
++i;
@@ -38,7 +29,10 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
/// @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");
if (polls[pollId].coordinator != _msgSender()) {
revert Semaphore__CallerIsNotThePollCoordinator();
}
_;
}
@@ -48,10 +42,9 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
address coordinator,
uint256 merkleTreeDepth
) public override {
require(
address(verifiers[merkleTreeDepth]) != address(0),
"SemaphoreVoting: Merkle tree depth value is not supported"
);
if (address(verifiers[merkleTreeDepth]) == address(0)) {
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
_createGroup(pollId, merkleTreeDepth, 0);
@@ -66,14 +59,18 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
/// @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");
if (polls[pollId].state != PollState.Created) {
revert Semaphore__PollHasAlreadyBeenStarted();
}
_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");
if (polls[pollId].state != PollState.Created) {
revert Semaphore__PollHasAlreadyBeenStarted();
}
polls[pollId].state = PollState.Ongoing;
@@ -89,7 +86,9 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
) public override onlyCoordinator(pollId) {
Poll memory poll = polls[pollId];
require(poll.state == PollState.Ongoing, "SemaphoreVoting: vote can only be cast in an ongoing poll");
if (poll.state != PollState.Ongoing) {
revert Semaphore__PollIsNotOngoing();
}
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);
@@ -106,7 +105,9 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
/// @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");
if (polls[pollId].state != PollState.Ongoing) {
revert Semaphore__PollIsNotOngoing();
}
polls[pollId].state = PollState.Ended;

View File

@@ -16,20 +16,11 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
/// @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 merkleTreeDepths: Three depths used in verifiers.
/// @param verifierAddresses: Verifier addresses.
constructor(uint256[] memory merkleTreeDepths, address[] memory verifierAddresses) {
require(
merkleTreeDepths.length == verifierAddresses.length,
"SemaphoreWhistleblowing: parameters lists does not have the same length"
);
for (uint8 i = 0; i < merkleTreeDepths.length; ) {
verifiers[merkleTreeDepths[i]] = IVerifier(verifierAddresses[i]);
/// @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; ) {
verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);
unchecked {
++i;
@@ -40,7 +31,10 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
/// @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");
if (entityId != entities[_msgSender()]) {
revert Semaphore__CallerIsNotTheEditor();
}
_;
}
@@ -50,10 +44,9 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
address editor,
uint256 merkleTreeDepth
) public override {
require(
address(verifiers[merkleTreeDepth]) != address(0),
"SemaphoreWhistleblowing: Merkle tree depth value is not supported"
);
if (address(verifiers[merkleTreeDepth]) == address(0)) {
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
_createGroup(entityId, merkleTreeDepth, 0);

View File

@@ -5,7 +5,7 @@ pragma solidity 0.8.4;
/// @dev Interface of a Semaphore contract.
interface ISemaphore {
error Semaphore__CallerIsNotTheGroupAdmin();
error Semaphore__TreeDepthIsNotSupported();
error Semaphore__MerkleTreeDepthIsNotSupported();
error Semaphore__MerkleTreeRootIsExpired();
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();

View File

@@ -4,12 +4,22 @@ pragma solidity 0.8.4;
/// @title SemaphoreVoting interface.
/// @dev Interface of SemaphoreVoting contract.
interface ISemaphoreVoting {
error Semaphore__CallerIsNotThePollCoordinator();
error Semaphore__MerkleTreeDepthIsNotSupported();
error Semaphore__PollHasAlreadyBeenStarted();
error Semaphore__PollIsNotOngoing();
enum PollState {
Created,
Ongoing,
Ended
}
struct Verifier {
address contractAddress;
uint256 merkleTreeDepth;
}
struct Poll {
address coordinator;
PollState state;

View File

@@ -4,6 +4,14 @@ pragma solidity 0.8.4;
/// @title SemaphoreWhistleblowing interface.
/// @dev Interface of SemaphoreWhistleblowing contract.
interface ISemaphoreWhistleblowing {
error Semaphore__CallerIsNotTheEditor();
error Semaphore__MerkleTreeDepthIsNotSupported();
struct Verifier {
address contractAddress;
uint256 merkleTreeDepth;
}
/// @dev Emitted when a new entity is created.
/// @param entityId: Id of the entity.
/// @param editor: Editor of the entity.

View File

@@ -4,8 +4,8 @@ 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> => {
.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)
@@ -35,7 +35,7 @@ task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
}
})
const contract = await ContractFactory.deploy([20], [verifier])
const contract = await ContractFactory.deploy(verifiers)
await contract.deployed()

View File

@@ -4,8 +4,8 @@ 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> => {
.addParam("verifiers", "Verifier contract address", undefined, types.json)
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
const poseidonABI = poseidonContract.generateABI(2)
const poseidonBytecode = poseidonContract.createCode(2)
@@ -35,7 +35,7 @@ task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contra
}
})
const contract = await ContractFactory.deploy([20], [verifier])
const contract = await ContractFactory.deploy(verifiers)
await contract.deployed()

View File

@@ -35,7 +35,7 @@ describe("Semaphore", () => {
it("Should not create a group if the tree depth is not supported", async () => {
const transaction = contract["createGroup(uint256,uint256,uint256,address)"](groupId, 10, 0, accounts[0])
await expect(transaction).to.be.revertedWith("Semaphore__TreeDepthIsNotSupported()")
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
})
it("Should create a group", async () => {

View File

@@ -28,7 +28,11 @@ describe("SemaphoreVoting", () => {
before(async () => {
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth: treeDepth })
contract = await run("deploy:semaphore-voting", { logs: false, verifier: verifierAddress })
contract = await run("deploy:semaphore-voting", {
logs: false,
verifiers: [{ merkleTreeDepth: treeDepth, contractAddress: verifierAddress }]
})
accounts = await ethers.getSigners()
coordinator = await accounts[1].getAddress()
})
@@ -37,7 +41,7 @@ describe("SemaphoreVoting", () => {
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: Merkle tree depth value is not supported")
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
})
it("Should not create a poll greater than the snark scalar field", async () => {
@@ -67,7 +71,7 @@ describe("SemaphoreVoting", () => {
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")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
})
it("Should start the poll", async () => {
@@ -79,7 +83,7 @@ describe("SemaphoreVoting", () => {
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")
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
})
})
@@ -94,7 +98,7 @@ describe("SemaphoreVoting", () => {
const transaction = contract.addVoter(pollIds[0], identityCommitment)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
})
it("Should not add a voter if the poll has already been started", async () => {
@@ -103,7 +107,7 @@ describe("SemaphoreVoting", () => {
const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], identityCommitment)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: voters can only be added before voting")
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
})
it("Should add a voter to an existing poll", async () => {
@@ -158,7 +162,7 @@ describe("SemaphoreVoting", () => {
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")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
})
it("Should not cast a vote if the poll is not ongoing", async () => {
@@ -166,7 +170,7 @@ describe("SemaphoreVoting", () => {
.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")
await expect(transaction).to.be.revertedWith("Semaphore__PollIsNotOngoing()")
})
it("Should not cast a vote if the proof is not valid", async () => {
@@ -200,7 +204,7 @@ describe("SemaphoreVoting", () => {
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")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
})
it("Should end the poll", async () => {
@@ -212,7 +216,7 @@ describe("SemaphoreVoting", () => {
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")
await expect(transaction).to.be.revertedWith("Semaphore__PollIsNotOngoing()")
})
})
})

View File

@@ -26,7 +26,11 @@ describe("SemaphoreWhistleblowing", () => {
before(async () => {
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth: treeDepth })
contract = await run("deploy:semaphore-whistleblowing", { logs: false, verifier: verifierAddress })
contract = await run("deploy:semaphore-whistleblowing", {
logs: false,
verifiers: [{ merkleTreeDepth: treeDepth, contractAddress: verifierAddress }]
})
accounts = await ethers.getSigners()
editor = await accounts[1].getAddress()
})
@@ -35,9 +39,7 @@ describe("SemaphoreWhistleblowing", () => {
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: Merkle tree depth value is not supported"
)
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
})
it("Should not create an entity greater than the snark scalar field", async () => {
@@ -70,7 +72,7 @@ describe("SemaphoreWhistleblowing", () => {
const transaction = contract.addWhistleblower(entityIds[0], identityCommitment)
await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
})
it("Should add a whistleblower to an existing entity", async () => {
@@ -107,7 +109,7 @@ describe("SemaphoreWhistleblowing", () => {
const transaction = contract.removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices)
await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
})
it("Should remove a whistleblower from an existing entity", async () => {
@@ -168,7 +170,7 @@ describe("SemaphoreWhistleblowing", () => {
solidityProof
)
await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: caller is not the editor")
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
})
it("Should not publish a leak if the proof is not valid", async () => {

View File

@@ -13375,7 +13375,14 @@ __metadata:
languageName: node
linkType: hard
"semaphore@>=1.0.1, semaphore@workspace:.":
"semaphore@npm:>=1.0.1, semaphore@npm:^1.0.3, semaphore@npm:^1.1.0":
version: 1.1.0
resolution: "semaphore@npm:1.1.0"
checksum: d2445d232ad9959048d4748ef54eb01bc7b60436be2b42fb7de20c4cffacf70eafeeecd3772c1baf408cfdce3805fa6618a4389590335671f18cde54ef3cfae4
languageName: node
linkType: hard
"semaphore@workspace:.":
version: 0.0.0-use.local
resolution: "semaphore@workspace:."
dependencies:
@@ -13426,13 +13433,6 @@ __metadata:
languageName: unknown
linkType: soft
"semaphore@npm:^1.0.3, semaphore@npm:^1.1.0":
version: 1.1.0
resolution: "semaphore@npm:1.1.0"
checksum: d2445d232ad9959048d4748ef54eb01bc7b60436be2b42fb7de20c4cffacf70eafeeecd3772c1baf408cfdce3805fa6618a4389590335671f18cde54ef3cfae4
languageName: node
linkType: hard
"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.6.0, semver@npm:^5.7.0":
version: 5.7.1
resolution: "semver@npm:5.7.1"