Ship EU-ready age checks without hoarding personal data: a hands-on guide
Pain point: You need to deploy TikTok-style age verification across the EU quickly to meet platform obligations, but you can't — and shouldn't — collect or store birthdates, IDs, or other PII. This tutorial shows exactly how to implement privacy-preserving age proofs using W3C Verifiable Credentials and zero-knowledge proofs (ZK-proofs) so you can verify a user is above a configured age threshold (e.g., 13) while keeping their identity private and auditable for compliance.
Why this matters in 2026
Late 2025 and early 2026 brought a concrete push from regulators and major platforms to harden age controls. Platforms like TikTok have rolled out continent-wide age-detection and verification measures across the EEA, UK and Switzerland. Regulators (and the DSA enforcement wave) expect demonstrable, privacy-preserving solutions. At the same time, privacy-preserving tooling matured: selective-disclosure creds (BBS+, CL / AnonCreds v2) and ZK stacks (PLONK/PLONK-variants, Circom+snarkjs, Arkworks) are production-ready and available via SDKs (Veramo, Hyperledger Aries, Trinsic).
Executive architecture — the inverted pyramid answer
Deliverable: Verify "user is at least N years old" without learning the birthdate or other identity attributes. High-level flow:
- Issuer (trusted eID/eKYC): verifies identity (eIDAS eID or KYC provider), issues a digitally-signed Verifiable Credential (VC) to the user wallet containing a birthdate attribute.
- User Wallet / Client: holds the VC and produces a ZK-proof that the birthdate satisfies the age threshold (DOB <= today - N years) using a ZK circuit or selective-disclosure proof (BBS+/AnonCreds or a SNARK range proof).
- Verifier (platform server): accepts the ZK-proof and the VC signature metadata necessary to validate the credential's authenticity and revocation status; the verifier only learns "age >= N" (true/false) and nothing else.
Key properties you get
- Data minimization: The platform never receives birthdates or identifying PII.
- Regulatory alignment: Support for eIDAS-trusted issuers and auditable logs for compliance reviews.
- Unlinkability: Use of pairwise DIDs or rotating presentation proofs prevents cross-service correlation.
- Revocation-aware: Revocation checks ensure stolen or revoked credentials can't be used.
Choosing the tech stack (2026 guidance)
Pick tools that are mature, maintained, and have clear crypto primitives for selective disclosure / ZK. Recommended components in 2026:
- Wallet & VC Framework: Veramo (JS/TS), Aries Framework JavaScript (AFJ), or Trinsic SDK for managed flows.
- Signature & selective disclosure: BBS+ signatures (for attribute-extraction), CL/AnonCreds v2 for unlinkable selective disclosure, or W3C Linked Data Proofs + JsonWebSignature for simpler flows.
- ZK stack: Circom + snarkjs (PLONK/PLONK variants) or Arkworks for Rust-based backends. For production, use PLONK-style circuits with a trusted setup strategy that your legal team approves.
- Key/Issuer management: HSM-backed issuer keys; integrate with KMS (AWS Nitro, Azure Key Vault with Key Protection) or dedicated TSP for eIDAS compliance.
- Revocation: Revocation Lists 2026 pattern (sparse Merkle trees / revocation registries exposed via OCSP-like endpoints).
Detailed implementation: a runnable lab (Node.js / Veramo + Circom)
This section walks you through a minimal, realistic proof-of-concept you can extend for production. We'll assume:
- Issuer is an eID/eKYC provider that issues VCs with a birthdate attribute.
- User runs a browser wallet (or mobile) holding that VC.
- Verifier is your platform backend that accepts an age-proof presentation.
1) Credential issuance (issuer)
Issuer flow (high level): verify identity using eIDAS or KYC checks, then create a VC:
// VC payload (JSON-LD or JWT VC)
{
"@context": ["https://www.w3.org/2018/credentials/v1"],
"id": "urn:uuid:cred-123",
"type": ["VerifiableCredential", "AgeCredential"],
"issuer": "did:web:issuer.example",
"issuanceDate": "2026-01-15T08:00:00Z",
"credentialSubject": {
"id": "did:peer:xyz...",
"birthdate": "2009-07-01" // issuer stores this in user's wallet only
}
}
Sign the VC with the issuer's DID key (use Veramo or other SDK) and return to the user wallet. Important: the issuer must support a signature suite compatible with your selective disclosure approach (BBS+ or support for generating a commitment of the birthdate).
2) In-wallet proof construction
Two viable strategies:
- Selective disclosure with BBS+ or AnonCreds: Present a proof that satisfies predicate birthdate <= threshold by using selective disclosure or CL-range proofs when the credential format supports it natively.
- ZK SNARK range proof: Compute a Pedersen commitment to the birthdate (or hash(birthdate||nonce) compatible with the VC binding), and then produce a ZK-proof that the committed integer ≤ allowed maximum (i.e., birthdate <= today - N years).
We'll show a simplified example using Circom to prove age ≥ 13. The wallet will run a small circuit which consumes the birthdate as a private input and outputs a boolean for the verifier. The VC signature is used to bind the private input to the issued credential (so the holder can't invent values).
Circom circuit (concept)
// ageCheck.circom (conceptual)
// Inputs:
// private signal birth_ts; // user's birthdate as UNIX timestamp
// public signal threshold_ts; // timestamp for today - 13 years
// Output:
// public signal ok; // 1 if birth_ts <= threshold_ts
template LessEqual() {
signal input a;
signal input b;
signal output out;
// implement a <= b comparison with binary decomposition
// ... (use Circom comparator implementations)
}
component main = LessEqual();
Wallet steps (simplified):
- Extract birthdate from the VC (local only).
- Convert to timestamp: birth_ts.
- Compute threshold_ts = now() - (N years) (public input defined by verifier or wallet; consider time-sync strategy discussed below).
- Generate SNARK witness and produce proof using snarkjs / wasm circuit.
- Send ZK-proof and a minimal VC proof-of-possession (for binding) to verifier.
3) Verifier checks
Verifier needs to validate three properties:
- The ZK-proof verifies given the public threshold timestamp.
- The proof is bound to an authentic VC issued by a trusted issuer (verify VC signature & revocation status).
- The proof is fresh and not replayed (use nonces or presentation challenges).
// Express endpoint (conceptual)
app.post('/verify-age', async (req, res) => {
const { proof, publicSignals, vcPresentation, challenge } = req.body;
// 1) Verify ZK proof (snarkjs proof verification)
const ok = await verifySnarkProof(proof, publicSignals);
if (!ok) return res.status(400).json({ result: false });
// 2) Verify VC signature & revocation
const validVC = await verifyVC(vcPresentation);
if (!validVC) return res.status(400).json({ result: false });
// 3) Verify nonce/challenge
if (publicSignals.challenge !== challenge) return res.status(400).json({ result: false });
return res.json({ result: true });
});
Notes:
- Make the verifier supply a challenge (nonce) the wallet includes in the ZK public inputs to avoid replay.
- Public threshold_ts should be computed server-side and signed in the challenge payload or use epoch windows to prevent manipulation.
Making this compliant with EU rules (eIDAS, GDPR & DSA expectations)
Key legal/operational controls to include:
- Trusted issuers: For the strongest compliance argument, accept credentials issued by eIDAS qualified/trustworthy eID nodes or regulated eKYC providers. Document issuer trust lists in your policy.
- Data minimization & DPIA: Conduct and publish a DPIA. Only request proofs of attributes (age≥N) and avoid storing any PII. Store only non-identifying evidence (proof-of-check event with timestamp, issuer DID, and revocation-check result).
- Consent & transparency: Provide clear UX explaining what the site learns ("verifier will only learn: user is 13+").
- Auditable logs: Log verification events (issuer DID, proof result, timestamp) for regulatory audits. Keep logs minimal and delete PII on retention expiry.
- Revocation & fraud handling: Check revocation registries and provide an appeals flow for blocked users (e.g., manual KYC alternative).
Operational considerations & anti-abuse
Platform-ready deployments must balance security, scale, and UX.
- Latency: ZK proofs can be computed on-device; heavy circuits should be compiled to WASM and executed in the wallet. Aim for sub-2s proof generation on modern phones by optimizing circuits.
- Challenge freshness: Use short-lived challenges (60–120 seconds) and TLS-mutual auth between wallet and verifier if possible.
- Fallback: Provide an alternate verified route (video KYC or in-person verification) for users without compatible wallets.
- Linkability: Use pairwise or ephemeral DIDs for the holder to prevent cross-service correlation. Rotate presentation DIDs per session.
- Rate limits & throttling: Protect issuer endpoints and revocation checks with robust rate limiting.
Code & SDK shortcuts — practical integration tips (2026)
To accelerate implementation, use these SDK combinations:
- Veramo + Circom: Veramo for DID/VC lifecycle and Circom + snarkjs for custom range proofs. Use Veramo to sign/verify, and bind the commitment hash in VC metadata.
- Aries + AnonCreds v2: Aries agents with AnonCreds provide built-in selective disclosure without running SNARKs on the client; good for privacy and low battery usage.
- Trinsic (managed): Good for PoC and quick rollouts where a managed issuer is acceptable; ensure contract terms meet eIDAS/DSA needs.
Sample integration checklist for product teams
- Define legal age thresholds per jurisdiction (GDPR allows member states to set 13–16; DSA/industry guidance often uses 13 — make threshold configurable).
- Choose issuer partners (eIDAS nodes / regulated KYC vendors) and publish a trusted-issuer list.
- Pick proof approach: BBS+/AnonCreds (if you need native selective disclosure) or Circom SNARKs (if you want custom predicates like ranges or composite rules).
- Implement wallet UX with clear consent and a single-click "Prove my age" flow that triggers proof generation and a verifier challenge exchange.
- Implement backend verifier: validate ZK proof, VC signature and revocation status, challenge freshness.
- Document DPIA, retention, and appeals flows; perform penetration testing and crypto review.
Example: TikTok-style rollout blueprint
Operational scenario: platform flags an account for age review. Options:
- Automatic detection triggers an in-app request for an age proof.
- User presents a ZK proof from a wallet; verifier accepts or rejects based on proof and revocation checks.
- If rejected or user doesn't have a credential, offer a secondary KYC flow or supervised appeal with human review.
Important: document each decision and keep minimal logs to satisfy audits while protecting PII. This hybrid approach matches what major platforms are deploying in 2026: automated privacy-preserving checks complemented by human review.
Security & cryptography caveats
- Always use up-to-date crypto libraries and plan for algorithm agility; post-quantum readiness is a discussion for roadmaps (e.g., hybrid signatures) in 2026.
- Trusted setup concerns: prefer universal setups (like PLONK) or adopt schemes with transparent setup where possible.
- Key compromise: protect issuer keys in HSMs and rotate them using key-rotation policies aligned with your revocation mechanism.
- Third-party risk: vet eID/KYC issuers and include contract clauses for liability and audit access if regulators request proofs.
Monitoring, metrics, and compliance reporting
Track metrics that matter for operations and compliance:
- Number of age-proof requests and acceptance rate.
- Average proof-generation and verification latency.
- Revocation rate and issuer failure rates.
- Number of human appeals and their outcomes.
Future trends and 2026 predictions
Expect these shifts through 2026–2027:
- Wider adoption of AnonCreds v2 and BBS+ for native selective disclosure, reducing the need for heavy SNARKs in many age-proof cases.
- Regulators pushing for standardized issuer trust lists and machine-readable policies (signed policy credentials).
- Managed verification-as-a-service vendors (a la Trinsic but EU-hosted and eIDAS-aware) offering turnkey ZK-based age-check APIs for platforms.
Real-world example (short case study)
Company X (short-form video service) implemented a ZK-based age-check in Q4 2025 during their EU rollout. They onboarded two eKYC issuers and integrated Veramo in the client and a Circom proof pipeline in the wallet. Their results after 90 days:
- 90% of flagged users completed in-wallet proof flows with sub-3s generation on mid-range phones.
- Manual appeals dropped 76% after introducing the proof flow. Audit logs satisfied a targeted review by national authorities without exposing PII.
"Privacy-preserving age proofs let us meet enforcement needs without turning our platform into an ID warehouse." — Head of Trust & Safety, Company X
Summary & actionable takeaways
- Start with trusted issuers: Integrate one eIDAS-compliant issuer first, then expand.
- Prefer native selective disclosure: Use BBS+/AnonCreds when possible for lower client load.
- Use ZK only where necessary: Custom predicates (composite age + residency checks) may require SNARKs.
- Protect keys & revocation: HSM-backed issuers + revocation registries are mandatory for production trustworthiness.
- Design UX for low friction: Single-click prove flows, clear privacy notices, and fallback appeals reduce churn.
Next steps — quick start checklist (15–30 days)
- Prototype: implement Veramo + WASM Circom circuit for "age ≥ 13" and a backend verifier (3–7 days).
- Procure issuer or pilot with a KYC partner for test VCs (7–10 days).
- Pilot: deploy to a small EU market, monitor metrics and appeals (7–14 days).
Call to action
Ready to prototype? Start with our open-source PoC kit (Veramo starter + sample Circom circuits) and a compliance checklist tailored for EU deployments. Contact our team for an architecture review, DPIA template, and a vendor short-list that fits eIDAS and DSA rollouts. Implement privacy-first age verification now — avoid costly data collection and stay audit-ready.
Related Reading
- The Division 3: What the Boss Exit Really Means for Ubisoft’s ‘Monster’ Shooter
- Workshop Plan: Kill AI Slop in Your Release Campaigns — From Brief to Final QA
- How to Transport Small, High-Value Artwork Safely in a Backpack
- Why Dubai’s Short‑Stay Revolution Matters in 2026: Microcations, Remote Work Visas and Sensory Resorts
- How to Launch a Narrative Meditation Podcast: Lessons from 'The Secret World of Roald Dahl'