System Architecture
Overview of how Zentity's services connect and how data flows through the system
Zentity is the verified-human signal layer for the agentic internet. Its architecture separates proving (browser), verifying (server), and computing (FHE service) so that no single component handles both plaintext data and policy decisions. This document maps how services connect, how data flows between them, and what crosses each trust boundary, with the deployment context (Web2, Web3, agent) as the axis of variation.
Architecture
Components and Key Technologies
| Area | Technologies | Responsibility / Notes |
|---|---|---|
| Web UI + API | Next.js 16 (App Router), React 19, Node.js, pnpm, tRPC | Primary UI and orchestration layer with type-safe API routes. |
| ZK proofs | Noir, bb.js (Barretenberg), UltraHonk | Client-side proving in Web Workers; server-side verification. |
| Liveness + face match | Human.js + tfjs-node | Multi-gesture liveness and face match; real-time guidance on client, verification on server. |
| OCR | RapidOCR (PPOCRv5), python-stdnum | Document parsing, field extraction, and validation. |
| FHE and Web3 | TFHE-rs (Rust), fhEVM, Base mirror | Encrypted computation off-chain, encrypted on-chain attestation, and narrow public payment-time predicates. |
| Storage | SQLite (libSQL/Turso), Drizzle ORM | Privacy-first storage of commitments, proofs, and encrypted blobs. |
| Auth + key custody | Better Auth + WebAuthn + PRF + OPAQUE + EIP-712 Wallet | Passkey, OPAQUE password, or wallet signature authentication; all three derive client-held keys for sealing secrets. |
| Verifiable credentials | OIDC4VCI, OIDC4VP, SD-JWT VC, DCQL, JARM | Credential issuance and wallet presentation via OpenID standards. |
| HAIP compliance | @better-auth/haip (DPoP, PAR, JARM, wallet attestation, DCQL) | High Assurance Interoperability Profile for regulated wallet integrations. |
| CIBA | @better-auth/ciba (backchannel auth, poll + ping modes) | Agent-initiated async authorization via email/push notification and user approval. |
| Social recovery signing | FROST signer services (Rust/Actix) | Threshold signing for guardian-approved recovery (and future registrar). |
| MCP identity server | Node.js, Hono, @modelcontextprotocol/sdk | HTTP/stdio MCP server with OAuth-authenticated identity tools (whoami, my_profile, my_proofs, check_compliance, purchase). |
| Observability | OpenTelemetry | Cross-service tracing with privacy-safe attributes. |
System Diagram
Cryptographic Foundation
Four cryptographic pillars (auth + key custody, ZK proofs, FHE, and commitments) interlock to eliminate plaintext data handling. This document focuses on flow and system boundaries; see Cryptographic Pillars for how the primitives bind together, Attestation & Privacy Architecture for data classification, ZK Architecture for proof system design, and Web3 Architecture for on-chain attestation.
Data Handling
We persist only the minimum required for verification and auditability:
- Commitments and hashes for integrity and deduplication
- Encrypted attributes (FHE ciphertexts)
- Proof payloads + public inputs
- Passkey-sealed profile (encrypted blob; client-decrypt only)
- OPAQUE registration records for password users (no plaintext or password hashes)
- Account snapshot state in
identity_bundles(validityStatus,effectiveVerificationId,verificationExpiresAt,nullifierSeed, and other non-sensitive operational metadata) - Credential history in
identity_verifications(OCR and NFC verification rows, supersession lineage, and method-specific metadata) - Validity transition and delivery ledgers in
identity_validity_eventsandidentity_validity_deliveries - Public Base mirror state containing only wallet address, active mirrored attestation state, and numeric compliance level
Key Custody
A two-layer encryption scheme (DEK wrapped by a credential-derived KEK) ensures the server stores only encrypted blobs it cannot decrypt. Users decrypt locally after an explicit credential unlock. See FHE Key Lifecycle for the full key hierarchy and credential-specific derivation paths.
Raw document images, selfies, plaintext PII, and biometric templates are never stored. Full classification and storage boundaries live in Attestation & Privacy Architecture.
Social Recovery
Zentity supports guardian-approved recovery for passkey loss. Recovery is initiated with email or a Recovery ID, guardians approve via email links or authenticator codes, and the signer services perform FROST threshold signing once the approval threshold is met. Recovery wrappers are stored in recovery_secret_wrappers, and the signer coordinator is contacted from the Next.js server (not the browser).
The FROST aggregated signature is not just an authorization gate; it is cryptographically entangled with key custody. The signature is used as input key material for HKDF-SHA256(ikm=signature, salt=challengeId, info="zentity:frost-unwrap") to derive an AES-256-GCM key that unwraps the recovery DEKs. Without the real FROST signature, the DEKs cannot be recovered even with DB access.
Three guardian types are supported: email (approval link), twoFactor (authenticator code), and custodialEmail (Zentity-operated signer, limited to 1 per user, cannot be the sole guardian).
Graduated Disclosure Depth
Zentity supports two usage modes that share the same core cryptography but differ in what is disclosed.
Non-regulated (age-gated / consumer apps)
- The relying party receives proofs only (e.g., "over 18", "document valid").
- No PII is shared. Verification is local to the relying party.
Regulated (banks / exchanges)
- The user authorizes disclosure with a passkey.
- The client decrypts the sealed profile and re-encrypts to the relying party.
- The relying party receives PII + proofs + evidence pack as required by regulation.
- Zentity retains cryptographic artifacts only, not plaintext PII.
ARCOM double anonymity (pairwise flows)
For DCR clients, Zentity defaults to pairwise subject identifiers (subject_type: "pairwise"), preventing cross-RP user correlation. In proof-only flows, additional ARCOM measures apply:
- Consent records deleted after authorization code issuance (transient linkage)
- Access token DB records deleted after JWT issuance
- Session IP/UA metadata scrubbed
- Opaque access tokens forced for pairwise clients (no JWT
subleakage)
Data Flows
Sign-Up and Verification
Password (OPAQUE) flow
OPAQUE authentication uses a password-derived export key:
- Client performs OPAQUE registration and derives an export key.
- Export key → HKDF → KEK wraps/unlocks the DEK during verification preflight and other step-up flows.
- Server stores the OPAQUE registration record (no plaintext password).
- Secret wrappers are stored with
kek_source = "opaque".
Wallet (EIP-712) flow
Wallet authentication uses EIP-712 typed data signing to derive the KEK:
- User signs an EIP-712 typed data message during wallet authentication and verification preflight.
- At sign-up, we run a best-effort stability check (sign twice, compare) to reject clearly unstable signers before key wrapping.
- This check does not guarantee future wallet behavior across firmware/app changes or device migration, so wallet users should add a backup passkey and/or guardian recovery wrapper.
- Signature bytes are processed through HKDF-SHA256 to derive the KEK.
- The private key never leaves the wallet; the signature stays in the browser.
- Server stores the wallet address for account association.
- Secret wrappers are stored with
kek_source = "wallet". - Sign-in also requires a SIWE (EIP-191) signature for session authentication (nonce-based replay protection).
- Supports hardware wallets (Ledger/Trezor) for enhanced security.
Disclosure
Wallets can receive credentials and respond to presentation requests directly via OID4VP, bypassing the OAuth consent UI. The verifier creates a VP session with a DCQL query, signs a JAR JWT with x5c chain, encodes it as an openid4vp:// QR code URI. The wallet presents an SD-JWT VP with KB-JWT binding, encrypted via JARM.
All token requests require DPoP (sender-constrained tokens) and PAR (pushed authorization requests). See OAuth Integrations for protocol details.
Agent Authorization (CIBA)
Agents and applications can request user authorization without a browser redirect via CIBA. The agent sends a backchannel request identifying the user; the user receives a push notification (with email fallback) and approves from the dashboard. If identity scopes are requested, the user unlocks their vault and PII is staged ephemerally for delivery via the userinfo endpoint. Poll and ping delivery modes are supported.
The access token includes an act claim identifying both the human and the agent. See Agent Architecture for host registration, session lifecycle, protocol composition, and security properties. See OAuth Integrations for endpoints, scopes, and configuration.
OCR + Liveness
Document OCR
- The OCR service extracts fields (name, DOB, document number, country) and validates formats.
- Images are processed transiently and never stored.
- Only derived claims and commitments return to the web app.
Liveness + face match
- The client runs Human.js for real-time detection and gesture guidance (smile, head turns).
- The server re-verifies frames with Human.js (tfjs-node) and issues signed liveness/face-match claims.
- This split keeps UX responsive while preserving server-side integrity.
For detailed liveness policy and integrity guarantees, see Tamper Model and Attestation & Privacy Architecture.
Web3 Layer
Zentity can attest verified identity on-chain using fhEVM while keeping attributes encrypted. The server (registrar) encrypts identity attributes and submits attestation; users authorize access with explicit grants.
For payment-time and resource-server checks, Zentity also writes a narrow public Base mirror that exposes only isCompliant(address,uint8), active mirrored attestation state, and numeric compliance level. The mirror is a delivery surface of the identity validity pipeline, not a second identity authority.
See Web3 Architecture and ADR-0005.
Verifiable Credentials (SSI)
Zentity issues portable verifiable credentials following OpenID standards:
- OIDC4VCI: Pre-authorized code flow with DPoP-bound tokens, immediate and deferred issuance, status list revocation.
- OIDC4VP: DCQL presentation queries with
x509_hashclient_id, JARM encrypted responses, AKI-based trust filtering. - SD-JWT VC: Selective disclosure with KB-JWT holder binding (
cnf.jktthumbprint, signature, freshness).
External wallets can receive and hold credentials; third-party verifiers can request presentations without Zentity involvement.
Credentials contain only derived claims, never raw PII. Claims like verified, age_verified, and verification_level indicate verification status without exposing underlying data.
See SSI Architecture for the complete Self-Sovereign Identity model.
Account-Scoped Identity Model
Zentity models identity as one account-scoped snapshot plus credential history.
identity_bundlesis the current account snapshot. It stores the authoritativeeffectiveVerificationId, currentvalidityStatus, the freshness deadline (verificationExpiresAt), and the bundle-ownednullifierSeedused for stable per-RP anti-abuse claims.identity_verificationsis credential history. OCR and NFC credentials append here, and older authoritative rows can become explicitly superseded without being deleted.reconcileIdentityBundle(userId)is the only bundle reconciler. Verification lifecycle checkpoints call it to select the authoritative credential, compute freshness state, and preserve or reseed the RP nullifier seed according to bundle policy.identity_validity_eventsrecords immutable lifecycle transitions such asverified,stale,revoked, andsuperseded.identity_validity_deliveriestracks downstream effects such as credential-status updates, back-channel logout, RP validity notice, CIBA cancellation, blockchain revocation delivery, and Base mirror writes.
This split is what lets OCR and NFC credentials coexist on one account without making disclosure, assurance, or operator reads re-rank raw rows ad hoc.
State Durability & Shared Devices
- Sign-up state is local React state only (no DB, no cookies). Refreshing the page restarts the sign-up form. An anonymous session is created on load for the credential flow.
- FHE enrollment state is tracked server-side via
identity_bundles.fheKeyId. Enrollment is resumable; if partial, the preflight re-checks completion criteria. - Verification progress and history live in first-party DB tables keyed by user ID, with the current snapshot in
identity_bundles, credential rows inidentity_verifications, and supporting artifacts in drafts, signed claims, and ZK proofs. - Validity transitions and downstream delivery state are durable and auditable through
identity_validity_eventsandidentity_validity_deliveries. - Profile data lives in a credential-encrypted vault (
encrypted_secrets+secret_wrappers), only decryptable client-side after a credential unlock. - Identity PII is never stored server-side. At consent time, PII is staged ephemerally in memory (5min TTL) and consumed once through the userinfo delivery path. The user's profile vault is the only persistent PII source.
Observability
- Distributed tracing via OpenTelemetry across Web, FHE, and OCR
- Sign-up and verification spans for step timing + duplicate-work signals
- Privacy-safe telemetry (hashed IDs only; no PII)
Compliance Derivation Engine
Compliance level is computed by a pure function that takes ZK proofs, signed claims, encrypted attribute presence, and flags as input, with no DB access, no mutable booleans, and no side effects.
Levels
| Level | Numeric | Criteria |
|---|---|---|
none | 1 | Default; fewer than half of the 7 checks pass |
basic | 2 | At least half of the 7 checks pass |
full | 3 | All 7 checks pass |
chip | 4 | NFC chip verification + sybil resistance |
verified is true only for full or chip.
The 7 Boolean Checks
Each check is derived from proof/claim existence, not stored boolean columns.
| Check | OCR path source | NFC chip path source |
|---|---|---|
documentVerified | doc_validity ZK proof exists | Always true (chip is the document) |
livenessVerified | liveness_score signed claim exists | chip_verification claim type present |
ageVerified | age_verification ZK proof exists | chip_verification claim type present |
faceMatchVerified | face_match ZK proof or face_match_score claim | chip_verification claim type present |
nationalityVerified | nationality_membership ZK proof exists | hasNationalityCommitment flag |
identityBound | identity_binding ZK proof exists | chipNullifier present |
sybilResistant | Effective verified credential has an internal identity key | Effective verified credential has an internal identity key |
For NFC chip checks, only claim type presence matters; boolean payloads inside the claim are ignored. This prevents a malicious server from downgrading compliance by flipping a stored boolean.
birthYearOffset
A privacy-preserving age representation: currentYear - birthYear. Validated to the range 0–255 (uint8). Stored in identity_verifications as a proof output, never client-supplied.
See Attestation & Privacy Architecture for the full data classification table and SSI Architecture for how compliance level feeds into verifiable credential claims.
Identity Revocation
Identity revocation is part of the same validity pipeline that handles freshness and re-verification.
- Admin revocation, self-service revocation, and chain-ingested revocation all converge on the same validity transition boundary.
- The account snapshot in
identity_bundlesbecomesrevoked, clearseffectiveVerificationId, and clearsnullifierSeedso a later full re-verification can establish a new unlinkable seed. - Credential history is preserved. Verification rows are retained for audit; current trust lives on the bundle snapshot and in the validity ledger.
- Downstream effects fan out through the delivery ledger: issued credential status updates, back-channel logout, pending CIBA cancellation, RP validity notice, blockchain attestation revocation, and Base mirror revocation.
After revocation, the user's assurance posture drops, current proof claims stop reflecting a valid identity, and document-level dedup guards are released according to the current product policy.
Storage Model
Database schema and table relationships are documented in Attestation & Privacy Architecture. The Drizzle schema is the authoritative source for column-level detail.