Platform
Security & privacy
Platform

Security & privacy

What LKP guarantees, what it doesn't, and what you need to get right in your integration.

What we store#

  • Wallets: AES-256-GCM ciphertext only. The key is wrapped by the user's passkey (WebAuthn PRF + HKDF), an optional passphrase (PBKDF2, 600,000 iterations) and a recovery kit. None of these reach our servers.
  • Sybil tags: a keyed HMAC of a verified document, phone, email or account identifier, computed in the enclave. That specific credential can back only one wallet. The HMAC key never leaves the enclave, so tags can't be reversed or brute-forced offline. This is not a biometric, and it does not prove a person has only one legal identity worldwide.
  • Revocation bits: one bit per issued credential.
  • Sessions: your requirements, status, timing and the nullifier for your app (derived from the wallet's identity key, not from a biometric).

LKP does not persist names, birth dates, nationalities, document numbers, images, phone numbers, email addresses, usernames, follower counts or bank details. Document and selfie images, OTP delivery, and related telemetry are processed by Didit under Didit's privacy policy; we request deletion of the Didit session after issuance, but Didit may retain materials for its own legal and anti-fraud periods.

What proofs guarantee#

  • Soundness: Groth16 proofs over BN254. A proof only verifies if the user holds an unrevoked, unexpired credential signed by the LKP issuer that satisfies your requirements.
  • Inherited authenticity: government-ID facts come from Didit's extraction of the document. The proof confirms that LKP signed those facts; it does not independently re-examine the physical document or prove current location.
  • Server-side public inputs: LKP rebuilds every public input from the stored session, including the threshold, the current time, your app ID and the revocation root. Users can't loosen requirements or replay proofs.
  • Holder binding: credentials are bound to the user's secret identity key, so a copied credential is useless without the wallet.
  • Single use: each challenge is consumed atomically. A proof is accepted at most once.
Not a compliance product by itself
A verified session is not, out of the box, a determination that you have met a specific age-gating, KYC, AML or residency law. You remain responsible for whether an LKP check is sufficient for your obligations.

Your responsibilities#

Confirm results on your server

The SDK's status and redirect parameters are hints. Always fetch the session (or use a signed webhook) before granting access.

Bind sessions to your users

Store the session ID you created against the user's own session or account, and look it up from there. If you accept session IDs from the browser, a user could present a session someone else completed.

Understand relaying

A zero-knowledge proof shows that some holder of a valid wallet approved the request, not that the person at the keyboard is that holder. Someone could open your session link and ask a friend to complete it. Nullifiers limit the damage: each wallet maps to one anonymous ID per app, and one-time actions can't be repeated by that wallet. They do not uniquely identify a human across every document they might hold. For high-stakes use, combine LKP with your own account checks.

Protect your keys

  • Keep secret keys and webhook secrets in server-side configuration only.
  • Create a separate key per environment or service, and revoke keys you no longer use.
  • Verify webhook signatures and timestamps on every request.

Platform security#

  • The issuer's signing keys live in an isolated signing service that only signs credentials and never stores the attributes it signs.
  • Console sign-in is handled by a dedicated identity provider; every API call carries a short-lived, signed access token.
  • API keys and session tokens are stored only as SHA-256 hashes.
  • All endpoints are rate limited per client IP.

Threat Model & Invariants#

LKP is engineered with explicit cryptographic invariants to balance user privacy against fraud resistance:

  • Credential sharing & theft: Every credential is cryptographically bound to holder_commitment = Poseidon([identity_secret]). Possession of a stolen credential ciphertext or public certificate alone is useless without the hardware-bound passkey that unlocks the private identity secret.
  • Proof replay: Challenges are single-use nonces stored with a 5-minute TTL. The server consumes them atomically via Redis GETDELbefore proof verification begins. Replaying a valid proof fails with an expired challenge error.
  • Estimation spoofing & buffer rule: Facial age estimation carries inherent variance. To prevent underage users from exploiting confidence intervals, LKP enforces a strict 3-year buffer. For an 18+ requirement, an estimated age under 21.0 automatically declines estimation-only issuance and falls back to full government ID document inspection. For 21+, estimated age under 24.0 triggers the same fallback.
  • LKP observability of Relying Party: Verifiers observe the app_id and requested policy requirements to enforce policy and accurate billing. LKP does not observe or persist the user's name, birth date, document images, or cross-site browsing activity.
  • Provider retention: Didit processes biometric captures and documents under its independent statutory compliance duties. LKP immediately triggers session deletion with privacy erasure upon completion, and retains zero document images in its database or enclave runtime.
Report a vulnerability
Found something? Contact us privately with details and steps to reproduce, and allow reasonable time for a fix before disclosure.