Skip to content

Commit

Permalink
feat(excubiae): add SemaphoreExcubia contract with mocks and test
Browse files Browse the repository at this point in the history
  • Loading branch information
0xjei committed Jul 1, 2024
1 parent b74bc41 commit 98d231c
Show file tree
Hide file tree
Showing 6 changed files with 395 additions and 3 deletions.
78 changes: 78 additions & 0 deletions packages/excubiae/contracts/extensions/SemaphoreExcubia.sol
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;
}
}
3 changes: 2 additions & 1 deletion packages/excubiae/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"@ethereum-attestation-service/eas-contracts": "^1.7.1",
"@openzeppelin/contracts": "^5.0.2"
"@openzeppelin/contracts": "^5.0.2",
"@semaphore-protocol/contracts": "^4.0.0-beta.16"
}
}
80 changes: 80 additions & 0 deletions packages/excubiae/contracts/test/MockSemaphore.sol
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 {}
}
2 changes: 1 addition & 1 deletion packages/excubiae/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HardhatUserConfig } from "hardhat/config"

const hardhatConfig: HardhatUserConfig = {
solidity: {
version: "0.8.20",
version: "0.8.23",
settings: {
optimizer: {
enabled: true
Expand Down
Loading

0 comments on commit 98d231c

Please sign in to comment.