The Problem: Age Verification vs. Privacy
Age verification on the internet has always been a privacy nightmare. Current solutions force users to choose between:
- Manual birthdate entry: Zero security, easily bypassed, creates data liability
- ID document upload: Massive privacy violation, expensive ($0.50-2 per verification), creates honeypots for identity theft
- Third-party KYC services: Tracking, data sharing, regulatory complexity
The fundamental tension: You need to verify someone is 18+ without actually knowing their birthdate.
This is especially critical in the EU, where:
- GDPR requires data minimization and purpose limitation
- DSA (Digital Services Act) mandates age verification for platforms
- eIDAS 2.0 is rolling out digital identity wallets across member states
Traditional age verification solutions violate GDPR’s data minimization principle by collecting unnecessary personal data.
Why Zero-Knowledge Proofs Solve This
Zero-knowledge (ZK) proofs are a cryptographic primitive that lets you prove a statement is true without revealing why it’s true.
Applied to age verification:
- Traditional: “Here’s my birthdate: 1990-06-15” → Verifier learns exact age
- Zero-knowledge: “I’m >= 18” → Verifier learns only the binary fact, nothing else
How it works technically:
- Credential issuance: A trusted issuer (government ID provider, bank) signs your birthdate with EdDSA
- Proof generation: Your device generates a ZK-SNARK proving
currentYear - birthYear >= 18without revealingbirthYear - Verification: The verifier checks the cryptographic proof without learning your birthdate
Key properties:
- ✅ Zero knowledge: Birth year never leaves your device
- ✅ Sound: Cryptographically impossible to fake (128-bit security)
- ✅ Binding: Proof is tied to a specific credential signed by a trusted issuer
- ✅ Non-interactive: No back-and-forth protocol, just submit proof once
OpenID4VP for Interoperability
Zero-knowledge is powerful, but proprietary ZK systems create vendor lock-in. Enter OpenID4VP (OpenID for Verifiable Presentations).
OpenID4VP is a W3C/OIDF standard that defines how:
- Verifiers request credentials
- Wallets present credentials
- Issuers provide credentials
Why this matters:
- Any OpenID4VP-compliant wallet works with any verifier
- Follows OAuth 2.0 patterns familiar to developers
- Integrates with existing SSO infrastructure
- Aligns with EU Digital Identity Wallet (EUDI Wallet) roadmap
zk-id is the first OpenID4VP implementation with true zero-knowledge proofs. Other implementations use:
- Selective Disclosure JWT (SD-JWT): Reveals birthdate, not zero-knowledge
- Anonymous Credentials: Complex setup, limited adoption
Demo Walkthrough
We’ve built a fully functional demo that runs locally in 3 minutes:
Step 1: Issue a Credential (30 seconds)
cd examples/openid4vp-demo
npm start
Visit http://localhost:5000. On the right panel (Browser Wallet):
- Enter a test birthdate
- Click “Issue Credential from Issuer”
- Credential appears in wallet
What happened: The issuer server signed your credential using EdDSA (same crypto as Signal, Telegram).
Step 2: Create Verification Request (15 seconds)
On the left panel (Verifier):
- Set minimum age (default: 18)
- Click “Create Authorization Request”
- QR code appears
What happened: The verifier created an OpenID4VP authorization request following DIF Presentation Exchange v2.0.
Step 3: Generate & Verify Proof (60 seconds)
On the right panel:
- Click “Generate & Submit Proof”
- Wait ~45 seconds (first proof loads circuit)
- Result appears: ✅ Verified
What happened:
- Your browser loaded a ZK circuit (5MB WASM + zkey)
- Generated a ZK-SNARK proving
age >= 18 - Packaged it as a W3C Verifiable Presentation
- Submitted to verifier via OpenID4VP callback
- Verifier verified the proof cryptographically
Key insight: The verifier confirmed you’re 18+ without learning your birthdate.
EU Regulatory Alignment
GDPR Compliance
zk-id satisfies multiple GDPR requirements:
Article 5(1)(c) - Data Minimization:
“Personal data shall be adequate, relevant and limited to what is necessary.”
✅ Only the binary fact (age >= 18) is transmitted. Birthdate never leaves the device.
Article 25 - Privacy by Design:
“The controller shall implement appropriate technical and organisational measures… designed to implement data-protection principles.”
✅ ZK proofs are privacy by design at the cryptographic layer.
Article 9 - Special Categories:
“Processing of personal data revealing… health, sex life or sexual orientation… shall be prohibited.”
✅ Age-gating adult content without collecting “special category” data.
DSA (Digital Services Act)
Article 28 - Protection of Minors:
“Providers of online platforms accessible to minors shall put in place appropriate and proportionate measures to ensure a high level of privacy, safety, and security of minors.”
Traditional solutions:
- Upload ID → Creates data breach risk, violates Article 28’s “high level of privacy”
- Manual birthdate entry → Easily bypassed, fails “appropriate measures”
zk-id:
- ✅ High privacy (ZK proof)
- ✅ Strong security (128-bit soundness)
- ✅ Proportionate (no data collection)
eIDAS 2.0 & EU Digital Identity Wallet
eIDAS 2.0 (effective 2026) mandates EU member states provide digital identity wallets. These wallets will:
- Store government-issued credentials (passport, driver’s license)
- Support selective disclosure and privacy-preserving proofs
- Use OpenID4VP and ISO mDOC standards
zk-id’s positioning:
- ✅ OpenID4VP-compliant (interoperable with EUDI Wallets)
- ✅ Supports selective disclosure (BBS+ credentials)
- ✅ Zero-knowledge proofs (next-gen privacy)
When EUDI Wallets roll out, zk-id-powered verifiers will work out-of-the-box.
Try It Yourself
1. Run the Demo
git clone https://github.com/star7js/zk-id.git
cd zk-id
npm install
npm run compile:circuits
npm run setup:circuits
cd examples/openid4vp-demo
npm start
Visit http://localhost:5000 and walk through the 3-step flow.
2. Embed in Your Site (3 Lines)
For a quick integration, use our age-gate widget:
import { ZkIdAgeGateWidget } from '@zk-id/age-gate-widget';
ZkIdAgeGateWidget.init({
verificationEndpoint: 'https://your-verifier.com/auth/request',
minAge: 18,
onVerified: () => showRestrictedContent(),
});
See age-gate widget demo.
3. Build Your Own Verifier
import { OpenID4VPVerifier, ZkIdServer, InMemoryIssuerRegistry } from '@zk-id/sdk';
// Initialize standard zk-id verifier
const zkIdServer = new ZkIdServer({
verificationKeyPath: './age-verify_verification_key.json',
issuerRegistry: new InMemoryIssuerRegistry(),
});
// Wrap with OpenID4VP protocol
const verifier = new OpenID4VPVerifier({
zkIdServer,
verifierUrl: 'https://your-verifier.com',
verifierId: 'your-verifier-id',
callbackUrl: 'https://your-verifier.com/openid4vp/callback',
});
// Create authorization request
const authRequest = await verifier.createAuthorizationRequest({
responseMode: 'direct_post',
presentationDefinition: {
id: 'age-verification',
input_descriptors: [
{
id: 'age-credential',
constraints: {
fields: [
{
path: ['$.credentialSubject.ageOver18'],
filter: { type: 'boolean', const: true },
},
],
},
},
],
},
});
// Verify presentation
const result = await verifier.verifyPresentation(vpToken, state);
See examples/openid4vp-demo/src/verifier.ts for a complete working example.
Technical Deep Dive
To be expanded with human-written content:
- Circuit architecture (Groth16, Circom)
- Signature verification (EdDSA in ZK)
- Commitment schemes (Poseidon hash)
- Presentation Exchange mappings
- BBS+ for selective disclosure
- Range proofs and predicate logic
Roadmap
- Q1 2026: Mobile SDK (React Native, Flutter)
- Q2 2026: EUDI Wallet integration
- Q3 2026: Custom predicate circuits (nationality, income ranges)
- Q4 2026: Recursive proofs for multi-credential composition
Get Involved
- Try it: Live Demo
- Build it: Integration Guide
- Contribute: GitHub
- Discuss: GitHub Discussions
Questions? Open an issue on GitHub or join the discussion.