Privacy-Preserving KYC: How to Implement ZK Proofs for Identity Verification

Privacy-Preserving KYC: How to Implement ZK Proofs for Identity Verification

A passport scan gets copied more often than it gets checked. A bank takes one, an exchange takes one, a landlord takes one, and each copy outlives the reason it was collected. Most of those checks are really asking one or two yes-or-no questions: old enough, resident here, is this you. A document answers all of them at once and then keeps answering them from someone else's database.

Zero-knowledge proofs change the shape of the exchange. You stop collecting documents and start accepting proofs that a specific statement about a document is true. The user keeps the passport. You learn "over 18" and nothing more. That sounds like a small change. It rewires what you store, what a breach can expose, and what your onboarding flow has to do.

Start With the Statement, Not the Document

Write down what you actually need to know. Not "verify identity," but the predicates: the user is over 18; the user holds a government-issued credential that has not been revoked; the user is resident in a permitted jurisdiction; this user has not already opened an account here.

Each of those is a boolean. A zero-knowledge proof lets the holder of a credential convince you that a boolean is true without showing you the underlying values. You learn the result. You do not learn the name, the birth date, the document number or the address.

The proof should answer your question, not enlarge your database.

That still leaves a design decision. What does the verifier learn, exactly? Usually the answer should be true or false, plus whatever the protocol unavoidably reveals: the issuer's identity, the credential type, the time of the proof. Anything beyond that needs a reason you can defend.

Three roles, one trust shift

An issuer signs a credential: a government signs an attribute set, a bank signs an account-holder attestation, an employer signs an employment claim. The holder stores that credential somewhere they control. The verifier, which is you, checks a proof derived from it. You are no longer judging whether a paper document looks genuine. You are deciding which issuers you accept and whether a cryptographic signature chain is valid. That is a cleaner problem, but it means your integration depends on issuers existing for the attributes you need.

The Building Blocks You Need

Most implementations combine a handful of pieces. The exact stack depends on your issuers and your latency budget.

  • A credential format with selective disclosure. BBS+ signatures, SD-JWT VCs, AnonCreds and SNARK-friendly signature schemes all let a holder reveal some attributes and prove things about the rest.
  • Commitments. The holder commits to values up front and can open one later if required, without exposing the others.
  • Predicate circuits. Small, audited circuits for range checks such as age above a threshold, set membership such as residency in a permitted list, and equality of hashed identifiers.
  • Scoped nullifiers. A value derived from the credential and your service name that proves the credential has not been used here before, without creating an identifier that follows the user between businesses.
  • Revocation. Issuers change their minds, and credentials expire. Decide how you check freshness, how often, and what you do when the issuer's endpoint is unreachable.

Where the Proof Gets Generated

Client-side proving. The proof is generated on the user's device and only the proof travels to your server, so private data never leaves the phone. This is the strongest privacy posture and the slowest, especially on older hardware.

Hosted proving. A service generates proofs on the user's behalf. The experience is smooth and the trust model is worse, because that service sees the witness. Reasonable for low-sensitivity predicates, poor for anything you would not want a vendor to hold.

Hybrid. The issuer issues into a wallet, the wallet proves, your backend verifies. Verification is cheap compared with proving, so a modest server handles a lot of traffic while the expensive step stays on the user's phone. Budget a few seconds and show progress rather than a frozen screen.

What You Can and Cannot Prove

Reliable today: age thresholds, jurisdiction membership, credential validity and expiry, institutional affiliation, uniqueness within your service, and account or balance thresholds with careful design.

Needs extra machinery or caution:

  • Liveness. A proof says nothing about who is holding the phone right now. Pair it with a device binding or a session check.
  • Binding a credential to a person. A credential proves an attribute, not that the presenter owns it. Bind to a device key or a local biometric unlock.
  • Live sanctions lists. Screening against a current list means querying something current, which leaks the query pattern. Handle it with a trusted oracle and an explicit threat model.
  • Cross-service correlation. If a nullifier is derived the same way every time, you have rebuilt a tracking identifier.

A Practical Integration Checklist

  1. Write the predicate list before you write code, and keep it to what your compliance team can defend as necessary.
  2. Choose issuers you can realistically integrate with. Check their signing keys, revocation endpoint and key rotation policy.
  3. Pick the credential format based on those issuers, not the other way around.
  4. Decide where proofs are generated and how long the user waits.
  5. Scope nullifiers per verifier: include your service identifier, and rotate the scheme if you change jurisdictions.
  6. Plan revocation and expiry handling, including issuer downtime.
  7. Store proof results, never witnesses. Log the predicate, the issuer, the timestamp and only the identifiers your own rules require.
  8. Test the ugly cases: replayed proofs, a credential scoped to another verifier, a revoked credential, one user holding two credentials, a circuit that fails closed.

The Parts Regulation Still Owns

Data minimisation helps, but it does not replace your obligations. Many jurisdictions require identity records to be retained for a set period, and a proof result may not satisfy that. Some rules expect you to be able to identify a customer again on request. Before you assume a proof is enough, get advice from qualified counsel in each jurisdiction you operate in.

Two engineering risks sit alongside the legal ones. Circuits can be wrong: a buggy predicate circuit can leak parts of the witness or accept a false statement, so use audited libraries and get custom circuits reviewed independently. And your verifier is now a security boundary. If it logs the raw credential or the witness, you have rebuilt the database you were trying to avoid.

Start With One Predicate

Pick the highest-volume, lowest-sensitivity check you run. Age verification on a signup form is the classic starting point; jurisdiction eligibility is another. Run the proof path alongside your existing review, keep the fallback, and measure how many users finish it.

Expect friction in the first month. Proving takes seconds, users need to understand what they are proving, and support teams need an answer for "why can't I just upload my ID." The answer is a good one: the stored copy of that ID is the thing that hurts you when it leaks. Once a single predicate works, adding residency or uniqueness is mostly a matter of circuits and issuer relationships rather than new architecture.

Photo: SookyungAn / Pixabay

Related Posts