Explainer 02
What is a zero-knowledge proof?
The everyday version
A zero-knowledge proof (ZK proof) is a way to convince someone that a statement is true without revealing why.
Some real-world analogies:
- Bartender check. You prove you are over 18 without showing your name, address, or birth date. You don't even show the date that proves it — just the fact that some valid date exists and you know it.
- Sealed envelope. You hand the bouncer a sealed envelope. A neutral machine on the door reads the envelope, lights up green if it contains a valid 18+ ID, and then shreds the envelope. The bouncer only sees the green light.
- Locked diary. You prove to a friend that page 47 of your diary contains the word "Tehran" without letting them read any other page — or even page 47.
A ZK proof is the cryptographic version of that envelope: a small piece of math, typically a few hundred bytes, that a verifier can check in milliseconds and that reveals only the truth of the claim, nothing else.
What Jomhoor uses ZK proofs for
We use ZK proofs to let a user prove three claims at once, on-chain, without revealing identity:
- "I hold a genuine Iranian travel document or national ID." Proven against the document's cryptographic signature and the official ICAO certificate tree.
- "I have not registered or voted before for this event." Proven via a unique, deterministic value called a nullifier — derived from the document and the event, but unlinkable to identity.
- "My document has not expired and I meet age / citizenship criteria." Proven against birth-date and expiry fields inside the document, without disclosing the actual values.
The on-chain record is just: some valid Iranian citizen, eligible for this vote, has voted "yes" on proposal #42. Nothing more.
The three properties every ZK proof must have
When cryptographers talk about ZK proofs, they always mean a system with three properties. The user-facing meaning of each is what matters:
| Property | Plain English | Why it matters to Jomhoor |
|---|---|---|
| Completeness | If the statement is true, an honest user can always produce a valid proof. | A real Iranian passport holder is never falsely rejected by the smart contract. |
| Soundness | If the statement is false, a cheater cannot produce a valid proof — except with vanishingly small probability (typically 1 in 2¹²⁸). | An attacker cannot register a fake identity by guessing or by computing a "lucky" proof. |
| Zero-knowledge | The proof reveals nothing beyond the truth of the statement — not the inputs, not the intermediate values, not any side information. | The chain never learns passport number, name, date of birth, or which person any given vote belongs to. |
These three together are what makes ZK proofs different from a signature, a hash, or an attestation. A signature proves "I have the key"; a ZK proof proves "I know inputs that satisfy a specific computation" — and only that.
How "soundness" actually works
The 1-in-2¹²⁸ number is not a guess. It comes from how the proof is built:
- The computation we want to verify (e.g. "passport signature is valid AND nullifier was derived correctly AND document is not expired") is compiled into a giant arithmetic circuit — millions of additions and multiplications over a prime field.
- The prover commits to every intermediate value of that computation using elliptic-curve points.
- The verifier issues a random challenge derived deterministically from those commitments.
- The prover must respond with values that satisfy a single algebraic identity tying the commitments and the challenge together.
A cheater would have to forge values that satisfy that identity by chance. The identity lives over a field with ~2²⁵⁴ elements, so the cheating probability collapses to roughly 1 in 2¹²⁸ even for the strongest known attacks. For context: 2¹²⁸ is more than the number of atoms in a billion human bodies. It is treated as "impossible" by every banking and government cryptography standard.
What "zero-knowledge" actually means
Zero-knowledge does not mean "the data is encrypted somewhere on the chain." It means the data was never put into the proof to begin with.
Compare:
- Encryption. "The message exists but is locked. Someone with the key can read it later."
- Zero-knowledge. "The message was used to compute a proof, then deleted. The proof is mathematically incapable of revealing the message — there is no key, because there is nothing to unlock."
For Jomhoor, this means:
- Your passport number is on your phone only. It enters the proof generator, is consumed by it, and is then discarded.
- The 256-byte proof your phone uploads contains no recoverable trace of it.
- Even if every smart contract, every relayer log, and the entire Rarimo L2 history were exfiltrated forever, no one could recover your passport number from them.
The only way an attacker learns your passport number is by getting it from your physical device — same threat surface as your photos or your WhatsApp messages, and unrelated to anything we publish on-chain.
"The shape of the circuit" — what that phrase means
Throughout these explainers we say things like "the circuit's shape determines what can and cannot be proved." Concretely, the circuit is a fixed program that says:
Given private inputs (passport bytes, signature, secrets) and
public inputs (event ID, current date, ICAO root):
1. Hash the data groups (DG1, DG2, ...) and verify SOD signature.
2. Verify the Document Signer certificate against the ICAO Merkle root.
3. Derive the nullifier = poseidon(secret, event_id).
4. Check that birth_date + 18 years ≤ current_date.
5. Output: nullifier, citizenship, current_date, ICAO root.
Every Jomhoor user's phone runs the same circuit for a given document class. Only the inputs differ. This is what lets one smart contract verify millions of users' proofs with one verification key.
Different document classes (Iranian passport RSA-2048-SHA256, Iranian passport RSA-3072-SHA1, INID RSA-2048, future ECDSA passports) use different circuits, because the signature math differs. That is why our SSO service keeps a circuit registry rather than a single hardcoded verifier.
What ZK proofs do not do
It is just as important to be honest about what this technology cannot solve.
| ZK proves | ZK does NOT prove |
|---|---|
| "I hold a document that the Iranian Civil Registry signed." | "The Iranian Civil Registry only signs real people." |
| "Some valid input satisfies this circuit." | "The input came from a live, consenting human." |
| "I have not voted on event X before." | "I am the same human across two events." (Different nullifier per event by design.) |
| "My birth date is before 2008." | "I am not lying about my opinions." |
The biggest honest limit: a ZK proof inherits the trust of whoever signed the document. If the issuer's signing key is compromised — by a state actor or otherwise — fake-but-valid documents can be minted and will pass the proof system perfectly. This is true of every passport-ZK system in the world, including ours. See the passport trust chain for the full attack surface and our mitigations.
Why this is still worth doing
Even with that honest limit, ZK identity beats every alternative on the deployable spectrum:
| Approach | Reveals identity? | Resists sybil? | Censorship resistant? | Cost per check |
|---|---|---|---|---|
| Government login (e.g. Sana / Shahkar) | Yes — everything | Yes | No — server can block any user | Server cost |
| Username + password | No, but no identity guarantees | No | Depends on host | Server cost |
| Plain digital signature with passport | Yes — full document | Yes | Depends on host | Server cost |
| ZK proof from passport / INID | No | Yes (via nullifier) | Yes (on-chain) | ~$0.001 |
The combination of no identity disclosure, one-person-one-vote enforcement, and no central server able to delete users is what makes ZK proofs the right primitive for civic-scale democratic infrastructure under hostile conditions.
Glossary
| Term | Meaning |
|---|---|
| Circuit | The fixed arithmetic program that defines what is being proved. |
| Witness | The full set of intermediate values the prover computes while running the circuit on their inputs. Stays on-device. |
| Proof | The 256-byte (Groth16) cryptographic object that gets uploaded. |
| Public signals | The small set of circuit outputs (nullifier, root, date, etc.) that the verifier sees. Never includes raw document data. |
| Nullifier | A deterministic, identity-blinding value used to prevent double-voting. Same person + same event = same nullifier; same person + different event = different nullifier. |
| Verification key | A small file the smart contract holds, used to check proofs from a specific circuit. One per circuit. |
| Soundness error | The mathematical probability a cheater succeeds (~2⁻¹²⁸ in our setup — treated as zero in practice). |
Next
Up next: the passport trust chain — how we get to the assumption that "the document a user scans is real," and exactly where that assumption stops being true.