Built a ZK identity system that lets you prove
who you are without revealing any data.
Tech: Rust (Plonky2 v0.2.2) via JNI → Kotlin
Two tiers:
- Tier 1: NFC passport (ICAO 9303)
- Tier 3: Android KeyStore + biometric
Benchmarks on Realme RMX3830 (Android 14):
- Proof: 39ms
- Verify: 8ms
- Memory: 78KB
- Proof size: 20KB
Live demo: zkp-identity-production.up.railway.app
💬 Feedback & Chat:
discord.gg/rXmMh2q2k
Happy to answer questions about circuit
design or Rust-JNI implementation.
Top comments (2)
The 8ms verify time is the number that matters most for relying parties at scale - at that latency you can verify inline per-request rather than caching session tokens, which eliminates a whole class of replay and session fixation attack surface.
One design question worth thinking through: how do you bind the proof to a specific transaction or session without leaking correlation data across uses? A blinded nonce committed inside the circuit would let the verifier confirm freshness while preserving zero-knowledge - otherwise the same 20KB proof could be replayed across relying parties if it ever leaks.
Also curious whether the Tier 1 (NFC passport) path handles the active authentication step in ICAO 9303, or only passive authentication - the distinction matters for proof binding since passive auth proofs don't prevent cloning from a captured NFC dump.
Great questions — exactly the threat model
we thought about.
On replay prevention: the proof includes a
Poseidon nullifier scoped to domain +
challenge. Each session generates a fresh
64-byte hex challenge server-side, committed
inside the circuit. The server maintains a
nullifier registry — same nullifier = instant
reject. The 20KB proof is session-bound, not
reusable across relying parties.
On ICAO path: currently passive authentication
only — we verify the SOD signature (RSA/ECDSA)
and DG1 hash chain, but active authentication
(chip private key challenge-response) is on
the roadmap. You're right that passive auth
alone doesn't prevent replay from a captured
NFC dump. Our current mitigation is binding
the passport proof to the device KeyStore key
at generation time — so even a valid DG1 dump
needs the hardware-bound ECDSA sig to complete
the proof. Not a full substitute for active
auth, but raises the bar.
The 8ms verify observation is spot on — that's
exactly why we chose Plonky2's FRI backend over
Groth16. Sub-10ms inline verification was the
design target.
Repo: github.com/Hamid0004/zkp-identity