Skip to content

Commit

Permalink
refactor(contracts): move access logic to group contract
Browse files Browse the repository at this point in the history
  • Loading branch information
cedoor committed Jan 12, 2024
1 parent b7d58bd commit 2b05a94
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 139 deletions.
78 changes: 15 additions & 63 deletions packages/contracts/contracts/Semaphore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,65 +18,29 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
/// @dev Gets a group id and returns the group parameters.
mapping(uint256 => Group) public groups;

/// @dev Checks if the group admin is the transaction sender.
/// @param groupId: Id of the group.
modifier onlyGroupAdmin(uint256 groupId) {
if (groups[groupId].admin != _msgSender()) {
revert Semaphore__CallerIsNotTheGroupAdmin();
}
_;
}

/// @dev Checks if the group exists.
/// @param groupId: Id of the group.
modifier onlyExistingGroup(uint256 groupId) {
if (groups[groupId].admin == address(0)) {
revert Semaphore__GroupDoesNotExist();
}

_;
}

/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
/// @param _verifier: Semaphore verifier address.
constructor(ISemaphoreVerifier _verifier) {
verifier = _verifier;
}

/// @dev See {ISemaphore-createGroup}.
/// @dev See {SemaphoreGroups-_createGroup}.
function createGroup(uint256 groupId, address admin) external override {
if (groups[groupId].admin != address(0)) {
revert Semaphore__GroupAlreadyExists();
}
_createGroup(groupId, admin);

groups[groupId].admin = admin;
groups[groupId].merkleTreeDuration = 1 hours;

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

/// @dev See {ISemaphore-createGroup}.
function createGroup(uint256 groupId, address admin, uint256 merkleTreeDuration) external override {
if (groups[groupId].admin != address(0)) {
revert Semaphore__GroupAlreadyExists();
}
_createGroup(groupId, admin);

groups[groupId].admin = admin;
groups[groupId].merkleTreeDuration = merkleTreeDuration;

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

/// @dev See {ISemaphore-updateGroupAdmin}.
function updateGroupAdmin(
uint256 groupId,
address newAdmin
) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
groups[groupId].admin = newAdmin;

emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
/// @dev See {SemaphoreGroups-_updateGroupAdmin}.
function updateGroupAdmin(uint256 groupId, address newAdmin) external override {
_updateGroupAdmin(groupId, newAdmin);
}

/// @dev See {ISemaphore-updateGroupMerkleTreeDuration}.
Expand All @@ -91,56 +55,44 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
emit GroupMerkleTreeDurationUpdated(groupId, oldMerkleTreeDuration, newMerkleTreeDuration);
}

/// @dev See {ISemaphore-addMember}.
function addMember(
uint256 groupId,
uint256 identityCommitment
) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
/// @dev See {SemaphoreGroups-_addMember}.
function addMember(uint256 groupId, uint256 identityCommitment) external override {
_addMember(groupId, identityCommitment);

uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);

groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}

/// @dev See {ISemaphore-addMembers}.
function addMembers(
uint256 groupId,
uint256[] calldata identityCommitments
) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
for (uint256 i = 0; i < identityCommitments.length; ) {
_addMember(groupId, identityCommitments[i]);

unchecked {
++i;
}
}
/// @dev See {SemaphoreGroups-_addMembers}.
function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external override {
_addMembers(groupId, identityCommitments);

uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);

groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}

/// @dev See {ISemaphore-updateMember}.
/// @dev See {SemaphoreGroups-_updateMember}.
function updateMember(
uint256 groupId,
uint256 identityCommitment,
uint256 newIdentityCommitment,
uint256[] calldata merkleProofSiblings
) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
) external override {
_updateMember(groupId, identityCommitment, newIdentityCommitment, merkleProofSiblings);

uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);

groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}

/// @dev See {ISemaphore-removeMember}.
/// @dev See {SemaphoreGroups-_removeMember}.
function removeMember(
uint256 groupId,
uint256 identityCommitment,
uint256[] calldata merkleProofSiblings
) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
) external override {
_removeMember(groupId, identityCommitment, merkleProofSiblings);

uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
Expand Down
79 changes: 72 additions & 7 deletions packages/contracts/contracts/base/SemaphoreGroups.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,90 @@ pragma solidity 0.8.4;

import "../interfaces/ISemaphoreGroups.sol";
import {InternalLeanIMT, LeanIMTData} from "@zk-kit/imt.sol/internal/InternalLeanIMT.sol";
import "@openzeppelin/contracts/utils/Context.sol";

/// @title Semaphore groups contract.
/// @dev This contract allows you to create groups, add, remove and update members.
/// You can use getters to obtain informations about groups (root, depth, number of leaves).
abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
abstract contract SemaphoreGroups is ISemaphoreGroups {
using InternalLeanIMT for LeanIMTData;

/// @dev Gets a group id and returns the tree data.
/// @dev Gets a group id and returns its tree data.
mapping(uint256 => LeanIMTData) internal merkleTrees;

/// @dev Gets a group id and returns its admin.
mapping(uint256 => address) internal admins;

/// @dev Checks if the group admin is the transaction sender.
/// @param groupId: Id of the group.
modifier onlyGroupAdmin(uint256 groupId) {
if (admins[groupId] != msg.sender) {
revert Semaphore__CallerIsNotTheGroupAdmin();
}
_;
}

/// @dev Checks if the group exists.
/// @param groupId: Id of the group.
modifier onlyExistingGroup(uint256 groupId) {
if (admins[groupId] == address(0)) {
revert Semaphore__GroupDoesNotExist();
}

_;
}

/// @dev Creates a new group. Only the admin will be able to add or remove members.
/// @param groupId: Id of the group.
/// @param admin: Admin of the group.
function _createGroup(uint256 groupId, address admin) internal virtual {
if (admins[groupId] != address(0)) {
revert Semaphore__GroupAlreadyExists();
}

admins[groupId] = admin;

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

/// @dev Updates the group admin.
/// @param groupId: Id of the group.
/// @param newAdmin: New admin of the group.
function _updateGroupAdmin(
uint256 groupId,
address newAdmin
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
admins[groupId] = newAdmin;

emit GroupAdminUpdated(groupId, msg.sender, newAdmin);
}

/// @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 {
function _addMember(
uint256 groupId,
uint256 identityCommitment
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 merkleTreeRoot = merkleTrees[groupId]._insert(identityCommitment);
uint256 leafIndex = getMerkleTreeSize(groupId) - 1;

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

/// @dev Adds new members to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitments: New identity commitments.
function _addMembers(uint256 groupId, uint256[] calldata identityCommitments) internal virtual {
for (uint256 i = 0; i < identityCommitments.length; ) {
_addMember(groupId, identityCommitments[i]);

unchecked {
++i;
}
}
}

/// @dev Updates an identity commitment of an existing group. A proof of membership is
/// needed to check if the node to be updated is part of the tree.
/// @param groupId: Id of the group.
Expand All @@ -35,13 +98,14 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
uint256 oldIdentityCommitment,
uint256 newIdentityCommitment,
uint256[] calldata merkleProofSiblings
) internal virtual {
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 leafIndex = merkleTrees[groupId]._indexOf(oldIdentityCommitment);

uint256 merkleTreeRoot = merkleTrees[groupId]._update(
oldIdentityCommitment,
newIdentityCommitment,
merkleProofSiblings
);
uint256 leafIndex = merkleTrees[groupId]._indexOf(newIdentityCommitment);

emit MemberUpdated(groupId, leafIndex, oldIdentityCommitment, newIdentityCommitment, merkleTreeRoot);
}
Expand All @@ -55,8 +119,9 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
uint256 groupId,
uint256 identityCommitment,
uint256[] calldata merkleProofSiblings
) internal virtual {
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 leafIndex = merkleTrees[groupId]._indexOf(identityCommitment);

uint256 merkleTreeRoot = merkleTrees[groupId]._remove(identityCommitment, merkleProofSiblings);

emit MemberRemoved(groupId, leafIndex, identityCommitment, merkleTreeRoot);
Expand Down
85 changes: 27 additions & 58 deletions packages/contracts/contracts/interfaces/ISemaphore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,20 @@ pragma solidity 0.8.4;

/// @title Semaphore contract interface.
interface ISemaphore {
error Semaphore__GroupDoesNotExist();
error Semaphore__GroupAlreadyExists();
error Semaphore__GroupHasNoMembers();
error Semaphore__CallerIsNotTheGroupAdmin();
error Semaphore__MerkleTreeDepthIsNotSupported();
error Semaphore__MerkleTreeRootIsExpired();
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
error Semaphore__YouAreUsingTheSameNillifierTwice();
error Semaphore__InvalidProof();

/// It defines all the group parameters, in addition to those in the Merkle tree.
/// It defines all the group parameters used by Semaphore.sol.
struct Group {
address admin;
uint256 merkleTreeDuration;
mapping(uint256 => uint256) merkleRootCreationDates;
mapping(uint256 => bool) nullifiers;
}

/// @dev Emitted when a new group is created.
/// @param groupId: Id of the group.
event GroupCreated(uint256 indexed groupId);

/// @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 the Merkle tree duration of a group is updated.
/// @param groupId: Id of the group.
/// @param oldMerkleTreeDuration: Old Merkle tree duration of the group.
Expand All @@ -57,71 +43,54 @@ interface ISemaphore {
uint256[8] proof
);

/// @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 nullifier: Nullifier.
/// @param message: Semaphore message.
/// @param scope: Scope.
/// @param proof: Zero-knowledge proof.
function verifyProof(
uint256 groupId,
uint256 merkleTreeRoot,
uint256 nullifier,
uint256 message,
uint256 scope,
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 admin: Admin of the group.
/// @dev See {SemaphoreGroups-_createGroup}.
function createGroup(uint256 groupId, address admin) external;

/// @dev Creates a new group. Only the admin will be able to add or remove members.
/// @dev It creates a group with a custom Merkle tree duration.
/// @param groupId: Id of the group.
/// @param admin: Admin of the group.
/// @param merkleTreeRootDuration: Time before the validity of a root expires.
function createGroup(uint256 groupId, address admin, uint256 merkleTreeRootDuration) external;
/// @param merkleTreeDuration: Merkle tree duration.
function createGroup(uint256 groupId, address admin, uint256 merkleTreeDuration) external;

/// @dev Updates the group admin.
/// @param groupId: Id of the group.
/// @param newAdmin: New admin of the group.
/// @dev See {SemaphoreGroups-_updateGroupAdmin}.
function updateGroupAdmin(uint256 groupId, address newAdmin) external;

/// @dev Updates the group Merkle tree duration.
/// @param groupId: Id of the group.
/// @param newMerkleTreeDuration: New Merkle tree duration.
function updateGroupMerkleTreeDuration(uint256 groupId, uint256 newMerkleTreeDuration) external;

/// @dev Adds a new member to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitment: New identity commitment.
/// @dev See {SemaphoreGroups-_addMember}.
function addMember(uint256 groupId, uint256 identityCommitment) external;

/// @dev Adds new members to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitments: New identity commitments.
/// @dev See {SemaphoreGroups-_addMembers}.
function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external;

/// @dev Updates an identity commitment of an existing group. A proof of membership is
/// needed to check if the node to be updated is part of the tree.
/// @param groupId: Id of the group.
/// @param oldIdentityCommitment: Existing identity commitment to be updated.
/// @param newIdentityCommitment: New identity commitment.
/// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership.
/// @dev See {SemaphoreGroups-_updateMember}.
function updateMember(
uint256 groupId,
uint256 oldIdentityCommitment,
uint256 newIdentityCommitment,
uint256[] calldata merkleProofSiblings
) 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 removed.
/// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership.
/// @dev See {SemaphoreGroups-_removeMember}.
function removeMember(uint256 groupId, uint256 identityCommitment, uint256[] calldata merkleProofSiblings) 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 merkleTreeRoot: Root of the Merkle tree.
/// @param nullifier: Nullifier.
/// @param message: Semaphore message.
/// @param scope: Scope.
/// @param proof: Zero-knowledge proof.
function verifyProof(
uint256 groupId,
uint256 merkleTreeRoot,
uint256 nullifier,
uint256 message,
uint256 scope,
uint256[8] calldata proof
) external;
}
Loading

0 comments on commit 2b05a94

Please sign in to comment.