-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(excubiae): add SemaphoreExcubia contract with mocks and test
- Loading branch information
Showing
6 changed files
with
395 additions
and
3 deletions.
There are no files selected for viewing
78 changes: 78 additions & 0 deletions
78
packages/excubiae/contracts/extensions/SemaphoreExcubia.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0; | ||
|
||
import {Excubia} from "../Excubia.sol"; | ||
import {ISemaphore} from "@semaphore-protocol/contracts/interfaces/ISemaphore.sol"; | ||
|
||
/// @title Semaphore Excubia Contract | ||
/// @notice This contract extends the Excubia contract to integrate with the Semaphore protocol. | ||
/// It verifies the passerby Semaphore group membership proofs to grant access through the gate. | ||
/// @dev To allow only specific Semaphore identities from a group, the contract stores the specific group identifier. | ||
/// To avoid identities from passing twice, nullifiers are stored upon successful verification of the proofs. | ||
contract SemaphoreExcubia is Excubia { | ||
/// @notice The Semaphore contract interface. | ||
ISemaphore public immutable SEMAPHORE; | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Variable SemaphoreExcubia.SEMAPHORE is not in mixedCase
|
||
/// @notice The specific group identifier that proofs must match to pass the gate. | ||
/// @dev Used as a `scope` to ensure consistency during proof membership verification. | ||
uint256 public immutable GROUP_ID; | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Variable SemaphoreExcubia.GROUP_ID is not in mixedCase
|
||
|
||
/// @notice Mapping to track which nullifiers have been used to avoid passing the | ||
/// gate twice using the same Semaphore identity. | ||
/// @dev The nullifier is derived from the hash of the secret and group identifier, | ||
/// ensuring that the same identity cannot be registered twice for the same group. | ||
mapping(uint256 => bool) public passedNullifiers; | ||
|
||
/// @notice Error thrown when the group identifier does not match the expected one. | ||
error InvalidGroup(); | ||
|
||
/// @notice Error thrown when the proof is invalid. | ||
error InvalidProof(); | ||
|
||
/// @notice Error thrown when the proof scope does not match the expected group identifier. | ||
error UnexpectedScope(); | ||
|
||
/// @notice Constructor to initialize with target Semaphore contract and specific group identifier. | ||
/// @param _semaphore The address of the Semaphore contract. | ||
/// @param _groupId The group identifier that proofs must match. | ||
constructor(address _semaphore, uint256 _groupId) { | ||
if (_semaphore == address(0)) revert ZeroAddress(); | ||
|
||
SEMAPHORE = ISemaphore(_semaphore); | ||
|
||
if (SEMAPHORE.groupCounter() < _groupId) revert InvalidGroup(); | ||
|
||
GROUP_ID = _groupId; | ||
} | ||
|
||
/// @notice Internal function to handle the passing logic with check. | ||
/// @dev Calls the parent `_pass` function and registers the nullifier to avoid passing the gate twice. | ||
/// @param passerby The address of the entity attempting to pass the gate. | ||
/// @param data Additional data required for the check (ie., encoded Semaphore proof). | ||
function _pass(address passerby, bytes calldata data) internal override { | ||
ISemaphore.SemaphoreProof memory proof = abi.decode(data, (ISemaphore.SemaphoreProof)); | ||
|
||
// Avoiding passing the gate twice using the same nullifier. | ||
if (passedNullifiers[proof.nullifier]) revert AlreadyPassed(); | ||
|
||
super._pass(passerby, data); | ||
|
||
passedNullifiers[proof.nullifier] = true; | ||
} | ||
|
||
/// @notice Internal function to handle the gate protection (proof check) logic. | ||
/// @dev Checks if the proof matches the group ID, scope, and is valid. | ||
/// @param passerby The address of the entity attempting to pass the gate. | ||
/// @param data Additional data required for the check (i.e., encoded Semaphore proof). | ||
/// @return True if the proof is valid and the passerby passes the check, false otherwise. | ||
function _check(address passerby, bytes calldata data) internal view override returns (bool) { | ||
super._check(passerby, data); | ||
|
||
ISemaphore.SemaphoreProof memory proof = abi.decode(data, (ISemaphore.SemaphoreProof)); | ||
|
||
if (GROUP_ID != proof.scope) revert UnexpectedScope(); | ||
|
||
if (!SEMAPHORE.verifyProof(GROUP_ID, proof)) revert InvalidProof(); | ||
|
||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0; | ||
|
||
import {ISemaphore} from "@semaphore-protocol/contracts/interfaces/ISemaphore.sol"; | ||
|
||
/// @title Mock Semaphore Contract | ||
/// @notice This contract is a mock implementation of the ISemaphore interface for testing purposes. | ||
/// @dev It simulates the behavior of a real Semaphore contract by simulating the storage and verification | ||
/// of a set of predefined mocked proofs. | ||
contract MockSemaphore is ISemaphore { | ||
/// @dev Gets a group id and returns the relative group. | ||
mapping(uint256 => bool) public mockedGroups; | ||
|
||
/// @notice A mapping to store mocked proofs by their unique nullifiers. | ||
mapping(uint256 => bool) private mockedProofs; | ||
|
||
/// @dev Counter to assign an incremental id to the groups. | ||
/// This counter is used to keep track of the number of groups created. | ||
uint256 public groupCounter; | ||
|
||
/// MOCKS /// | ||
/// @notice Constructor to initialize the mock contract with predefined proofs. | ||
/// @param _groupIds An array of identifiers of groups to be intended as the contract managed groups. | ||
/// @param _nullifiers An array of nullifiers to be mocked as proofs. | ||
/// @param _validities An array of booleans to mock the validity of proofs associated with the nullifiers. | ||
constructor(uint256[] memory _groupIds, uint256[] memory _nullifiers, bool[] memory _validities) { | ||
for (uint256 i = 0; i < _groupIds.length; i++) { | ||
mockedGroups[_groupIds[i]] = true; | ||
groupCounter++; | ||
} | ||
|
||
for (uint256 i = 0; i < _nullifiers.length; i++) { | ||
mockedProofs[_nullifiers[i]] = _validities[i]; | ||
} | ||
} | ||
|
||
function verifyProof(uint256 groupId, SemaphoreProof calldata proof) external view returns (bool) { | ||
return mockedGroups[groupId] && mockedProofs[proof.nullifier]; | ||
} | ||
|
||
/// STUBS /// | ||
// The following functions are stubs and do not perform any meaningful operations. | ||
// They are placeholders to comply with the IEAS interface. | ||
function createGroup() external pure override returns (uint256) { | ||
return 0; | ||
} | ||
|
||
function createGroup(address /*admin*/) external pure override returns (uint256) { | ||
return 0; | ||
} | ||
|
||
function createGroup(address /*admin*/, uint256 /*merkleTreeDuration*/) external pure override returns (uint256) { | ||
return 0; | ||
} | ||
|
||
function updateGroupAdmin(uint256 /*groupId*/, address /*newAdmin*/) external override {} | ||
|
||
function acceptGroupAdmin(uint256 /*groupId*/) external override {} | ||
|
||
function updateGroupMerkleTreeDuration(uint256 /*groupId*/, uint256 /*newMerkleTreeDuration*/) external override {} | ||
|
||
function addMember(uint256 groupId, uint256 identityCommitment) external override {} | ||
|
||
function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external override {} | ||
|
||
function updateMember( | ||
uint256 /*groupId*/, | ||
uint256 /*oldIdentityCommitment*/, | ||
uint256 /*newIdentityCommitment*/, | ||
uint256[] calldata /*merkleProofSiblings*/ | ||
) external override {} | ||
|
||
function removeMember( | ||
uint256 /*groupId*/, | ||
uint256 /*identityCommitment*/, | ||
uint256[] calldata /*merkleProofSiblings*/ | ||
) external override {} | ||
|
||
function validateProof(uint256 /*groupId*/, SemaphoreProof calldata /*proof*/) external override {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.