Skip to main content
Version: V4-beta

Semaphore proofs

Once a user joins a Semaphore group with their Semaphore identity, the user can send their anonymous message with a zero-knowledge proof that proves the following:

  • the user is a member of the group,
  • the same user created the message and the proof.

A unique nullifier is also generated for each proof that can be used to check whether that proof has already been validated.

Install package​

In your code, use the @semaphore-protocol/proof package to generate and verify a proof.

npm install @semaphore-protocol/proof

Semaphore also provides @semaphore-protocol/core, which includes the functions of the following core packages: @semaphore-protocol/identity, @semaphore-protocol/group, @semaphore-protocol/proof.

Generate a proof​

1. Create the identity​

In order for a user to generate a proof, it is necessary to create a Semaphore identity. If you do not know how to create an identity, see the previous guide on identities.

2. Create the group​

Before generating a proof you also need to create a Semaphore group containing the commitment of the Semaphore identity of the user who will generate the proof. If you do not know how to create a group, see the previous guide on groups.

If your group is on-chain, you can use the @semaphore-protocol/data library to fetch the group members and re-create the off-chain group. For example:

import { SemaphoreSubgraph } from "@semaphore-protocol/data"
import { Group } from "@semaphore-protocol/group"

const semaphoreSubgraph = new SemaphoreSubgraph("sepolia")

const { members } = await semaphoreSubgraph.getGroup("42", { members: true })

const group = new Group(members)

3. Choose the scope​

Each proof requires a scope, on which each user may only generate one valid proof. The scope, together with the user's private key, is used to generate the nullifier, which is the value you can actually use to check whether a proof with that scope has already been generated by that user. In a voting application where double-voting must be prevented, the scope could be the ballot id, or the Merkle root of the group.

4. Generate the anomymous message​

Finally, you can generate the proof with the anonymous message using the generateProof function. For example:

import { generateProof } from "@semaphore-protocol/proof"

const scope = group.root
const message = 1

const proof = await generateProof(identity, group, message, scope)

Verify a proof​

To verify a proof, pass the proof you generated to the verifyProof function. For example:

import { verifyProof } from "@semaphore-protocol/proof"

await verifyProof(proof) // true or false.

If you want to validate a proof on-chain, you can use @semaphore-protocol/contracts and the Semaphore.sol contract, as explained in the previous guide, and use the validateProof function. For example:

function validateProof(ISemaphore.SemaphoreProof calldata proof) external {
semaphore.validateProof(groupId, proof);