Zentity
Protocols

ZK Architecture

Noir circuits, zero-knowledge proof generation, and verification

Zentity’s ZK system separates proving (browser) from verification (server) so that private inputs never leave the user’s device while the server obtains cryptographic assurance of each claim. This document maps the circuit architecture, trust boundaries, and field constraints, with the proving environment (browser Web Worker vs. mobile NFC app) as the axis of variation.

Proving Architecture

Zentity generates proofs client‑side so private inputs stay in the browser. Proofs are verified server‑side and stored with metadata for auditability. Private inputs are derived from passkey‑sealed profile data (e.g., birth year, nationality) and server‑signed claim payloads (e.g., face match score). The server never sees plaintext values.

Loading diagram...

Circuits

CircuitPurposePrivate InputsPublic Inputs
age_verificationProve age >= thresholdDOB (days since 1900) + document hashCurrent days, min age days, nonce, claim hash
doc_validityProve document not expiredExpiry date + document hashCurrent date, nonce, claim hash
nationality_membershipProve nationality in groupNationality code (weighted-sum) + Merkle pathMerkle root, nonce, claim hash
address_jurisdictionProve address in jurisdictionAddress + Merkle pathMerkle root, nonce, claim hash
face_matchProve similarity >= thresholdSimilarity score + document hashThreshold, nonce, claim hash
identity_bindingBind proof to user identityBinding secret, user ID hash, document hashNonce, msg_sender_hash, audience_hash, base_commitment, binding_commitment

The verifier learns only the boolean outcome (e.g., "over 18"), never the underlying PII.

Identity Binding Circuit

The identity_binding circuit provides replay protection by cryptographically binding proofs to a specific user identity. It works across all four authentication modes:

Auth ModeBinding Secret SourcePrivacy Level
PasskeyPRF output (32 bytes)Highest – device-bound, non-extractable
OPAQUEExport key (64 bytes)High – password-derived, deterministic
WalletEIP-712 signature (65 bytes)Medium – wallet address stored for association
Wallet + BBS+BBS+ credential proof hashHighest – unlinkable presentations, wallet address hidden

The circuit is auth-mode agnostic: it accepts a generic binding_secret as a private input. The application layer handles per-mode derivation using HKDF with domain separation strings (e.g., zentity-binding-wallet-bbs-v1) to prevent cross-use attacks.

ZK vs FHE encoding: Nationality codes in ZK circuits use weighted-sum encoding (char1×65536 + char2×256 + char3) via getCountryWeightedSum() from @zkpassport/utils. FHE encryption uses a separate encoding for homomorphic operations. See Nationality Proofs for details.

Dual-commitment design:

The circuit computes two commitments from the private inputs and verifies them against the corresponding public inputs:

base_commitment    = Poseidon2(binding_secret, user_id_hash)
binding_commitment = Poseidon2(binding_secret, user_id_hash, document_hash, msg_sender_hash, audience_hash)
  • base_commitment: binds the proof to the user's identity secret only, enabling linkage across sessions for the same user
  • binding_commitment: binds the full session context (document, caller, audience), preventing replay across different verification contexts

The circuit returns pub bool (is_bound), which is true when both commitments match; otherwise the circuit panics via assert. This is a return value, not a public input.

This ensures that:

  • Same user + same document + same auth secret + same caller/audience context = same commitment (deterministic)
  • Different auth modes produce different commitments (domain separation)
  • Proofs cannot be replayed across users, documents, callers, or relying-party audiences

Mandatory Enforcement

Identity binding is mandatory; the proof generation pipeline requires a binding context and will not generate any proofs without it. This applies to both on-chain attestation and off-chain verification:

  • On-chain: Prevents proof replay across wallets. Without binding, a malicious relayer could submit someone else's valid proofs under a different address.
  • Off-chain: Provides tamper-evidence. The cryptographic user↔proof link means shuffled DB records would fail verification.

Credential Material Lifecycle During Verification

Binding requires the user's raw credential material (PRF output, OPAQUE export key, or wallet signature). This material follows a specific lifecycle:

  1. Cached at FHE enrollment: when the user enrolls FHE keys (verification preflight), the credential material is stored in an in-memory cache.
  2. Consumed at proof generation: the binding context resolver reads the cache to derive the binding secret via HKDF.
  3. Cleared after proof storage: the cache is cleared after all proofs are stored, regardless of success or failure.
  4. TTL safety net: if cleanup doesn't run (e.g., tab crash), the cache auto-expires.

For the ZKPassport NFC flow, credential material is still required for identity binding but the WebAuthn prompt can be suppressed via the promptPasskey option when the cache is warm.

If the cache is expired when proofs are generated (user was idle, page refreshed, TTL fired), the behavior depends on auth mode:

Auth ModeRecoveryUX
PasskeyAutomatic; WebAuthn PRF is re-prompted directlyUser taps/scans biometric
OPAQUERe-auth dialog; user enters password to re-derive export keyModal with password input
WalletRe-auth dialog; user signs EIP-712 typed dataModal with "Sign with Wallet" button

The re-auth dialog blocks proof generation until credential material is available. There is no fallback path that skips binding.

Proof Binding & Integrity

  • Nonces (server‑issued) prevent replay.
  • Claim hashes bind proofs to server‑signed OCR/liveness claims.
  • Context hashes (msg_sender_hash, audience_hash) bind identity proofs to the authenticated caller and relying-party audience.
  • Verifier metadata stores circuit/version identifiers for audit.

See Tamper Model for integrity rules and Attestation & Privacy Architecture for data classification.

Performance & UX Notes

  • Proof generation runs in a Web Worker to avoid UI blocking.
  • Circuits are optimized for Poseidon2 hashing (cheaper in ZK than SHA‑256).
  • Proof times are device- and circuit-dependent; treat any numbers as guidance.

Security Notes

  • Nonce binding prevents proof replay.
  • Claim binding ties proofs to server‑signed measurements (OCR, liveness, face match).
  • Range checks avoid wrap‑around and invalid inputs (dates, scores, thresholds).
  • Date input sanity checks enforce plausible ranges (current_days, min_age_days, current_date) at both circuit and server validation layers.

BN254 Field Constraints

All ZK circuits operate over the BN254 scalar field (~254 bits). Values exceeding the field modulus will cause proof generation to fail.

Field Modulus

BN254_FR_MODULUS = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
                ≈ 2^254 (actually slightly less)

Common Pitfalls

SourceSizeRisk
Passkey PRF output32 bytes (256 bits)⚠️ Can exceed modulus
OPAQUE export key64 bytes (512 bits)⚠️ Must reduce
Wallet signature65 bytes (520 bits)⚠️ Must reduce
SHA-256 hash32 bytes (256 bits)⚠️ Can exceed modulus
Poseidon2 output~254 bits✓ Always valid

Hash-to-Field Mapping

Direct modular reduction of 256-bit cryptographic outputs introduces small statistical bias. For cryptographic inputs (SHA-256 hashes, PRF outputs, wallet signatures), HKDF-based hash-to-field is used:

  1. HKDF-Extract+Expand (SHA-256) with domain-separated info string
  2. Expand to 512 bits
  3. Reduce modulo the BN254 field modulus

This technique applies to identity binding fields (binding secrets and user ID hashes from passkey, OPAQUE, or wallet credentials), document hashes (SHA-256 outputs before circuit use), and any 32-byte cryptographic output used as a circuit input. Values that are already valid field elements require only canonical formatting.

ZKPassport NFC Chip Verification

ZKPassport provides an alternative trust model where proofs are generated on the user's mobile device by the ZKPassport app, not in the browser Web Worker.

Loading diagram...

Key differences from browser-based Noir proving:

  • Proof generation: ZKPassport's own circuit infrastructure on mobile, not Noir/Barretenberg in a Web Worker
  • Verification: Server-side via zkpassport.verify(), not UltraHonkVerifierBackend
  • Liveness: Synthetic score (1.0), since NFC chip challenge-response proves physical possession
  • Nullifier: uniqueIdentifier prevents the same passport from being registered across multiple accounts
  • Country/document pre-check: buildCountryDocumentList (uses @zkpassport/registry) confirms NFC support before showing the option
  • Dev mode: devMode flag relaxes proof verification in development/test environments; production enforces strict verification

The unified identity_verifications table stores results from both paths via the method discriminator ("ocr" | "nfc_chip"). FHE encryption is scheduled identically after either path completes.

Sybil Deduplication

Zentity prevents the same identity document from being registered under multiple accounts using HMAC-based deduplication.

OCR path: computeDedupKey(DEDUP_HMAC_SECRET, docNumber, issuerCountry, dob)HMAC-SHA256 stored as dedupKey on identity_verifications (unique constraint). Any attempt to register the same document under a different account is rejected at the DB level.

NFC path: ZKPassport does not expose the document number. Deduplication relies solely on uniqueIdentifier (a nullifier from the NFC chip proof). Cross-method dedup (same passport via both OCR and NFC) is handled only by uniqueIdentifier.

Per-RP nullifier: An HMAC-SHA256 derived from the dedup key and client ID is delivered via the proof:sybil scope as sybil_nullifier in access tokens (not id_tokens). Each RP receives a unique pseudonymous nullifier; the same user always produces the same nullifier for the same RP, but different RPs cannot correlate users.

Implementation Notes

Proving flow (Web Worker)

Loading diagram...

Verification flow

Loading diagram...

Verification uses Barretenberg's UltraHonkVerifierBackend directly in the API route with singleton instances for efficiency.

Proof metadata (stored for auditability)

  • noirVersion
  • bbVersion
  • circuitHash
  • verificationKeyHash
  • verificationKeyPoseidonHash
  • circuitId (derived from the verification key hash)

On this page