Skip to main content

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 verified 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"
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.
  • pylongfellow.backend (default google-cpp) — the proof engine, google-cpp or isrg-rust; an unknown or unbuilt name fails startup.
  • 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 whose timestamp differs from the current time by more than this fail stale-proof.
  • cors_allowed_origins (default []) — origins for which CORS headers are emitted.
  • 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 https URL or file path).

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
uv run ruff check
uv run ruff format --check
uv run mypy

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.

  • No rate limiting.
  • The session store is in-process.
  • The proof is verified against an empty device-namespace map; the ZK response format carries no field holding the map the wallet signed.

License

Apache-2.0.

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.4.0.tar.gz (1.1 MB 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.4.0-py3-none-any.whl (337.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: zk_age_verifier-0.4.0.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zk_age_verifier-0.4.0.tar.gz
Algorithm Hash digest
SHA256 3066716bfc7163cd3e41c803bf7f3de4d7474c555b60efc9a5cf912bc3b5c9b9
MD5 405087501a6b53b8285178806f776d79
BLAKE2b-256 da7595144f64500159410f722c0d763d72b8ec98eaf08792ce7151843f14e29b

See more details on using hashes here.

Provenance

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

File metadata

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

File hashes

Hashes for zk_age_verifier-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ca6646ba4ff48b61ec20b23a81d8657bb134c1a45639f3969ede7c034fb59a5
MD5 711e4229a9c4da58a71bfdbc90807408
BLAKE2b-256 7195c9ead896aa4fc2508523c18ce85f2764802f4502e9c12fd8d09fffe21a7c

See more details on using hashes here.

Provenance

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

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page