Skip to content

New function to verify proofs without nullifiers #547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions packages/contracts/contracts/Semaphore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,35 @@ 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,
uint256 message,
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) {
Expand All @@ -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.
Expand Down
22 changes: 19 additions & 3 deletions packages/contracts/contracts/interfaces/ISemaphore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -85,12 +85,28 @@ interface ISemaphore {
/// @param message: Semaphore message.
/// @param scope: Scope.
/// @param proof: Zero-knowledge proof.
function verifyProof(
function validateProof(
uint256 groupId,
uint256 merkleTreeRoot,
uint256 nullifier,
uint256 message,
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);
}
128 changes: 81 additions & 47 deletions packages/contracts/test/Semaphore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -273,7 +330,7 @@ describe("Semaphore", () => {
)

await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.to.emit(semaphoreContract, "ProofValidated")
.withArgs(
groupOneMemberId,
fullProofOneMember.merkleRoot,
Expand All @@ -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,
Expand All @@ -295,7 +352,7 @@ describe("Semaphore", () => {
)

await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.to.emit(semaphoreContract, "ProofValidated")
.withArgs(
groupId,
fullProof.merkleRoot,
Expand All @@ -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,
Expand All @@ -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"
)
})
})
})