Skip to content

Commit 20fc3c5

Browse files
authored
Merge pull request #138 from semaphore-protocol/feat/custom-nullifier-hashes
Custom nullifier hashes Former-commit-id: 506fa0a
2 parents 0c9c0c9 + 7329ca6 commit 20fc3c5

File tree

8 files changed

+58
-52
lines changed

8 files changed

+58
-52
lines changed

packages/contracts/contracts/Semaphore.sol

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,13 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
1111
/// @dev Gets a tree depth and returns its verifier address.
1212
mapping(uint256 => IVerifier) public verifiers;
1313

14-
/// @dev Gets a group id and returns the group admin address.
15-
mapping(uint256 => address) public groupAdmins;
16-
17-
/// @dev Gets a group id and returns data to check if a Merkle root is expired.
18-
mapping(uint256 => MerkleTreeExpiry) public merkleTreeExpiries;
14+
/// @dev Gets a group id and returns the group parameters.
15+
mapping(uint256 => Group) public groups;
1916

2017
/// @dev Checks if the group admin is the transaction sender.
2118
/// @param groupId: Id of the group.
2219
modifier onlyGroupAdmin(uint256 groupId) {
23-
if (groupAdmins[groupId] != _msgSender()) {
20+
if (groups[groupId].admin != _msgSender()) {
2421
revert Semaphore__CallerIsNotTheGroupAdmin();
2522
}
2623
_;
@@ -56,8 +53,8 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
5653
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
5754
_createGroup(groupId, merkleTreeDepth, zeroValue);
5855

59-
groupAdmins[groupId] = admin;
60-
merkleTreeExpiries[groupId].rootDuration = 1 hours;
56+
groups[groupId].admin = admin;
57+
groups[groupId].merkleRootDuration = 1 hours;
6158

6259
emit GroupAdminUpdated(groupId, address(0), admin);
6360
}
@@ -72,15 +69,15 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
7269
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
7370
_createGroup(groupId, merkleTreeDepth, zeroValue);
7471

75-
groupAdmins[groupId] = admin;
76-
merkleTreeExpiries[groupId].rootDuration = merkleTreeRootDuration;
72+
groups[groupId].admin = admin;
73+
groups[groupId].merkleRootDuration = merkleTreeRootDuration;
7774

7875
emit GroupAdminUpdated(groupId, address(0), admin);
7976
}
8077

8178
/// @dev See {ISemaphore-updateGroupAdmin}.
8279
function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) {
83-
groupAdmins[groupId] = newAdmin;
80+
groups[groupId].admin = newAdmin;
8481

8582
emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
8683
}
@@ -91,7 +88,7 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
9188

9289
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
9390

94-
merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
91+
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
9592
}
9693

9794
/// @dev See {ISemaphore-addMembers}.
@@ -110,7 +107,7 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
110107

111108
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
112109

113-
merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
110+
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
114111
}
115112

116113
/// @dev See {ISemaphore-updateMember}.
@@ -150,25 +147,29 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
150147
}
151148

152149
if (merkleTreeRoot != currentMerkleTreeRoot) {
153-
uint256 rootCreationDate = merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot];
154-
uint256 rootDuration = merkleTreeExpiries[groupId].rootDuration;
150+
uint256 merkleRootCreationDate = groups[groupId].merkleRootCreationDates[merkleTreeRoot];
151+
uint256 merkleRootDuration = groups[groupId].merkleRootDuration;
155152

156-
if (rootCreationDate == 0) {
153+
if (merkleRootCreationDate == 0) {
157154
revert Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
158155
}
159156

160-
if (block.timestamp > rootCreationDate + rootDuration) {
157+
if (block.timestamp > merkleRootCreationDate + merkleRootDuration) {
161158
revert Semaphore__MerkleTreeRootIsExpired();
162159
}
163160
}
164161

162+
if (groups[groupId].nullifierHashes[nullifierHash]) {
163+
revert Semaphore__YouAreUsingTheSameNillifierTwice();
164+
}
165+
165166
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
166167

167168
IVerifier verifier = verifiers[merkleTreeDepth];
168169

169170
_verifyProof(signal, merkleTreeRoot, nullifierHash, externalNullifier, proof, verifier);
170171

171-
_saveNullifierHash(uint256(keccak256(abi.encodePacked(groupId, nullifierHash))));
172+
groups[groupId].nullifierHashes[nullifierHash] = true;
172173

173174
emit ProofVerified(groupId, merkleTreeRoot, nullifierHash, externalNullifier, signal);
174175
}

packages/contracts/contracts/base/SemaphoreCore.sol

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import "../interfaces/IVerifier.sol";
1010
/// nullifier to prevent double-signaling. External nullifier and Merkle trees (i.e. groups) must be
1111
/// managed externally.
1212
contract SemaphoreCore is ISemaphoreCore {
13-
/// @dev Gets a nullifier hash and returns true or false.
14-
/// It is used to prevent double-signaling.
15-
mapping(uint256 => bool) internal nullifierHashes;
16-
1713
/// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid.
1814
/// Otherwise it reverts.
1915
/// @param signal: Semaphore signal.
@@ -30,10 +26,6 @@ contract SemaphoreCore is ISemaphoreCore {
3026
uint256[8] calldata proof,
3127
IVerifier verifier
3228
) internal view {
33-
if (nullifierHashes[nullifierHash]) {
34-
revert Semaphore__YouAreUsingTheSameNillifierTwice();
35-
}
36-
3729
uint256 signalHash = _hashSignal(signal);
3830

3931
verifier.verifyProof(
@@ -44,14 +36,6 @@ contract SemaphoreCore is ISemaphoreCore {
4436
);
4537
}
4638

47-
/// @dev Stores the nullifier hash to prevent double-signaling.
48-
/// Attention! Remember to call it when you verify a proof if you
49-
/// need to prevent double-signaling.
50-
/// @param nullifierHash: Semaphore nullifier hash.
51-
function _saveNullifierHash(uint256 nullifierHash) internal {
52-
nullifierHashes[nullifierHash] = true;
53-
}
54-
5539
/// @dev Creates a keccak256 hash of the signal.
5640
/// @param signal: Semaphore signal.
5741
/// @return Hash of the signal.

packages/contracts/contracts/base/SemaphoreGroups.sol

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import "@openzeppelin/contracts/utils/Context.sol";
1212
abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
1313
using IncrementalBinaryTree for IncrementalTreeData;
1414

15-
/// @dev Gets a group id and returns the group/tree data.
16-
mapping(uint256 => IncrementalTreeData) internal groups;
15+
/// @dev Gets a group id and returns the tree data.
16+
mapping(uint256 => IncrementalTreeData) internal merkleTree;
1717

1818
/// @dev Creates a new group by initializing the associated tree.
1919
/// @param groupId: Id of the group.
@@ -32,7 +32,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
3232
revert Semaphore__GroupAlreadyExists();
3333
}
3434

35-
groups[groupId].init(merkleTreeDepth, zeroValue);
35+
merkleTree[groupId].init(merkleTreeDepth, zeroValue);
3636

3737
emit GroupCreated(groupId, merkleTreeDepth, zeroValue);
3838
}
@@ -45,7 +45,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
4545
revert Semaphore__GroupDoesNotExist();
4646
}
4747

48-
groups[groupId].insert(identityCommitment);
48+
merkleTree[groupId].insert(identityCommitment);
4949

5050
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
5151
uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1;
@@ -71,7 +71,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
7171
revert Semaphore__GroupDoesNotExist();
7272
}
7373

74-
groups[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
74+
merkleTree[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
7575

7676
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
7777
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
@@ -95,7 +95,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
9595
revert Semaphore__GroupDoesNotExist();
9696
}
9797

98-
groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
98+
merkleTree[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
9999

100100
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
101101
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
@@ -105,17 +105,17 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
105105

106106
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
107107
function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) {
108-
return groups[groupId].root;
108+
return merkleTree[groupId].root;
109109
}
110110

111111
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
112112
function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) {
113-
return groups[groupId].depth;
113+
return merkleTree[groupId].depth;
114114
}
115115

116116
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
117117
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
118-
return groups[groupId].numberOfLeaves;
118+
return merkleTree[groupId].numberOfLeaves;
119119
}
120120

121121
/// @dev Converts the path indices of a Merkle proof to the identity commitment index in the tree.

packages/contracts/contracts/extensions/SemaphoreVoting.sol

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
1414
/// @dev Gets a poll id and returns the poll data.
1515
mapping(uint256 => Poll) internal polls;
1616

17+
/// @dev Gets a nullifier hash and returns true or false.
18+
/// It is used to prevent double-voting.
19+
mapping(uint256 => bool) internal nullifierHashes;
20+
1721
/// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs.
1822
/// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth).
1923
constructor(Verifier[] memory _verifiers) {
@@ -90,15 +94,18 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
9094
revert Semaphore__PollIsNotOngoing();
9195
}
9296

97+
if (nullifierHashes[nullifierHash]) {
98+
revert Semaphore__YouAreUsingTheSameNillifierTwice();
99+
}
100+
93101
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
94102
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);
95103

96104
IVerifier verifier = verifiers[merkleTreeDepth];
97105

98106
_verifyProof(vote, merkleTreeRoot, nullifierHash, pollId, proof, verifier);
99107

100-
// Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)).
101-
_saveNullifierHash(nullifierHash);
108+
nullifierHashes[nullifierHash] = true;
102109

103110
emit VoteAdded(pollId, vote);
104111
}

packages/contracts/contracts/interfaces/ISemaphore.sol

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ interface ISemaphore {
88
error Semaphore__MerkleTreeDepthIsNotSupported();
99
error Semaphore__MerkleTreeRootIsExpired();
1010
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
11+
error Semaphore__YouAreUsingTheSameNillifierTwice();
1112

1213
struct Verifier {
1314
address contractAddress;
1415
uint256 merkleTreeDepth;
1516
}
1617

17-
/// It defines all the parameters needed to check whether a
18-
/// zero-knowledge proof generated with a certain Merkle tree is still valid.
19-
struct MerkleTreeExpiry {
20-
uint256 rootDuration;
21-
mapping(uint256 => uint256) rootCreationDates;
18+
/// It defines all the group parameters, in addition to those in the Merkle tree.
19+
struct Group {
20+
address admin;
21+
uint256 merkleRootDuration;
22+
mapping(uint256 => uint256) merkleRootCreationDates;
23+
mapping(uint256 => bool) nullifierHashes;
2224
}
2325

2426
/// @dev Emitted when an admin is assigned to a group.

packages/contracts/contracts/interfaces/ISemaphoreCore.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ pragma solidity 0.8.4;
44
/// @title SemaphoreCore interface.
55
/// @dev Interface of SemaphoreCore contract.
66
interface ISemaphoreCore {
7-
error Semaphore__YouAreUsingTheSameNillifierTwice();
8-
97
/// @notice Emitted when a proof is verified correctly and a new nullifier hash is added.
108
/// @param nullifierHash: Hash of external and identity nullifiers.
119
event NullifierHashAdded(uint256 nullifierHash);

packages/contracts/contracts/interfaces/ISemaphoreVoting.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface ISemaphoreVoting {
88
error Semaphore__MerkleTreeDepthIsNotSupported();
99
error Semaphore__PollHasAlreadyBeenStarted();
1010
error Semaphore__PollIsNotOngoing();
11+
error Semaphore__YouAreUsingTheSameNillifierTwice();
1112

1213
enum PollState {
1314
Created,

packages/contracts/test/Semaphore.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,19 @@ describe("Semaphore", () => {
252252
)
253253
})
254254

255+
it("Should not verify the same proof for an onchain group twice", async () => {
256+
const transaction = contract.verifyProof(
257+
groupId,
258+
group.root,
259+
signal,
260+
fullProof.publicSignals.nullifierHash,
261+
fullProof.publicSignals.merkleRoot,
262+
solidityProof
263+
)
264+
265+
await expect(transaction).to.be.revertedWith("Semaphore__YouAreUsingTheSameNillifierTwice()")
266+
})
267+
255268
it("Should not verify a proof if the Merkle tree root is expired", async () => {
256269
const groupId = 2
257270
const group = new Group(treeDepth)

0 commit comments

Comments
 (0)