espuni·
← Age verification demo
Zero-Knowledge age proof

A Zero-Knowledge proof (longfellow-zk, EU AV Blueprint Annex A §A.8 — the scheme Google Wallet generates) proves age_over_18 from a real mdoc credential without revealing the credential, the issuer signature or anything else. Below is how we built and validated our verifier, in three stages — each one a step closer to a real wallet. Everything runs the native verifier server-side; every proof is downloadable so you can re-verify it yourself (bottom).

~360 KBper proof <1 sserver-side verify 1 bitrevealed
1 · Does the verifier actually work?

The foundation. Our native longfellow-zk verifier runs against the scheme's own official test vectors: it must accept a genuine proof and reject every kind of forgery. This is the proof-of-concept the whole thing started from.

Genuine proof → accepted

A real proof (credential eu.europa.ec.av.1, age_over_18 = true) verified against the v7 circuit. Proves the verifier accepts a valid proof.

Tampered proof → rejected

The same valid proof with one byte flipped in its ~360 KB. Proves integrity: any alteration breaks verification.

Replayed session → rejected

A valid proof presented with a different session's transcript. Proves session binding: a proof can't be reused elsewhere.

Under-18 claiming adult → rejected

A real credential with age_over_18 = false trying to assert true. Proves statement binding: you can't prove what the credential doesn't say.

2 · From test vectors to a real credential

No more canned data. Pick any age: a demo issuer mints a real ISO 18013-5 credential on the spot, and we prove and verify it end to end — the full issue → prove → verify pipeline under our control.

Issue → prove → verify

Move the slider to any age. A demo issuer mints a real ISO 18013-5 mdoc on the spot (fresh keys, fresh session), the prover builds the ~360 KB proof from it, and the verifier checks the statement "over 18". Under 18 the proof simply cannot claim adult — try it.

25
3 · The real production flow ✓ interop verified with the reference AV app

The exact Relying-Party pipeline over the Digital Credentials API (ISO 18013-7): we build the encrypted request a browser hands to a wallet, the wallet answers with an encrypted proof, and we decrypt and verify it. This is real — verified end to end against the official EU AV reference app presenting a genuine Proof-of-Age credential (issuer "Age Verification Reference Implementation"). "Run the wallet flow" simulates the wallet for visitors without a phone; "With your phone" runs it for real.

Wallet ↔ RP over DC API

The full exchange: we build the DC API request (zkRequest + an HPKE key), the wallet answers with an encrypted zkDocuments response, and we decrypt it, take the issuer key from the credential's certificate and verify the proof — transcript "dcapi", HPKE P-256/AES-128-GCM, exactly as the reference AV app does it. The verdict has three gates: the proof verifies, its timestamp is fresh, and the issuer's Document Signer is on the real EU AV Trusted List (ETSI TS 119 612, signature pinned to the EC scheme operator). Pick an age to run it against the simulated wallet — whose ephemeral issuer is deliberately NOT on the list, so it fails the trust gate — or use "With your phone" to present from the real AV app, whose issuer (DS-001) is on the list and should pass all three.

25

On an Android phone with the AV app + a Proof-of-Age credential: this fires a real DC API request, the app answers with an encrypted ZK proof, and we verify it. No age slider — it uses whatever credential the wallet holds. Any wallet speaking the same profile works (mdoc eu.europa.ec.av.1, longfellow zk v7); with a self-signed or test credential expect the first two gates green and the trust gate red — that is the issuer not being on the EU list, not your proof failing.

Transparency: the verifier is real and ours, and the "With your phone" flow has been verified end to end against the official EU AV reference app. The "Run the wallet flow" button simulates the wallet server-side so visitors without a phone can still see it. Either way, every run publishes its evidence below — download the proof and re-verify it yourself, outside our infrastructure.

Native longfellow-zk verifier (Apache-2.0, google/longfellow-zk via the dyne/longfellow-zk build) running server-side. Circuit v7, hash pinned against the published kZkSpecs table. Rate-limited public demo.