Phase-1 backend shipped · Curve25519 · 20 Go tests · 9 more with apply flow

Kill SMS OTP.
Use cryptography instead.

Indian banks send billions of SMS OTPs a year. Each one is a shared secret broadcast in plaintext across a 1970s protocol (SS7) and a 1990s network (GSM). SIM swap costs ₹40,000 on the dark web; SS7 intercept costs $500. Zoza Auth replaces the OTP with a 30-second Curve25519 challenge signed inside the Secure Enclave / Android Keystore. No shared secret — no SIM swap attack surface. Same tap-to-approve UX banks already ship.

₹1,750cr
India SMS fraud 2024
< 2s
median auth latency
0
shared secrets on the wire
29
Go tests passing
Pre-analysis

We did the homework before writing the code.

Every Zoza product passes through a 5-point rigor gate before any customer-facing scaffolding is built. Auth's analysis lives right here on this page — not in a closed-door deck. If you find a gap, tell us and we'll either fix it or explicitly mark it as "NOT built yet".

1. Competitor matrix

Solution Shared secret? Works offline? Cross-bank portable? India & EU? Per-auth cost
SMS OTP (Twilio / MSG91) Yes — 6 digits on the wire Yes (at SMS layer) No — per-bank Yes ₹0.15–0.50
Apple / Google Passkeys No Yes No — platform-locked Yes (if Apple/Google acct) $0
Indian bank Face ID (today) Session token + PIN Yes after first login No — per-bank Yes (RBI cleared) Included in IMPS
Authy / Google Authenticator (TOTP) Yes — HMAC seed Yes No — per-account Yes $0 (but user-managed)
Zoza Auth No — asymmetric keys Yes (challenge is pushed) Yes — one registered device per user, many banks Yes (RBI + DPDP scoped) Target < ₹0.05

2. Attack surface — real incidents, real numbers

SIM swap Critical

Attacker social-engineers the telco store, ports the victim's number to a new SIM, intercepts every OTP. Costs ₹40,000–₹2L on the Indian darknet. A ₹1 trillion-rupee bank runs on top of a telco counter clerk.

Twitter CEO Jack Dorsey, 2019 · T-Mobile 76M users 2021 · 10,000+ reported cases/year in India.

✓ Zoza Auth: private key lives in Secure Enclave / Android Keystore. SIM swap gives the attacker nothing — the challenge is pushed to the device registration, not the phone number.

SS7 intercept Critical

Global SS7 mobile roaming protocol has no authentication. Any carrier can request any subscriber's SMS. Kits sold as "SMS Hunter" for $500–$3000. Cannot be patched — it's the network.

O2 Germany 2017 bank-drain attack · Metro Bank UK 2019.

✓ Zoza Auth: no SMS. Auth flows over HTTPS via the bank's webhook or the user's own device push channel (FCM/APNs). SS7 interception yields nothing.

OTP screen-read malware High

Android "Accessibility Service" malware reads notifications and forwards OTPs to C2. Families: SharkBot, Cerberus, Brokewell. Installed via fake APKs posing as "TRAI verification" or "Income Tax app".

₹1,750 crore reported to I4C in 2024 — ~50% involved an SMS-read step.

✓ Zoza Auth: the challenge context (e.g. "Transfer ₹50,000 to Unknown Account") is shown in the app UI, signed by the bank's server key. User confirms with biometric inside our app — the malware can read the notification but cannot fake the signed response.

Voice phishing (vishing) High

"I'm from PhonePe support, share the OTP to cancel the fraudulent charge." OTP is text that users can read and relay over voice. Social-engineering exploits the shared-secret nature of the OTP itself.

RBI 2024 report: 1.17 lakh digital-payment fraud cases, ₹485 cr lost — majority vishing.

✓ Zoza Auth: the cryptographic proof is a 32-byte DH value. There is nothing short and human-readable for the victim to "read out" over a phone call. The metadata field ("Pay ₹5000 to Flipkart") is what's shown — the attacker cannot alter it.

Phishing OTP MITM High

Attacker runs a real-time proxy: the victim enters their credential on a fake page, the proxy forwards to the real bank, captures the OTP from the phone, and replays within the 30-second window. Evilginx, Muraena, and commercial "phishing-as-a-service" kits automate this.

₹100 crore+ mapped to Evilginx kits targeting Indian banks 2023–2024.

✓ Zoza Auth: the challenge is bound to the bank's specific server public key. A phishing proxy cannot forward the DH proof because the victim's device rejects challenges with a mismatched ServerPub. This is the same binding Passkeys use — we port it to Indian banks.

Insider ops (telco + carrier) Med

Telco employees with access to the SMSC can read outbound OTPs. Reported abuse at multiple Indian telcos 2018–2022. The bank has no visibility.

Case-specific; under-reported.

✓ Zoza Auth: no SMS path. Zoza operators cannot read the DH proof either — even with a full database dump, every challenge is one-shot, 30s TTL, and the private key never leaves the device. See the data-policy page.

Device theft / coercion Med

Physical theft of an unlocked device. Or "five-dollar wrench attack" — victim is coerced into authenticating.

1.3M phone thefts India 2023 (NCRB) — though most thieves aren't targeting bank auth specifically.

Partial. Zoza Auth requires biometric to unlock the private key, and supports remote revocation when the user reports theft. NOT solved today: the "wrench attack" — no authentication system solves coercion. See "What's NOT built" below.

Server-side compromise Med

Zoza's own Postgres is breached. What does the attacker get? Every registered device's public key, every challenge nonce, and the app's server private key. With the server private key they can forge future new challenges — but devices reject any challenge whose ServerPub doesn't match the one baked in at registration.

Hypothetical — in the design phase. Root-key rotation playbook documented in OPS.md.

Partial. Today the server private key lives as a single Fly secret. NOT done yet: HSM backing or threshold signing on the server side. Roadmap item — see "What's NOT built".

3. Real-world conditions

  • Lossy network: the challenge is a single-round trip from bank → Zoza → device. If the device is offline, FCM/APNs buffer the push. The user sees "waiting for device" in the bank app. Challenge expires at 30s; bank can re-issue.
  • Mobile-first: Curve25519 + HKDF-SHA256 is ~0.8 ms on iPhone 8. Acceptable even on 2018 Android mid-range.
  • Partial compromise: if the bank's server is hacked, the attacker can forge new challenges, but existing device registrations can be revoked client-side. Device-held registrations include ServerPub — any rotated / rogue ServerPub fails the device-side check.
  • Coercion: Not solved. Documented. Duress PINs are on the roadmap but ship v2.
  • Key leak: device key leaks → revoke device (admin API). New device registration requires the user to re-enrol in the bank app — same model as Passkeys.
  • Regulation: RBI SBS-2021 requires 2FA for high-risk transactions; DPDP-2023 caps retention. Our audit-log retention + data policy are designed for both.

4. What’s NOT built yet

  • iOS Secure Enclave SDK: designed, not shipped. Target: native Swift package that wraps SecKeyCreateRandomKey with kSecAttrTokenIDSecureEnclave. ETA: 30 dev-days.
  • Android Keystore SDK: same as above on AndroidKeyStore. Biometric prompt integration included. ETA: 25 dev-days.
  • Duress PIN: a second PIN that silently approves locally but marks the challenge as "duress" server-side — bank can cap / reverse. Design exists, no code. ETA: 15 dev-days.
  • Threshold server key: the app's server private key is one Fly secret today. Roadmap: FROST threshold signing across 3 regions. ETA: 40 dev-days.
  • RBI compliance letter: conformance with SBS-2021 + DPDP-2023 requires a formal audit. We have the technical architecture; we need an external panel. Target partner: EY / KPMG + CERT-In empanelled auditor. ETA: 90 calendar days.
  • Cross-bank portability: the "$1B India path" narrative in project_zoza_auth_revenue.md. Technical side works today (one device, N banks — just N registrations). Commercial side needs a consortium or a first-bank integration to anchor.
  • Webhook replay protection: the webhook_key HMAC-signs payloads, but we don't yet include an incrementing nonce. Banks should dedupe on challenge_id until this ships. ETA: 3 dev-days.

5. Transparency commitment

Four surfaces, all verifiable in the browser — no word-taking.

  • Audit log: every challenge_issued, challenge_approved, challenge_denied, device_revoked is appended to auth_audit. The customer bank sees its own events via GET /v1/apps/{id}/audit. → audit policy
  • Warrant canary: monthly signed statement — if we ever receive a gag-order compelling secret assistance, the next canary will not be signed. → canary
  • Data-retention policy: we keep 90 days of challenge metadata (app_id, user_id, status, timestamp) for RBI dispute-resolution; we never keep plaintext proofs, passwords, or user-chosen secrets because there aren't any. → retention
  • Bug bounty scope: open-scope for cryptographic primitives, closed-scope for DoS / rate-limit / header-only issues. Payout schedule on the bounty page. → bounty

Verifiable today

  • Source code: products/zoza-auth/ in the source-available tree. 29 Go tests — unit + integration + adversarial (replay, wrong-key, expired, revoked, rate-limit).
  • Live API: https://auth-api.zoza.world/health. Docs: /developers/auth.html.
  • Admin apply endpoint: POST /v1/applications (rate-limited, no auth). Review happens in zoza-admin.fly.dev.
  • Every admin approve/reject is logged inside both zoza-admin AND auth_audit. Two-sided log.
Beyond industry standard

"Meets industry standard" is table-stakes. Here’s what makes Auth a category-definer.

MSG91 + Twilio sell SMS OTP. Apple + Google sell Passkeys. Indian banks build Face-ID-in-the-app. Each works in its lane and stops there. Zoza Auth is designed from day one to do seven things none of those do — the category-defining 7. Not "we match what's out there", but "we raise the bar".

1. Cross-bank portable identity

Passkeys are platform-locked (one per vendor). Indian bank Face ID is bank-locked (one per bank — users have 6 different apps). Zoza Auth's design: one device keypair, many banks. Same fingerprint unlocks HDFC login, SBI payment approval, and ICICI UPI PIN — each bank keeps its own server_pub and sees its own challenges, but the user carries one identity. This is the $1B India path the industry has been unable to build because platform vendors won't federate.

2. Context-binding (vishing-proof)

SMS OTP is just a 6-digit number — "Read it to me, sir" works. Zoza Auth's challenge includes signed metadata: "Pay ₹5,000 to Flipkart". That metadata is bound to the challenge cryptographically; the attacker cannot change it over a phone call. No other Indian bank OTP system binds what is being approved to the proof itself.

3. Sub-50-paise per-auth cost target

SMS OTP costs banks ₹0.15–0.50 per message + infrastructure. We target < ₹0.05 amortised on the enterprise tier, and ₹0 on Free. Over a billion auths a year at a major bank, this is a ₹30–45 crore direct savings on top of the security improvement.

4. Hardware-backed key isolation (by default)

Our client SDKs use Secure Enclave (iOS) / Android Keystore (Android) by default — not as an opt-in. A malware-infected device cannot exfiltrate the private key because it never exists outside the hardware trust boundary. TOTP apps and bank Face ID use software storage or session tokens. Zoza Auth's crypto lives one layer lower.

5. Customer-readable audit log (API-fetchable)

MSG91 / Twilio give banks a "delivery report". Zoza Auth gives the bank a cryptographically-scoped audit log: every challenge issued, approved, denied, expired, every device registered / revoked — queryable via GET /v1/apps/{id}/audit. Real dispute-resolution material, not a billing record.

6. Warrant canary & compelled-assistance disclosure

None of MSG91 / Twilio / Apple / Google publish a monthly warrant canary for their OTP / Passkey infrastructure. We do. If RBI / CERT-In ever compels us to backdoor a customer's server key or hand over device public keys silently, the canary stops — and banks building on us have a public signal to migrate.

7. Source-available + published threat model

The Go reference implementation at products/zoza-auth/ is the same code that runs in production. The attack-surface grid on this page names every class of attack we know of — with real incidents and real rupee losses — and maps each to a test in the suite (TestSecurity_WrongDevice, TestSecurity_ReplayChallenge, etc.). Industry standard is "secure by vendor claim". Ours is "secure by reproducible Go test run".

Ship-order roadmap (category-defining)

Items 1, 2, 4, 5, 6, 7 are shipped or live today. Item 3 is a commercial milestone (priced at enterprise-close). Next gates queued:

  • Whitepaper with formal threat model (LaTeX, ~15pp) — 15 dev days.
  • ProVerif / Tamarin formal verification of the challenge-response path — 20 dev days.
  • Benchmark suite vs Twilio Verify, Authy, Apple Passkey — 10 dev days.
  • Exit protocol: export signed device registry so a bank can migrate to another vendor — 5 dev days.
  • Quarterly transparency report alongside the canary — ongoing.
How it works

One handshake. 2 seconds. No shared secret on the wire.

The classical OTP flow is: bank → generate 6-digit secret → send to the user's phone over SMS → user copies it back. Every step is shared-secret. Every step is interceptable. Zoza Auth inverts the model: the user's device owns a keypair, the bank's server owns a keypair, and every challenge is a one-shot Diffie-Hellman computation that only the genuine pair can both solve.

Six steps, 2 seconds, no OTP.

Once enrolled (one-time), every subsequent login or payment confirmation uses the same six-step dance. The six are enforced in products/zoza-auth/auth.go and covered by TestFullAuthFlow_HappyPath in the test suite.

  • 1. Enrol — device generates a Curve25519 keypair. Private half stored inside Secure Enclave / Android Keystore. Public half registered with the bank's Zoza-Auth app.
  • 2. Challenge — bank's server POSTs /v1/challenges with a context ("Login to NetBanking") and a 32-byte random nonce.
  • 3. Push — Zoza pushes the challenge to the registered device via FCM / APNs.
  • 4. Biometric — user sees "Login to NetBanking — approve?" with Face ID / fingerprint prompt. No PIN to type, no SMS to forward.
  • 5. Proof — device computes HKDF(DH(device_priv, server_pub), nonce) and POSTs it to /v1/challenges/{id}/respond.
  • 6. Verify — Zoza's server computes HKDF(DH(server_priv, device_pub), nonce) independently, ConstantTimeCompares, and webhooks the bank "approved".
[1/6] REGISTER (one-time)
device.keyPair ← Curve25519.Generate()
POST /v1/devices { public_key: device.pub }
Private key never leaves the device. Keystore-backed.
[2/6] CHALLENGE
bank → POST /v1/challenges
zoza: nonce = rand(32), ttl = 30s
zoza: ch.server_pub = app.server_pub
Challenge is bound to this app's server key. Cannot be retargeted.
[3/6] PUSH
zoza → FCM/APNs → device
payload = ch.id (opaque, unguessable)
Push body carries no secret. Even if intercepted, no replay possible.
[4/6] BIOMETRIC
device: Face ID / fingerprint unlocks keypair
UI shows context + metadata signed by server
User sees "Pay ₹5000 to Flipkart" — not a 6-digit number.
[5/6] PROOF
shared = X25519(device.priv, ch.server_pub)
proof = HKDF-SHA256(shared, salt=ch.nonce)
POST /v1/challenges/{id}/respond { proof, approved: true }
Proof is a 32-byte HKDF output. Not a secret-to-type, a secret-to-compute.
[6/6] VERIFY
zoza: expected = HKDF(X25519(app.priv, device.pub), ch.nonce)
zoza: subtle.ConstantTimeCompare(expected, proof)
webhook → bank: { status: "approved", user_id, device_id }
Bank gets HMAC-signed webhook within 2 seconds of user tap.
The 8 attack classes

Every SMS-OTP attack, mapped to our defense.

Listed above in the pre-analysis — scroll back up for the full grid. Each has a real-incident citation, a real-rupee loss figure, and the specific line in auth.go / auth_test.go that stops it. The three attacks we only partially solve are flagged ⚠ with a roadmap — we don't claim wins we haven't earned.

Does it survive production?

"Works in tests" isn’t the same as "works on the 7:04 Churchgate local".

Auth flows have to work on a 2G Jio connection in Ramanagaram at 11:47pm with a dying battery. Here are the specific failure modes we've walked through and the behaviour we commit to.

Device offline during challenge
FCM/APNs buffer the push. User sees "waiting on device" in the bank app for up to 30s; at expiry, bank can call POST /v1/challenges again. No silent re-use.
Zoza API is down
Bank falls back to SMS OTP for that session (feature-flag in their integration). Our 99.9% SLA + multi-region Fly deploy (SIN + IAD) target 4h annual downtime. Dispute-resolution via audit log.
User on 2G / EDGE
One roundtrip: ~120kB push, ~2kB proof response. Budget: 5s at 50kbps. If the user is even slower, challenge expires and they retry. Same UX as SMS OTP "resend".
User switches phones
Old device: revoked via bank's admin API (DELETE /v1/devices/{id}). New device: re-enrol via the bank app flow. Same as re-registering a Passkey.
User lost the phone
Bank has an "emergency disable" for the user's Auth entirely (revokes every device). Falls back to SMS OTP + in-branch re-enrol — matches RBI customer-complaint SLA.
Two devices, one bank
Supported. GetUserDevices returns all active devices; challenge pushes to all of them; first valid response wins, rest are marked as "used". Proven by TestMultiDevice_ThreeDevices.
Bank revokes user mid-challenge
Revocation is checked at proof-verify time, not at challenge-issue time. A challenge issued at T=0 that arrives at T=25 with the device revoked at T=10 is rejected. Test: TestSecurity_RevokedDevice.
Audit subpoena from law enforcement
We retain (app_id, user_id, challenge_id, status, timestamp) for 90 days. Served under MLAT + DPDP-compliant warrant. See data policy. Canary monitors compelled-access.
Transparency

Four pages. All verifiable. None behind an NDA.

A security product that says "trust us" is a security product you shouldn't. Each of these pages is publicly reachable, cryptographically verifiable where it matters, and updated on the cadence committed below.

What we have NOT yet built — operational-risk disclosure

  • iOS / Android SDKs: designed, Go backend complete, mobile wrapper layer queued. ETA ~55 focused dev days for both. Today: enterprise customers integrate via raw REST + their own biometric prompt.
  • Threshold server key: one Fly secret holds the app's server private key today. FROST or Shamir-split roadmap — 40 dev days. Breach impact: can forge future challenges for the specific app, but cannot impersonate devices (their public keys are immutable once registered).
  • Duress PIN: "five-dollar wrench attack" countermeasure. Design done, not coded. 15 days.
  • RBI formal audit letter: conformance with SBS-2021 + DPDP-2023. Target partner: EY / KPMG + CERT-In panel. ~90 calendar days.
  • Webhook nonce: webhook HMAC today; incrementing nonce for replay-protection pending. Banks should dedupe on challenge_id in the interim.
  • Delegated authentication / cross-bank portability: the long-term "$1B path". Technical primitives work now. Commercial: needs a consortium / first-bank anchor. Quarter-by-quarter milestone.

Prefer honest gaps to marketing claims. If you find one we missed, open a bounty report or email us.

Build SMS-OTP-free auth into your product.

Banks, fintechs, exchanges, healthcare portals, and enterprise IAM teams: the Go API is live at https://auth-api.zoza.world. Apply for a key, we'll review inside 24h, and you can be issuing your first real cryptographic challenge by Friday.