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.2.tar.gz (520.7 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.2-py3-none-any.whl (37.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: zk_age_verifier-0.1.2.tar.gz
  • Upload date:
  • Size: 520.7 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.2.tar.gz
Algorithm Hash digest
SHA256 31157fa5b025d255c04555cdde19b393d7f8aa64d3efb776964842fa6f468b9f
MD5 910e3cb171a63e664ba76af8f7f122d7
BLAKE2b-256 0b088210c85572c751f8004f17e8811735b686b2077c61c27d12c7760fcf0769

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: zk_age_verifier-0.1.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 195ee085da65f2c6d1ff25a1a692ce019069957a38c3d659777f33391899f51f
MD5 eb093cef45ef99c502c2c01a7ab303de
BLAKE2b-256 93d7f436df0496820f09561a27222dd50902a23cb7e0a5f4091465537038cdbd

See more details on using hashes here.

Provenance

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