The Semaphore contract forms a base layer for other contracts to create applications that rely on anonymous signaling.

First, you should ensure that the proving key, verification key, and circuit file, which are static, be easily available to your users. These may be hosted in a CDN or bundled with your application code.

The Semaphore team has not performed a trusted setup yet, so trustworthy versions of these files are not available yet.

Untrusted versions of these files, however, may be obtained via the circuits/scripts/ script.

Next, to have full flexibility over Semaphore's mechanisms, write a Client contract and set the owner of the Semaphore contract as the address of the Client contract. You may also write a Client contract which deploys a Semaphore contract in its constructor, or on the fly.

With the Client contract as the owner of the Semaphore contract, the Client contract may call owner-only Semaphore functions such as addExternalNullifier().

Add, deactivate, or reactivate external nullifiiers

These functions add, deactivate, and reactivate an external nullifier respectively. As each identity can only signal once to an external nullifier, and as a signal can only be successfully broadcasted to an active external nullifier, these functions enable use cases where it is necessary to have multiple external nullifiers or to activate and/or deactivate them.

Refer to the high-level explanation of Semaphore for more details.

Set broadcast permissioning

Note that Semaphore.broadcastSignal() is permissioned by default, so if you wish for anyone to be able to broadcast a signal, the owner of the Semaphore contract (either a Client contract or externally owned account) must first invoke setPermissioning(false).

See SemaphoreClient.sol for an example.

Insert identities

To generate an identity commitment, use the libsemaphore functions genIdentity() and genIdentityCommitment() Typescript (or Javascript) functions:

const identity: Identity = genIdentity()
const identityCommitment = genIdentityCommitment(identity)

Be sure to store identity somewhere safe. The serialiseIdentity() function can help with this:

const serialisedId: string = serialiseIdentity(identity: Identity)

It converts an Identity into a JSON string which looks like this:


To convert this string back into an Identity, use unSerialiseIdentity().

const id: Identity = unSerialiseIdentity(serialisedId)

Broadcast signals

First obtain the leaves of the identity tree (in sequence, up to the user's identity commitment, or more).

const leaves = <list of leaves>

Next, load the circuit from disk (or from a remote source):

const circuitPath = path.join(__dirname, '/path/to/circuit.json')
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
const circuit = genCircuit(cirDef)

Next, use libsemaphore's genWitness() helper function as such:

const result = await genWitness(
  • signal: a string which is the signal to broadcast.
  • circuit: the output of genCircuit() (see above).
  • identity: the user's identity as an Identity object.
  • leaves the list of leaves in the tree (see above).
  • num_levels: the depth of the Merkle tree.
  • external_nullifier: the external nullifier at which to broadcast.

Load the proving key from disk (or from a remote source):

const provingKeyPath = path.join(__dirname, '/path/to/proving_key.bin')
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)

Generate the proof (this takes about 30-45 seconds on a modern laptop):

const proof = await genProof(result.witness, provingKey)

Generate the broadcastSignal() parameters:

const publicSignals = genPublicSignals(result.witness, circuit)
const params = genBroadcastSignalParams(result, proof, publicSignals)

Finally, invoke broadcastSignal() with the parameters:

const tx = await semaphoreClientContract.broadcastSignal(
    { gasLimit: 500000 },