Privacy-first identity platform with zero-knowledge biometric authentication
Indian Patent Granted • Application No. 202311041001 • US Patent Filed
Z Auth combines WebAuthn passkeys, face-based liveness verification, and Groth16 zero-knowledge proofs into a fully standards-compliant OpenID Connect provider. Raw biometric data never leaves the user's device — the server receives only irreversible cryptographic commitments and verifiable proofs.
Pramaan (Sanskrit: proof/evidence) is the identity verification protocol at the heart of Z Auth.
- Zero-Knowledge Biometric Proofs — Real Groth16 circuits (Circom + snarkjs) prove identity without revealing biometric data. The server verifies a Poseidon commitment, never the face itself.
- Client-Side Face Matching — Face embeddings are extracted, quantized, and matched entirely in the browser via face-api.js. Only a SHA-256 hash of the quantized embedding is transmitted.
- Cross-Device ZK Authentication — Enrollment hash is securely provided during authentication challenges, enabling seamless ZK proof generation on new devices without local biometric storage.
- WebAuthn Passkeys — FIDO2 discoverable credentials eliminate passwords. No server-side secrets, no phishing vectors.
- Standards-Compliant OIDC — Full OAuth 2.0 Authorization Code flow with PKCE (S256), token refresh, revocation, and a signed JWKS endpoint.
- On-Chain ZK Verification (Base L2) — Every enrollment and verification is submitted directly to a Groth16 verifier contract on Base Sepolia. Identity commitments are stored immutably on-chain with full proof verification.
- Blockchain Audit Anchoring — Hash-chained audit events with optional Merkle root anchoring and metadata pinning to IPFS.
- Single-VPS Deployment — Runs on a single 2 vCPU / 8 GB VM behind Caddy with automatic TLS.
User Device Z Auth Server Base L2 (Sepolia)
┌──────────────────┐ ┌──────────────────┐ ┌───────────────────┐
│ 1. Face capture │ │ │ │ ZAuthIdentity.sol │
│ 2. Client-side │ SHA-256 hash │ Verify biometric │ Enroll │ + Groth16Verifier │
│ embedding + ├───────────────>│ commitment ├──────────>│ │
│ face matching │ │ │ Verify │ On-chain ZK proof │
│ 3. Groth16 proof │ ZK proof + │ groth16.verify() ├──────────>│ verification + │
│ generation │ public signals│ Poseidon check │ │ identity storage │
│ 4. Passkey sign ├───────────────>│ │ │ │
│ │ │ Issue OIDC tokens│ │ │
│ │<───────────────┤ │ │ │
└──────────────────┘ access_token └──────────────────┘ └───────────────────┘
id_token
Privacy invariant: Raw biometric descriptors never leave the user's device. The server receives only:
| Data | Purpose | Reversible? |
|---|---|---|
biometric_hash |
SHA-256 of quantized face embedding | No |
zk_proof |
Groth16 proof binding biometric to challenge | No |
public_signals |
Poseidon commitment + challenge binding | No |
passkey_assertion |
WebAuthn signature from secure enclave | No |
apps/
├── zauth-core/ # OAuth/OIDC server + Pramaan V2 identity engine
│ ├── src/routes/ # OIDC, Pramaan, WebAuthn, admin, liveness endpoints
│ ├── src/services/ # ZK verification, passkey, sessions, audit, anchoring
│ ├── zk/ # Groth16 circuit artifacts
│ │ ├── biometric_commitment.circom # Poseidon(preimage) + challenge binding
│ │ ├── biometric_commitment.wasm # Client-side witness generator
│ │ ├── circuit_final.zkey # Proving key (client-side)
│ │ └── verification_key.json # Verification key (server-side)
│ ├── contracts/ # ZAuthIdentity.sol (Base Sepolia) + ZAuthAnchor.sol
│ └── assets/ # Static assets (logo, fonts)
├── zauth-ui/ # Admin console + status dashboard
└── zauth-notes/ # Reference relying-party app (OAuth client)
├── src/ # Express backend (OAuth callback, PostgreSQL)
└── web/ # React 18 + Vite frontend
packages/
└── sdk/ # @zauth/sdk — OIDC client library
docker/
├── compose.base.yml # Service definitions (Postgres 16, Redis 7, apps)
├── compose.dev.yml # Hot reload, local ports, debug logging
├── compose.prod.yml # Caddy TLS, health checks, read-only FS
└── compose.test.yml # Ephemeral CI stack
| Layer | Technology |
|---|---|
| Authentication | WebAuthn / FIDO2 passkeys (@simplewebauthn/server) |
| Biometrics | face-api.js (client-side only), SHA-256 commitments |
| Zero-Knowledge | Circom 2.1.9, Groth16 via snarkjs, Poseidon hash |
| Identity Protocol | OAuth 2.0 / OpenID Connect with PKCE S256 |
| Blockchain | Base Sepolia (L2), Solidity 0.8.24, ethers.js v6 |
| On-Chain Verification | Groth16Verifier.sol — ZK proof verification directly on Base |
| Storage | IPFS via Pinata REST API |
| Backend | Node.js 20, Express, TypeScript |
| Frontend | React 18, Vite, CSS design tokens (light + dark mode) |
| Database | PostgreSQL 16 (append-only audit model) |
| Cache | Redis 7 (sessions, challenges, handoff state) |
| Reverse Proxy | Caddy 2 (automatic TLS via Let's Encrypt) |
| CI/CD | GitHub Actions, GHCR images, Trivy vulnerability scanning |
# Clone and configure
git clone https://github.com/pulkitpareek18/Z_Auth.git
cd Z_Auth
cp env/.env.dev.example env/.env.dev
# Start the development stack
make up-dev
# Services available at:
# Auth server → http://localhost:3000/ui/login
# Demo app → http://localhost:3001
# Notes app → http://localhost:5173| Service | URL |
|---|---|
| Auth Server | auth.geturstyle.shop |
| Notes App | notes.geturstyle.shop |
| Admin Console | console.geturstyle.shop |
| OIDC Discovery | auth.geturstyle.shop/.well-known/openid-configuration |
All authentication flows require face verification with zero-knowledge proof generation (AAL2:zk assurance level). The primary flow uses cross-device QR handoff:
Desktop Phone Server
│ │ │
├─ POST /auth/handoff/start ─────────────────────────>│
│<── QR code + handoff_id ────────────────────────────┤
│ │ │
│ Scan QR │
│ ├─ Face liveness challenge ─────────────────>│
│ │<── [blink, turn_left, turn_right] ─────────┤
│ ├─ Liveness frames ─────────────────────────>│
│ ├─ Passkey assertion ───────────────────────>│
│ ├─ Groth16 proof + public signals ──────────>│
│ ├─ POST /auth/handoff/approve ──────────────>│
│ │ │
│ ← Poll (approved) → session + consent redirect ────┤
Cross-device support: When authenticating from a new device (no local enrollment data), the server securely provides the enrollment hash during the challenge phase so the ZK proof can be generated with the correct Poseidon preimage.
- Enter one of eight recovery codes generated at enrollment
- Verify biometric commitment or provide three-of-eight codes
- Old passkeys revoked, new passkey + recovery codes issued
Z Notes is a full-featured notes app that demonstrates Z Auth integration as a relying party. It showcases the complete OAuth 2.0 + PKCE flow with ZK biometric authentication.
Stack: React 18 + Vite + TypeScript frontend, Express + PostgreSQL backend
Landing page features:
- Professional conversion-focused design with dark mode support
- "How It Works" visual flow showing the 3-step ZK authentication process
- Security deep-dive section with ZK flow diagram and privacy data comparison
- Patent and trust section highlighting Indian Patent 202311041001
- Full responsive design (mobile, tablet, desktop)
| Method | Path | Description |
|---|---|---|
GET |
/.well-known/openid-configuration |
Discovery document |
GET |
/.well-known/jwks.json |
JSON Web Key Set |
GET POST |
/oauth2/authorize |
Authorization endpoint (PKCE S256) |
POST |
/oauth2/token |
Token exchange |
POST |
/oauth2/revoke |
Token revocation |
GET |
/oauth2/userinfo |
User claims (sub, acr, amr, uid, did) |
| Method | Path | Description |
|---|---|---|
POST |
/pramaan/v2/enrollment/start |
Begin identity enrollment |
POST |
/pramaan/v2/enrollment/complete |
Finalize with ZK proof |
POST |
/pramaan/v2/proof/challenge |
Request authentication challenge (includes enrollment hash) |
POST |
/pramaan/v2/proof/submit |
Submit Groth16 proof for verification |
GET |
/pramaan/v2/identity/me |
Current identity context |
| Method | Path | Description |
|---|---|---|
POST |
/auth/handoff/start |
Initiate QR cross-device handoff |
GET |
/auth/handoff/status |
Poll handoff state |
POST |
/auth/handoff/approve |
Phone-side approval |
POST |
/auth/liveness/challenge |
Start liveness challenge |
POST |
/auth/liveness/verify |
Submit liveness result |
| Method | Path | Description |
|---|---|---|
GET |
/health/live |
Liveness probe |
GET |
/health/ready |
Readiness (DB + Redis) |
GET |
/health/deps |
Dependency status |
import { ZAuthClient } from "@zauth/sdk";
const client = new ZAuthClient({
issuer: "https://auth.geturstyle.shop",
clientId: "my-app",
redirectUri: "https://myapp.com/callback",
scopes: ["openid", "profile", "zauth.identity"],
});
// Start authorization flow
const { url, state, codeVerifier } = await client.authorize();
window.location.href = url;
// Handle callback
const { code } = client.parseCallback(window.location.search);
const tokens = await client.exchangeCode(code, codeVerifier);
const user = await client.getUserInfo(tokens.access_token);
// → user.sub, user.uid, user.did, user.acr, user.amr| Property | Implementation |
|---|---|
| No biometric templates server-side | Face matching is client-side only; server stores SHA-256 hashes |
| Zero-knowledge identity proofs | Groth16 circuit with Poseidon commitment binding |
| No passwords | WebAuthn discoverable credentials (passkeys) |
| Cross-device ZK support | Enrollment hash provided in challenge for new-device authentication |
| On-chain ZK verification | Groth16 proofs verified on Base Sepolia via ZAuthIdentity.sol |
| Tamper-evident audit trail | SHA-256 hash-chained events, on-chain proof events (ProofVerified) |
| Nullifier-based consumption | Recovery codes and proof requests use insert-only nullifiers |
| PKCE S256 enforced | All OAuth flows require proof key for code exchange |
| Container hardening | Read-only filesystem, memory limits, Trivy scanning |
| CSP + Helmet | Strict Content Security Policy with form-action restrictions |
See docs/THREAT_MODEL.md for the full threat model.
# 1. Point DNS A records for all subdomains → VPS IP
# 2. Configure firewall
make vps-setup
# 3. Deploy
make release| Environment | Compose File | Features |
|---|---|---|
dev |
compose.dev.yml |
Hot reload, local ports, debug logging |
test |
compose.test.yml |
Ephemeral CI stack, integration tests |
prod |
compose.prod.yml |
Caddy TLS, health checks, read-only FS |
| Workflow | Trigger | Actions |
|---|---|---|
ci-pr.yml |
Pull request | Lint, type-check, test, build, Trivy scan |
ci-main.yml |
Manual | Full test suite, build, security scan |
deploy-demo.yml |
Manual | Deploy to demo environment |
deploy-prod.yml |
Push to main / tag | Build, deploy to VPS, smoke test |
Every enrollment and verification is submitted directly to the Base Sepolia L2 chain. The ZAuthIdentity.sol contract inherits a Groth16 verifier generated from the ZK circuit, enabling full on-chain proof verification.
- Enrollment — When a user enrolls, their ZK proof is verified on-chain via
enrollIdentity(). The contract stores the identity commitment (Poseidon hash), commitment root, and version. - Verification — During login, the ZK proof is submitted to
verifyAndLog(), which re-verifies the proof on-chain and emits aProofVerifiedevent for an immutable audit trail. - Identity Lookup —
getIdentity()is a free view call that retrieves the on-chain identity record for cross-device verification.
| Contract | Chain | Purpose |
|---|---|---|
ZAuthIdentity.sol |
Base Sepolia | On-chain identity registry + Groth16 proof verification |
Groth16Verifier.sol |
Base Sepolia | Auto-generated BN128 verifier (inherited by ZAuthIdentity) |
Identity Contract: 0x34E3dd36326B8360B161Cd8DEc33f71821E33797 |
| Indian Patent | Granted — Application No. 202311041001 |
| US Patent | Filed |
| Title | A system for performing person identification using biometric data and zero-knowledge proof in a decentralized network |
| Applicant | Yushu Excellence Technologies Private Limited |