Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merkle tree expiries #124

Merged
merged 5 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 51 additions & 11 deletions contracts/Semaphore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
/// @dev Gets a group id and returns the group admin address.
mapping(uint256 => address) public groupAdmins;

/// @dev Gets a group id and returns data to check if a Merkle root is expired.
mapping(uint256 => MerkleTreeExpiry) public merkleTreeExpiries;

/// @dev Checks if the group admin is the transaction sender.
/// @param groupId: Id of the group.
modifier onlyGroupAdmin(uint256 groupId) {
Expand All @@ -24,9 +27,9 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
}

/// @dev Checks if there is a verifier for the given tree depth.
/// @param depth: Depth of the tree.
modifier onlySupportedDepth(uint256 depth) {
if (address(verifiers[depth]) == address(0)) {
/// @param merkleTreeDepth: Depth of the tree.
modifier onlySupportedMerkleTreeDepth(uint256 merkleTreeDepth) {
if (address(verifiers[merkleTreeDepth]) == address(0)) {
revert Semaphore__TreeDepthIsNotSupported();
}
_;
Expand All @@ -37,6 +40,7 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
constructor(Verifier[] memory _verifiers) {
for (uint8 i = 0; i < _verifiers.length; ) {
verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);

unchecked {
++i;
}
Expand All @@ -46,13 +50,30 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
/// @dev See {ISemaphore-createGroup}.
function createGroup(
uint256 groupId,
uint256 depth,
uint256 merkleTreeDepth,
uint256 zeroValue,
address admin
) external override onlySupportedDepth(depth) {
_createGroup(groupId, depth, zeroValue);
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
_createGroup(groupId, merkleTreeDepth, zeroValue);

groupAdmins[groupId] = admin;
merkleTreeExpiries[groupId].rootDuration = 1 hours;

emit GroupAdminUpdated(groupId, address(0), admin);
}

/// @dev See {ISemaphore-createGroup}.
function createGroup(
uint256 groupId,
uint256 merkleTreeDepth,
uint256 zeroValue,
address admin,
uint256 merkleTreeRootDuration
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
_createGroup(groupId, merkleTreeDepth, zeroValue);

groupAdmins[groupId] = admin;
merkleTreeExpiries[groupId].rootDuration = merkleTreeRootDuration;

emit GroupAdminUpdated(groupId, address(0), admin);
}
Expand All @@ -67,6 +88,10 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
/// @dev See {ISemaphore-addMember}.
function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) {
_addMember(groupId, identityCommitment);

uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);

merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
}

/// @dev See {ISemaphore-removeMember}.
Expand All @@ -82,21 +107,36 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
/// @dev See {ISemaphore-verifyProof}.
function verifyProof(
uint256 groupId,
uint256 merkleTreeRoot,
bytes32 signal,
uint256 nullifierHash,
uint256 externalNullifier,
uint256[8] calldata proof
) external override {
uint256 root = getRoot(groupId);
uint256 depth = getDepth(groupId);
uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId);

if (depth == 0) {
if (currentMerkleTreeRoot == 0) {
revert Semaphore__GroupDoesNotExist();
}

IVerifier verifier = verifiers[depth];
if (merkleTreeRoot != currentMerkleTreeRoot) {
uint256 rootCreationDate = merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot];
uint256 rootDuration = merkleTreeExpiries[groupId].rootDuration;

if (rootCreationDate == 0) {
revert Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
}

if (block.timestamp > rootCreationDate + rootDuration) {
revert Semaphore__MerkleTreeRootIsExpired();
}
}

uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);

IVerifier verifier = verifiers[merkleTreeDepth];

_verifyProof(signal, root, nullifierHash, externalNullifier, proof, verifier);
_verifyProof(signal, merkleTreeRoot, nullifierHash, externalNullifier, proof, verifier);

_saveNullifierHash(nullifierHash);

Expand Down
34 changes: 17 additions & 17 deletions contracts/base/SemaphoreGroups.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,39 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {

/// @dev Creates a new group by initializing the associated tree.
/// @param groupId: Id of the group.
/// @param depth: Depth of the tree.
/// @param merkleTreeDepth: Depth of the tree.
/// @param zeroValue: Zero value of the tree.
function _createGroup(
uint256 groupId,
uint256 depth,
uint256 merkleTreeDepth,
uint256 zeroValue
) internal virtual {
if (groupId >= SNARK_SCALAR_FIELD) {
revert Semaphore__GroupIdIsNotLessThanSnarkScalarField();
}

if (getDepth(groupId) != 0) {
if (getMerkleTreeDepth(groupId) != 0) {
revert Semaphore__GroupAlreadyExists();
}

groups[groupId].init(depth, zeroValue);
groups[groupId].init(merkleTreeDepth, zeroValue);

emit GroupCreated(groupId, depth, zeroValue);
emit GroupCreated(groupId, merkleTreeDepth, 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 {
if (getDepth(groupId) == 0) {
if (getMerkleTreeDepth(groupId) == 0) {
revert Semaphore__GroupDoesNotExist();
}

groups[groupId].insert(identityCommitment);

uint256 root = getRoot(groupId);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);

emit MemberAdded(groupId, identityCommitment, root);
emit MemberAdded(groupId, identityCommitment, merkleTreeRoot);
}

/// @dev Removes an identity commitment from an existing group. A proof of membership is
Expand All @@ -64,29 +64,29 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) internal virtual {
if (getDepth(groupId) == 0) {
if (getMerkleTreeRoot(groupId) == 0) {
revert Semaphore__GroupDoesNotExist();
}

groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);

uint256 root = getRoot(groupId);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);

emit MemberRemoved(groupId, identityCommitment, root);
emit MemberRemoved(groupId, identityCommitment, merkleTreeRoot);
}

/// @dev See {ISemaphoreGroups-getRoot}.
function getRoot(uint256 groupId) public view virtual override returns (uint256) {
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
function getMerkleTreeRoot(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 (uint256) {
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) {
return groups[groupId].depth;
}

/// @dev See {ISemaphoreGroups-getNumberOfLeaves}.
function getNumberOfLeaves(uint256 groupId) public view virtual override returns (uint256) {
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
return groups[groupId].numberOfLeaves;
}
}
29 changes: 17 additions & 12 deletions contracts/extensions/SemaphoreVoting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
/// 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 merkleTreeDepths: Three depths used in verifiers.
/// @param verifierAddresses: Verifier addresses.
constructor(uint256[] memory depths, address[] memory verifierAddresses) {
constructor(uint256[] memory merkleTreeDepths, address[] memory verifierAddresses) {
require(
depths.length == verifierAddresses.length,
merkleTreeDepths.length == verifierAddresses.length,
"SemaphoreVoting: parameters lists does not have the same length"
);

for (uint8 i = 0; i < depths.length; ) {
verifiers[depths[i]] = IVerifier(verifierAddresses[i]);
for (uint8 i = 0; i < merkleTreeDepths.length; ) {
verifiers[merkleTreeDepths[i]] = IVerifier(verifierAddresses[i]);

unchecked {
++i;
}
Expand All @@ -45,11 +46,14 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
function createPoll(
uint256 pollId,
address coordinator,
uint256 depth
uint256 merkleTreeDepth
) public override {
require(address(verifiers[depth]) != address(0), "SemaphoreVoting: depth value is not supported");
require(
address(verifiers[merkleTreeDepth]) != address(0),
"SemaphoreVoting: Merkle tree depth value is not supported"
);

_createGroup(pollId, depth, 0);
_createGroup(pollId, merkleTreeDepth, 0);

Poll memory poll;

Expand Down Expand Up @@ -87,11 +91,12 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {

require(poll.state == PollState.Ongoing, "SemaphoreVoting: vote can only be cast in an ongoing poll");

uint256 depth = getDepth(pollId);
uint256 root = getRoot(pollId);
IVerifier verifier = verifiers[depth];
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);

IVerifier verifier = verifiers[merkleTreeDepth];

_verifyProof(vote, root, nullifierHash, pollId, proof, verifier);
_verifyProof(vote, merkleTreeRoot, nullifierHash, pollId, proof, verifier);

// Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)).
_saveNullifierHash(nullifierHash);
Expand Down
29 changes: 17 additions & 12 deletions contracts/extensions/SemaphoreWhistleblowing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
/// 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 merkleTreeDepths: Three depths used in verifiers.
/// @param verifierAddresses: Verifier addresses.
constructor(uint256[] memory depths, address[] memory verifierAddresses) {
constructor(uint256[] memory merkleTreeDepths, address[] memory verifierAddresses) {
require(
depths.length == verifierAddresses.length,
merkleTreeDepths.length == verifierAddresses.length,
"SemaphoreWhistleblowing: parameters lists does not have the same length"
);

for (uint8 i = 0; i < depths.length; ) {
verifiers[depths[i]] = IVerifier(verifierAddresses[i]);
for (uint8 i = 0; i < merkleTreeDepths.length; ) {
verifiers[merkleTreeDepths[i]] = IVerifier(verifierAddresses[i]);

unchecked {
++i;
}
Expand All @@ -47,11 +48,14 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
function createEntity(
uint256 entityId,
address editor,
uint256 depth
uint256 merkleTreeDepth
) public override {
require(address(verifiers[depth]) != address(0), "SemaphoreWhistleblowing: depth value is not supported");
require(
address(verifiers[merkleTreeDepth]) != address(0),
"SemaphoreWhistleblowing: Merkle tree depth value is not supported"
);

_createGroup(entityId, depth, 0);
_createGroup(entityId, merkleTreeDepth, 0);

entities[editor] = entityId;

Expand Down Expand Up @@ -80,11 +84,12 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
uint256 entityId,
uint256[8] calldata proof
) public override onlyEditor(entityId) {
uint256 depth = getDepth(entityId);
uint256 root = getRoot(entityId);
IVerifier verifier = verifiers[depth];
uint256 merkleTreeDepth = getMerkleTreeDepth(entityId);
uint256 merkleTreeRoot = getMerkleTreeRoot(entityId);

IVerifier verifier = verifiers[merkleTreeDepth];

_verifyProof(leak, root, nullifierHash, entityId, proof, verifier);
_verifyProof(leak, merkleTreeRoot, nullifierHash, entityId, proof, verifier);

emit LeakPublished(entityId, leak);
}
Expand Down
25 changes: 25 additions & 0 deletions contracts/interfaces/ISemaphore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ pragma solidity 0.8.4;
interface ISemaphore {
error Semaphore__CallerIsNotTheGroupAdmin();
error Semaphore__TreeDepthIsNotSupported();
error Semaphore__MerkleTreeRootIsExpired();
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();

struct Verifier {
address contractAddress;
uint256 merkleTreeDepth;
}

/// It defines all the parameters needed to check whether a
/// zero-knowledge proof generated with a certain Merkle tree is still valid.
struct MerkleTreeExpiry {
uint256 rootDuration;
mapping(uint256 => uint256) rootCreationDates;
}

/// @dev Emitted when an admin is assigned to a group.
/// @param groupId: Id of the group.
/// @param oldAdmin: Old admin of the group.
Expand All @@ -26,12 +35,14 @@ interface ISemaphore {
/// @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 merkleTreeRoot: Root of the Merkle tree.
/// @param signal: Semaphore signal.
/// @param nullifierHash: Nullifier hash.
/// @param externalNullifier: External nullifier.
/// @param proof: Zero-knowledge proof.
function verifyProof(
uint256 groupId,
uint256 merkleTreeRoot,
bytes32 signal,
uint256 nullifierHash,
uint256 externalNullifier,
Expand All @@ -50,6 +61,20 @@ interface ISemaphore {
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.
/// @param merkleTreeRootDuration: Time before the validity of a root expires.
function createGroup(
uint256 groupId,
uint256 depth,
uint256 zeroValue,
address admin,
uint256 merkleTreeRootDuration
) external;

/// @dev Updates the group admin.
/// @param groupId: Id of the group.
/// @param newAdmin: New admin of the group.
Expand Down
Loading