diff --git a/packages/contracts/contracts/Semaphore.sol b/packages/contracts/contracts/Semaphore.sol index 539391688..308408353 100644 --- a/packages/contracts/contracts/Semaphore.sol +++ b/packages/contracts/contracts/Semaphore.sol @@ -100,8 +100,7 @@ contract Semaphore is ISemaphore, SemaphoreGroups { groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp; } - /// @dev See {ISemaphore-verifyProof}. - function verifyProof( + function validateProof( uint256 groupId, uint256 merkleTreeRoot, uint256 nullifier, @@ -109,6 +108,27 @@ contract Semaphore is ISemaphore, SemaphoreGroups { uint256 scope, uint256[8] calldata proof ) external override onlyExistingGroup(groupId) { + if (groups[groupId].nullifiers[nullifier]) { + revert Semaphore__YouAreUsingTheSameNullifierTwice(); + } + + if (!verifyProof(groupId, merkleTreeRoot, nullifier, message, scope, proof)) { + revert Semaphore__InvalidProof(); + } + + groups[groupId].nullifiers[nullifier] = true; + + emit ProofValidated(groupId, merkleTreeRoot, nullifier, message, scope, proof); + } + + function verifyProof( + uint256 groupId, + uint256 merkleTreeRoot, + uint256 nullifier, + uint256 message, + uint256 scope, + uint256[8] calldata proof + ) public view override onlyExistingGroup(groupId) returns (bool) { uint256 merkleTreeSize = getMerkleTreeSize(groupId); if (merkleTreeSize == 0) { @@ -132,24 +152,13 @@ contract Semaphore is ISemaphore, SemaphoreGroups { } } - if (groups[groupId].nullifiers[nullifier]) { - revert Semaphore__YouAreUsingTheSameNullifierTwice(); - } - - if ( - !verifier.verifyProof( + return + verifier.verifyProof( [proof[0], proof[1]], [[proof[2], proof[3]], [proof[4], proof[5]]], [proof[6], proof[7]], [merkleTreeRoot, nullifier, _hash(message), _hash(scope)] - ) - ) { - revert Semaphore__InvalidProof(); - } - - groups[groupId].nullifiers[nullifier] = true; - - emit ProofVerified(groupId, merkleTreeRoot, nullifier, message, scope, proof); + ); } /// @dev Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. diff --git a/packages/contracts/contracts/interfaces/ISemaphore.sol b/packages/contracts/contracts/interfaces/ISemaphore.sol index 7e16a5039..50de51b13 100644 --- a/packages/contracts/contracts/interfaces/ISemaphore.sol +++ b/packages/contracts/contracts/interfaces/ISemaphore.sol @@ -27,14 +27,14 @@ interface ISemaphore { uint256 newMerkleTreeDuration ); - /// @dev Emitted when a Semaphore proof is verified. + /// @dev Emitted when a Semaphore proof is validated. /// @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. - event ProofVerified( + event ProofValidated( uint256 indexed groupId, uint256 indexed merkleTreeRoot, uint256 nullifier, @@ -85,7 +85,7 @@ interface ISemaphore { /// @param message: Semaphore message. /// @param scope: Scope. /// @param proof: Zero-knowledge proof. - function verifyProof( + function validateProof( uint256 groupId, uint256 merkleTreeRoot, uint256 nullifier, @@ -93,4 +93,20 @@ interface ISemaphore { uint256 scope, uint256[8] calldata proof ) external; + + /// @dev Verifies a zero-knowledge proof by returning true or false. + /// @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 view returns (bool); } diff --git a/packages/contracts/test/Semaphore.ts b/packages/contracts/test/Semaphore.ts index 44305979b..980663857 100644 --- a/packages/contracts/test/Semaphore.ts +++ b/packages/contracts/test/Semaphore.ts @@ -205,6 +205,78 @@ describe("Semaphore", () => { }) describe("# verifyProof", () => { + const groupId = 10 + const message = 2 + const identity = new Identity("0") + + const group = new Group() + + group.addMembers(members) + + let fullProof: SemaphoreProof + + before(async () => { + await semaphoreContract["createGroup(uint256,address)"](groupId, accounts[0]) + + await semaphoreContract.addMembers(groupId, members) + + fullProof = await generateProof(identity, group, message, group.root as string, 10) + }) + + it("Should not verify a proof if the group does not exist", async () => { + const transaction = semaphoreContract.verifyProof(11, 1, 0, message, 0, [0, 0, 0, 0, 0, 0, 0, 0]) + + await expect(transaction).to.be.revertedWithCustomError(semaphoreContract, "Semaphore__GroupDoesNotExist") + }) + + it("Should not verify a proof if the Merkle tree root is not part of the group", async () => { + const transaction = semaphoreContract.verifyProof(groupId, 1, 0, message, 0, [0, 0, 0, 0, 0, 0, 0, 0]) + + await expect(transaction).to.be.revertedWithCustomError( + semaphoreContract, + "Semaphore__MerkleTreeRootIsNotPartOfTheGroup" + ) + }) + + it("Should verify a proof for an onchain group", async () => { + const validProof = await semaphoreContract.verifyProof( + groupId, + fullProof.merkleRoot, + fullProof.nullifier, + fullProof.message, + fullProof.merkleRoot, + fullProof.proof + ) + + expect(validProof).to.equal(true) + }) + + it("Should not verify a proof if the Merkle tree root is expired", async () => { + const groupId = 2 + + const group = new Group() + + group.addMembers([members[0], members[1]]) + + const fullProof = await generateProof(identity, group, message, group.root as string, 10) + + const transaction = semaphoreContract.verifyProof( + groupId, + fullProof.merkleRoot, + fullProof.nullifier, + fullProof.message, + fullProof.merkleRoot, + fullProof.proof + ) + + await expect(transaction).to.be.revertedWithCustomError( + semaphoreContract, + "Semaphore__MerkleTreeRootIsExpired" + ) + }) + }) + + describe("# validateProof", () => { const message = 2 const identity = new Identity("0") const groupOneMemberId = 6 @@ -234,23 +306,8 @@ describe("Semaphore", () => { ) }) - it("Should not verify a proof if the group does not exist", async () => { - const transaction = semaphoreContract.verifyProof(10, 1, 0, message, 0, [0, 0, 0, 0, 0, 0, 0, 0]) - - await expect(transaction).to.be.revertedWithCustomError(semaphoreContract, "Semaphore__GroupDoesNotExist") - }) - - it("Should not verify a proof if the Merkle tree root is not part of the group", async () => { - const transaction = semaphoreContract.verifyProof(2, 1, 0, message, 0, [0, 0, 0, 0, 0, 0, 0, 0]) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreContract, - "Semaphore__MerkleTreeRootIsNotPartOfTheGroup" - ) - }) - it("Should throw an exception if the proof is not valid", async () => { - const transaction = semaphoreContract.verifyProof( + const transaction = semaphoreContract.validateProof( groupId, fullProof.merkleRoot, fullProof.nullifier, @@ -262,8 +319,8 @@ describe("Semaphore", () => { await expect(transaction).to.be.revertedWithCustomError(semaphoreContract, "Semaphore__InvalidProof") }) - it("Should verify a proof for an onchain group with one member correctly", async () => { - const transaction = semaphoreContract.verifyProof( + it("Should validate a proof for an onchain group with one member correctly", async () => { + const transaction = semaphoreContract.validateProof( groupOneMemberId, fullProofOneMember.merkleRoot, fullProofOneMember.nullifier, @@ -273,7 +330,7 @@ describe("Semaphore", () => { ) await expect(transaction) - .to.emit(semaphoreContract, "ProofVerified") + .to.emit(semaphoreContract, "ProofValidated") .withArgs( groupOneMemberId, fullProofOneMember.merkleRoot, @@ -284,8 +341,8 @@ describe("Semaphore", () => { ) }) - it("Should verify a proof for an onchain group with more than one member correctly", async () => { - const transaction = semaphoreContract.verifyProof( + it("Should validate a proof for an onchain group with more than one member correctly", async () => { + const transaction = semaphoreContract.validateProof( groupId, fullProof.merkleRoot, fullProof.nullifier, @@ -295,7 +352,7 @@ describe("Semaphore", () => { ) await expect(transaction) - .to.emit(semaphoreContract, "ProofVerified") + .to.emit(semaphoreContract, "ProofValidated") .withArgs( groupId, fullProof.merkleRoot, @@ -306,8 +363,8 @@ describe("Semaphore", () => { ) }) - it("Should not verify the same proof for an onchain group twice", async () => { - const transaction = semaphoreContract.verifyProof( + it("Should not validate the same proof for an onchain group twice", async () => { + const transaction = semaphoreContract.validateProof( groupId, fullProof.merkleRoot, fullProof.nullifier, @@ -321,28 +378,5 @@ describe("Semaphore", () => { "Semaphore__YouAreUsingTheSameNullifierTwice" ) }) - - it("Should not verify a proof if the Merkle tree root is expired", async () => { - const groupId = 2 - const group = new Group() - - group.addMembers([members[0], members[1]]) - - const fullProof = await generateProof(identity, group, message, group.root as string, 10) - - const transaction = semaphoreContract.verifyProof( - groupId, - fullProof.merkleRoot, - fullProof.nullifier, - fullProof.message, - fullProof.merkleRoot, - fullProof.proof - ) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreContract, - "Semaphore__MerkleTreeRootIsExpired" - ) - }) }) })