ROOTVIELROOTVIEL
← Academy

Module 13 · Zero-Knowledge identity & private protocols

Semaphore: anonymous membership and signalling

Semaphore is a widely-used zero-knowledge protocol that lets a member of a group prove membership and broadcast a signal without revealing which member they are, while preventing them from signalling twice in the same context. A user generates an identity from two secrets and publishes an identity commitment, which an admin adds to a Merkle tree; the tree root names the group. To signal, the user proves in zero knowledge that their commitment is in the tree and publishes a nullifier derived from the identity and an external nullifier (the context). Repeating a signal in the same context reuses the nullifier and is rejected, so "one member, one vote per proposal" is enforced anonymously. It is the identity analogue of the privacy-pool pattern, and the same honesty applies: the proof certifies membership and single-use, not the truth or safety of what was signalled.

Learning objective

Understand the Semaphore protocol as a reusable "anonymous membership + one-signal-per-context" primitive: how an identity commitment joins a Merkle group, how a signal proves group membership in zero knowledge, and how the external nullifier bounds a member to one action per context — enabling anonymous voting, rate-limiting and whistleblowing. Then draw the honest boundary: a valid Semaphore proof attests to membership and single-use within a context, not to the honesty of the signal or the soundness of the group.

Track
Research
Module
13 · Zero-Knowledge identity & private protocols
Prerequisites
Merkle trees · Nullifiers, Merkle membership and the privacy-pool pattern · What is a zero-knowledge proof

Conceptual introduction

Many systems need the same awkward combination: let a member of a known group act, keep which member secret, but stop any member acting twice. Anonymous voting needs "one member, one vote, nobody sees who voted for what". Rate-limiting an anonymous forum needs "one post per member per hour". Whistleblowing needs "a real employee is speaking, but not which one". Semaphore packages exactly this combination as a reusable primitive so applications do not reinvent it.

Structurally it is the privacy-pool pattern from the previous module pointed at identity instead of money: identity commitments are leaves in a Merkle tree, membership is proven in zero knowledge, and a nullifier prevents reuse. The twist that makes it general is the external nullifier, which scopes the "one action" rule to a context, so the same member can act once per proposal, per hour, or per topic.

Note

Semaphore proves membership in a group and single-use within a context. It does not vouch for who is in the group — that is the group admin's responsibility — nor for the content of the signal. A valid proof from a Sybil-stuffed group faithfully attests membership in a worthless group.

Identity and group membership

A Semaphore identity is generated from two secrets — a trapdoor and a nullifier secret — hashed into an identity, and then into a public identity commitment. The commitment, not the identity, is what goes public.

synthetic — Semaphore identity and group registration (illustrative)
# Identity generation (user, private) trapdoor, nullifier_secret = random(), random() identity = Poseidon(nullifier_secret, trapdoor) commitment = Poseidon(identity) # PUBLIC identity commitment # Group registration (admin) tree.insert(commitment) # leaf in the group Merkle tree group_root = tree.root # the root NAMES the group

Anyone can verify that a given root corresponds to a set of commitments, but the mapping from a commitment back to a person is outside the protocol — it is whatever vetting the admin did when adding members. The root is the group's public identity; proving against it proves "I am one of the members behind this root".

Signalling and the external nullifier

To signal, the member computes a nullifier from their identity and an external nullifier — a public value naming the context — then proves in zero knowledge that their commitment is in the tree and that the nullifier was correctly derived. They publish the nullifier, the signal, and the proof.

synthetic — Semaphore signal and double-signal detection (illustrative)
# External nullifier = the context (proposal id, time window, topic) external_nullifier = hash("proposal_42") # Nullifier binds identity to context nf = Poseidon(external_nullifier, identity) # ZK proof (private: trapdoor, nullifier_secret, Merkle path; # public: group_root, external_nullifier, nf, signalHash) # proves: commitment = Poseidon(Poseidon(nf_secret, trapdoor)) is in the tree # AND nf = Poseidon(external_nullifier, identity) publish(nf, signal, proof) # Verifier / contract assert valid(proof, group_root, external_nullifier, nf, hash(signal)) assert nf not in used_nullifiers[external_nullifier] # one signal per context used_nullifiers[external_nullifier].add(nf)

The external nullifier is the design hinge. Because the nullifier depends on both the identity and the context, the same member produces a different nullifier in each context — so they can signal once per proposal, per hour, per topic — but cannot signal twice in the same context without colliding on the nullifier and being rejected. Change the external nullifier and the same members get a fresh single-use budget.

Anonymity
The proof reveals membership in the group but not which member signalled.
Double-signal resistance
A repeated signal in the same context reuses the nullifier and is rejected.
External nullifier (context)
A public value scoping the single-use rule; different contexts give the same member independent one-time actions.
Signal binding
The signal (e.g. the vote) is hashed into the proven statement, so the proof is bound to this signal and cannot be replayed to attach a different one.

Applications and their failure modes

The same primitive maps onto several applications just by choosing the group and the external nullifier.

  • ▸Anonymous voting: external nullifier = proposal id, signal = the vote. One vote per member per proposal, nobody sees who voted how.
  • ▸Anonymous whistleblowing / signalling: group = verified employees, signal = the disclosure. Proves a real insider is speaking without revealing which.
  • ▸Rate limiting: external nullifier = a time window, so each member gets one anonymous action per window — spam resistance without identification.
  • ▸Proof-of-humanity-style gating: group = a set vetted as unique humans, used to gate an action to "one real person, once" — only as strong as the vetting.

The failure modes are instructive and mostly live outside the cryptography. If the admin admits Sybil identities, the "one member, one vote" guarantee is cryptographically perfect over a group that does not mean what you think. If an application reuses the same external nullifier across contexts that should be independent, a member's single-use budget leaks between them. And if the signal is not bound into the proof, a relayer can swap it. The cryptography enforces membership-and-single-use; the meaning of the group and the context is an application responsibility.

Note

Semaphore's anonymity is only as large as the group. In a group of three, a signal narrows the author to one of three; the anonymity set is the membership count, exactly as the privacy-pool anonymity set is the deposit count. A proof over a tiny or Sybil-controlled group hides little.

Reading the guarantee honestly (RootRaven)

A verified Semaphore proof establishes a precise statement: the signaller is one of the members behind this group root, the nullifier is correctly derived from their identity and this context, this is their first signal in this context, and the proof is bound to this signal — all under the circuit and the commitment scheme's assumptions. That is exactly what anonymous one-member-one-action needs.

It does not establish that the member is a distinct real person (that is the admission policy and any proof-of-humanity vetting), that the group is free of Sybils, that the signal is truthful or safe, or that the context was scoped correctly. For RootRaven the lesson mirrors the privacy-pool one: the proof is evidence of membership-and-single-use under stated assumptions, not evidence that the group means what it claims or that the signalled decision is correct. Report the group's admission policy and size as separate evidence from the proof itself.

Exercise

Note

A DAO uses Semaphore for anonymous governance: group = token holders, external nullifier = proposal id, signal = the vote. A proposal passes, then two problems surface — an auditor finds the admin had quietly added 400 identity commitments they controlled, and a developer admits the UI reused the external nullifier "governance" for every proposal instead of a per-proposal value. Explain the impact of each, say which is a protocol-soundness failure and which is a usage/governance failure, and give the fix.

Expected direction: Neither is a Semaphore protocol-soundness failure — the proofs are cryptographically valid. (1) The admin adding 400 self-controlled commitments is a Sybil / admission-policy failure: Semaphore faithfully enforces "one vote per member per proposal", but over a group that has been stuffed, so the attacker casts 400 valid anonymous votes and the "one member one vote" guarantee is meaningless. The guarantee is only as good as who is admitted; the fix is a sound, auditable admission policy (and ideally proof-of-humanity-style uniqueness vetting), not a change to Semaphore. (2) Reusing the external nullifier "governance" across all proposals is a usage failure: the single-use budget is scoped to the context, so with one shared context a member can vote once ever, not once per proposal — meaning only their first-ever vote counts and later proposals silently reject them (or, depending on logic, cross-proposal interference). The fix is to use a distinct external nullifier per proposal (e.g. the proposal id) so each proposal gives members a fresh single-use budget. Honest reading: the proof attests membership and single-use within a context; it does not vouch for the group's integrity or that the context was scoped correctly.

Check your understanding

What two things does a Semaphore signal prove, and what stays hidden?
It proves the signaller's identity commitment is in the group's Merkle tree (membership) and that the published nullifier is correctly derived from their identity and the external nullifier (enabling single-use). Which member signalled stays hidden — the anonymity set is the whole group.
What is the external nullifier and what does it control?
A public value naming the context (a proposal id, a time window, a topic). The nullifier depends on both the identity and the external nullifier, so a member gets one action per context: they cannot signal twice in the same context, but different contexts give independent single-use budgets.
How is Semaphore structurally the same as the privacy-pool pattern?
Both use commitments as Merkle-tree leaves, prove membership in zero knowledge against the root, and publish a nullifier to prevent reuse. Semaphore applies it to identity/signalling (identity commitments, one signal per context) rather than to funds (deposits, one withdrawal per note).
Why can a cryptographically valid Semaphore vote still be worthless, and whose responsibility is that?
Because anonymity and single-use hold only over the group as admitted: if the admin added Sybil identities, each cast vote is a valid proof of membership in a stuffed group, so "one member one vote" means nothing. The integrity of the group (admission policy, Sybil/uniqueness vetting) and the size of the anonymity set are application/governance responsibilities, separate from the proof.

References

  1. [1]Banco ZK — Vol. III (2025), Chapter 1, Semaphore protocol and applications, non-membership proofs — external educational reference
  2. [2]Semaphore — protocol documentation (identity commitments in a Merkle tree, external nullifier, signalling)
  3. [3]Goldwasser, Micali & Rackoff — The Knowledge Complexity of Interactive Proof Systems (1985)

References point to public, well-known technical sources for further study.

Related articles