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
: Thezkey
andwasm
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 thesemaphore.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:
signal
: The Semaphore signal to prove.root
: The root of the Merkle tree.nullifierHash
: a nullifier hash.externalNullifier
: The external nullifier.proof
: A Solidity-compatible Semaphore proof.verifier
: The verifier address.
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