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 page 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 of the page that runs the credential call.
  • 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.0.tar.gz (522.2 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.0-py3-none-any.whl (37.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: zk_age_verifier-0.1.0.tar.gz
  • Upload date:
  • Size: 522.2 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.0.tar.gz
Algorithm Hash digest
SHA256 ffc4697b484e20c541d601d5f28319f6262b670c68ab915e2275897496f5a60b
MD5 3266f8fc223d5101fefbdf8ec53b7909
BLAKE2b-256 725997c1fb0b4a8bdcc5751d61f15fe1ebe831b0c6b2179eb6b921984c9b165d

See more details on using hashes here.

Provenance

The following attestation bundles were made for zk_age_verifier-0.1.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: zk_age_verifier-0.1.0-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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c2e2ddf60d63842355e3e633f7bba3be03e7d54eb986ad0f342cb31091f1eff1
MD5 2cccc7e7a2845aaac2f67cccb524461e
BLAKE2b-256 ab49813e84c8f70fa4c7a85e1113c2a093d5225b21989873370e6ef07c889e44

See more details on using hashes here.

Provenance

The following attestation bundles were made for zk_age_verifier-0.1.0-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