Skip to main content
Version: V2

Semaphore proofs

Learn how to use Semaphore to generate and verify zero-knowledge proofs.

Once a user joins their Semaphore identity to a Semaphore group, the user can signal anonymously with a zero-knowledge proof that proves the following:

  • The user is a member of the group.
  • The same user created the signal and the proof.

Developers can use Semaphore for the following:

Generate a proof off-chain

Use the @semaphore-protocol/proof library to generate an off-chain proof. To generate a proof, pass the following properties to the generateProof function:

  • identity: The Semaphore identity of the user broadcasting the signal and generating the proof.
  • group: The group to which the user belongs.
  • externalNullifier: The value that prevents double-signaling.
  • signal: The signal the user wants to send anonymously.
  • snarkArtifacts: The zkey and wasm trusted setup files.

In the voting system use case, once all the voters have joined their identities to the ballot group, a voter can generate a proof to vote for a proposal. In the call to generateProof, the voting system passes the unique ballot ID (the Merkle tree root of the group) as the externalNullifier to prevent the voter signaling more than once for the ballot. The following code sample shows how to use generateProof to generate the voting proof:

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

const externalNullifier = group.root
const signal = "proposal_1"

const fullProof = await generateProof(identity, group, externalNullifier, signal, {
zkeyFilePath: "./semaphore.zkey",
wasmFilePath: "./semaphore.wasm"

Verify a proof off-chain

Use the @semaphore-protocol/proof library to verify a Semaphore proof off-chain. To verify a proof, pass the following to the verifyProof function:

  • proof: the Semaphore proof.
  • verificationKey: the JavaScript object in the semaphore.json trusted setup file.

The following code sample shows how to parse the verification key object from semaphore.json and verify the previously generated proof:

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

const verificationKey = JSON.parse(fs.readFileSync("./semaphore.json", "utf-8"))

await verifyProof(verificationKey, fullProof) // true or false.

verifyProof returns a Promise that resolves to true or false.

Verify a proof on-chain

Use the SemaphoreCore contract to verify proofs on-chain. It uses a verifier deployed to Ethereum and provides methods hash the signal and verify a proof.


You can import SemaphoreCore and other Semaphore contracts from the @semaphore-protocol/contracts NPM module.

To verify Semaphore proofs in your contract, import SemaphoreCore and pass the following to the _verifyProof internal method:

Remember to save the nullifierHash on-chain to avoid double-signaling.

Alternatively, you can use an already deployed Semaphore contract and use its verifyProof external function.

Generate a Solidity-compatible proof

To transform a proof to be compatible with Solidity contracts, pass the proof to the packToSolidityProof utility function--for example:

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

const solidityProof = packToSolidityProof(fullProof.proof)

Semaphore returns a new Solidity-compatible instance of the proof.

Retrieve a nullifier hash

To get the Semaphore proof nullifier hash, access the proof's publicSignals.nullifierHash property--for example:

const { nullifierHash } = fullProof.publicSignals