Skip to main content

Verifier service for EU age-verification proofs (Longfellow ZK over mdoc, W3C Digital Credentials API)

Project description

zk-age-verifier

zk-age-verifier is a verifier service for EU age verification, accepting Longfellow zero-knowledge proofs over mdoc through the W3C Digital Credentials API. It runs as a sidecar HTTP service beside a consumer backend. A verdict contains one boolean per requested check. The service has no authentication and is intended to be reachable only from the consumer backend, not the browser or internet. It is experimental and unstable.

CI Docs PyPI License: Apache 2.0

Installation

pip install zk-age-verifier
uv add zk-age-verifier
docker pull ghcr.io/pipe23-org/zk-age-verifier:latest

Usage

The verifier needs a configured origin and at least one trust source.

[service]
expected_origin = "https://av.example"

[[trust.sources]]
pem = "/etc/zk-age-verifier/anchors"

First start generates the ZK circuit and caches it on disk.

python -m zk_age_verifier --config config.toml

POST /sessions opens a session and returns the navigator.credentials.get() argument under transports.dc.

$ curl -X POST http://127.0.0.1:8000/sessions \
    -H 'content-type: application/json' -d '{"checks": ["age_over_18"]}'
{"session_id": "tmcdOPmo7oCgd4AmmMFyYg",
 "transports": {"dc": {"digital": {"requests": [{"protocol": "org-iso-mdoc",
   "data": {"deviceRequest": "omd2ZXJzaW9u…", "encryptionInfo": "gmVkY2FwaaJ…"}}]},
   "mediation": "required"}},
 "expires_at": "2026-07-20T09:34:22.089682Z"}

The consumer backend relays the wallet response to POST /sessions/{session_id}/presentation. A verified and a failed verification both return 200.

POST /sessions/{session_id}/presentation
{"response": "<wallet response, base64url>"}

{"state": "verified", "result": {"age_over_18": true}, "verified_at": "<iso8601>"}
{"state": "failed", "reason": "decrypt-failed"}

GET /health returns {"status": "ok"} while the process is up.

GET /debug/transcript/{session_id} returns the transcript inputs stored for a session — the origin and the encryptionInfo string — with the handover hash and session-transcript bytes reconstructed from them, hex-encoded. It is a development route, unauthenticated like the rest of the service.

Configuration

Two TOML tables, [service] and [trust], passed with --config.

  • expected_origin (required) — the exact scheme://host[:port] origin the presentation asserts.
  • session_ttl_seconds (default 300) — session lifetime.
  • session_cap (default 1000) — live-session limit; POST /sessions returns 503 at the cap.
  • timestamp_skew_seconds (default 300) — proofs with a timestamp older than this fail stale-proof.
  • cors_allowed_origins (default []) — origins for which CORS headers are emitted.
  • circuit_cache_dir (default $XDG_CACHE_HOME/zk-age-verifier/circuits) — where the generated circuit is cached.
  • trust.sources (required) — non-empty list; each entry sets one of pem (a PEM file or directory of issuer CA certs) or etsi_xml (an ETSI trusted-list URL).

A presented document-signer certificate must carry the keyUsage extension asserting digitalSignature; an anchor accepted as the issuer of a chained leaf must assert keyCertSign.

Configured trust sources merge into one anchor set. Any anchor in that set can vouch for a certificate that signs age credentials. The certificate layer carries no required marker restricting what an anchor's certificates may sign. ETSI TS 119 412-6 clause 6 places no type indicator on EAA signing certificates. The trust.sources list must name only anchors intended to vouch for age credentials. A mixed-purpose or broad list authorizes every CA on it as an age-credential issuer.

Environment variables ZK_AGE_VERIFIER_<SECTION>__<KEY> override scalar values; lists and nested tables come from the TOML file only. Environment variables take precedence over the TOML file, which takes precedence over the defaults.

Documentation

Full documentation: https://zk-age-verifier.readthedocs.io/

Development

uv sync
uv run pytest

make test-live runs the suite against a running server over HTTP. make test-container runs it against the built container image.

Status

You should not rely on this code.

  • End-to-end testing covers Chrome on one Android 16 device.
  • No rate limiting.
  • The session store is in-process.

License

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zk_age_verifier-0.1.1.tar.gz (522.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zk_age_verifier-0.1.1-py3-none-any.whl (37.3 kB view details)

Uploaded Python 3

File details

Details for the file zk_age_verifier-0.1.1.tar.gz.

File metadata

  • Download URL: zk_age_verifier-0.1.1.tar.gz
  • Upload date:
  • Size: 522.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for zk_age_verifier-0.1.1.tar.gz
Algorithm Hash digest
SHA256 58a64285975a169c7556290127936fc6af3d5666ba86406230a0eb1b6bdbe6ab
MD5 7706fc92e839e82b69ec06caf5a711eb
BLAKE2b-256 14b2f172dfd57876ed01e2f3ad938b76b42a3f1c8b5224134becf9f3f74d42de

See more details on using hashes here.

Provenance

The following attestation bundles were made for zk_age_verifier-0.1.1.tar.gz:

Publisher: release.yml on pipe23-org/zk-age-verifier

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zk_age_verifier-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: zk_age_verifier-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 37.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for zk_age_verifier-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 38cc119654f16aec268fba70a1ff36ab912ee0745bbd8c40d649b0a11fb17d0c
MD5 a82aad2347f7060509f882f44a48d3f1
BLAKE2b-256 1c2af27f6a8095faf0b88a6ce237ecef2901e38861052ce959d3c2bacefa27c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for zk_age_verifier-0.1.1-py3-none-any.whl:

Publisher: release.yml on pipe23-org/zk-age-verifier

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page