Skip to main content

Thin Python binding to Google's Longfellow zero-knowledge mdoc library

Project description

pylongfellow

CI Docs PyPI Python License: Apache 2.0

Overview

A thin Python binding to google/longfellow-zk, the zero-knowledge library that ISO 18013-5 / EUDI mdoc wallets use to prove attributes of a credential without revealing the credential. The underlying scheme is described in Anonymous credentials from ECDSA.

pylongfellow is a cffi wrapper over the library's extern "C" mdoc ABI (lib/circuits/mdoc/mdoc_zk.h). It binds the prove and verify calls and the few structs they take; it does not add a layer of its own. The binding is attribute-agnostic — it proves and verifies (namespace, id, cbor_value) statements over an mdoc and has no notion of what any attribute means. age_over_18 is just the attribute the examples and tests happen to use.

Status: pre-1.0. Upstream is explicitly experimental and its ABI and circuits can change between releases. The vendored upstream is hard-pinned (see Upstream); treat pylongfellow, like its upstream, as not production-ready.

For what each function does and the exact types, read the docstrings — they render to a docs site (mkdocstrings + MkDocs-Material).

Install

pip install pylongfellow

Wheels are published for CPython 3.11–3.14 on Linux x86_64 (manylinux and musllinux). On any other platform pip falls back to the source distribution, which builds the vendored C++ locally and needs a C++ toolchain — see Build from source.

The wheel's runtime dependencies are cffi, cryptography, and cbor2. It ships the google/longfellow-zk backend. The abetterinternet/zk-cred-longfellow (ISRG) backend is not in any wheel: it is source-build only (see Backends), and its zstandard runtime dependency comes from the isrg-rust extra:

pip install pylongfellow[isrg-rust]

What it binds

Pylongfellow is the entry point: a client bound to one backend at construction. Data types and errors are in the pylongfellow.mdoc submodule; the spec-table functions are in the pylongfellow.backends.google_cpp module, which reads the google-cpp backend's compiled-in table. Each client method wraps one native entry point. The wrappers marshal inputs, copy results out, and turn non-success return codes into typed exceptions.

Python Role
Pylongfellow(*, backend) bind a backend, by registry name ("google-cpp", "isrg-rust") or instance
.generate_circuit(spec) produce the compressed circuit a spec names
.load_circuit(spec, compressed) load a circuit into the bound backend, return a CircuitHandle
.prove(handle, mdoc, issuer_pk, transcript, attrs, timestamp) holder side; produce a proof
.verify(handle, issuer_pk, transcript, attrs, timestamp, proof, doctype, *, device_namespaces=None) verifier side; raises on a bad proof, returns on success
google_cpp.circuit_id(circuit) recompute a circuit's canonical id (equals CircuitSpec.circuit_hash); google-cpp only
google_cpp.find_zk_spec(system, circuit_hash) look up a built-in CircuitSpec, or None; google-cpp only

A compressed circuit is bytes: get them from generate_circuit, or read a committed blob from disk. prove and verify do not take the bytes directly. Pass them once to load_circuit, which returns a CircuitHandle bound to a backend, and pass the handle to every prove and verify call. There is no default backend; construction names one. See Backends.

Two C structs are exposed as frozen dataclasses:

  • RequestedAttribute(namespace, id, cbor_value) — "attribute (namespace, id) holds this value." cbor_value is raw CBOR bytes (e.g. b"\xf5" is CBOR true); the binding does no encoding.
  • CircuitSpec(system, circuit_hash, num_attributes, version, block_enc_hash, block_enc_sig) — a circuit's identity. The spec is the small descriptor prover and verifier agree on; circuit_hash (SHA-256 hex) pins which circuit it is. len(attrs) must equal num_attributes. Every backend reads version and num_attributes; the google-cpp backend additionally requires the whole record to match its compiled-in spec table.

A non-success C return code raises mdoc.ProverError, mdoc.VerifierError, or mdoc.CircuitError. All three are subclasses of mdoc.Error, which is a subclass of LongfellowError:

LongfellowError
└── mdoc.Error
    ├── mdoc.ProverError      # .code: mdoc.ProverErrorCode or None
    ├── mdoc.VerifierError    # .code: mdoc.VerifierErrorCode or None
    └── mdoc.CircuitError     # .code: mdoc.CircuitGenerationErrorCode or None

Catch by class. .code carries the specific failure when the backend supplies one: the google/longfellow-zk backend always does, the abetterinternet/zk-cred-longfellow (ISRG) backend leaves it None. Do not branch on the code. The code enums mirror C ints and overlap, so only the exception class says which enum a code is from.

Four functions in pylongfellow.mdoc bind no C entry point: create_credential assembles an ISO 18013-5 DeviceResponse test credential under locally held keys, with caller-controlled issuer-signed claims and device namespaces; create_certificate, sign_device_authentication, and verify_device_authentication are its trust-chain and device-signature companions. They run on cryptography and cbor2 alone; signatures are in the API reference.

Usage

from pylongfellow import Pylongfellow, mdoc
from pylongfellow.backends.google_cpp import find_zk_spec

client = Pylongfellow(backend="google-cpp")

spec = find_zk_spec("longfellow-libzk-v1", circuit_hash)
compressed = client.generate_circuit(spec)       # or Path(...).read_bytes()
handle = client.load_circuit(spec, compressed)

attrs = [mdoc.RequestedAttribute("org.iso.18013.5.1", "age_over_18", b"\xf5")]  # CBOR true

proof = client.prove(handle, credential, issuer_pk, transcript, attrs, now)
client.verify(handle, issuer_pk, transcript, attrs, now, proof, doctype)   # raises on failure

Migrating from 0.2.x: the module-level mdoc functions are gone; operations are methods on a client bound to a named backend. prove and verify no longer take the circuit bytes and the trailing spec. Load the circuit once and pass the handle.

# 0.2.x
proof = mdoc.prove(circuit, credential, issuer_pk, transcript, attrs, now, spec)
# 0.3.0
client = Pylongfellow(backend="google-cpp")
handle = client.load_circuit(spec, circuit)
proof = client.prove(handle, credential, issuer_pk, transcript, attrs, now)

A complete, runnable version over a committed sample mdoc is in examples/prove_and_verify.py: find_zk_specgenerate_circuitcircuit_idload_circuitproveverify. It needs nothing but the package and the bundled fixture; circuit generation takes ~15s.

Backends

A client is bound to one backend at construction. Pylongfellow(backend=...) takes a registry name or a Backend instance and raises BackendUnavailableError when the backend's native dependency is not built. prove and verify dispatch through the backend a circuit was loaded into: the CircuitHandle carries it, so a handle works on any client. Two backends ship in the source tree.

google-cpp binds the vendored longfellow-zk C++ library and is in every wheel. can_generate is True. It populates .code on the exceptions it raises, ignores device_namespaces on verify, and checks at load that spec.circuit_hash matches the circuit bytes.

isrg-rust binds abetterinternet/zk-cred-longfellow (ISRG) through its UniFFI bindings. can_generate is False; it raises GenerationUnsupportedError from generate_circuit, so circuits come from a google-cpp client or from disk. verify requires device_namespaces (the inner bytes of the tag-24 DeviceNameSpacesBytes) and raises ValueError without it. It leaves .code as None. Circuit identity checking is backend-native behaviour: this backend does not check spec.circuit_hash at load, so a wrong circuit of the same version and attribute count is not detected there; mismatched version or count surfaces as an error at prove/verify.

Select it by name:

client = Pylongfellow(backend="isrg-rust")
handle = client.load_circuit(spec, compressed)

The isrg-rust backend is not in any wheel. Build it from source:

git submodule update --init vendor/zk-cred-longfellow
uv run python scripts/build_isrg_rust_backend.py # needs cargo 1.85+; ~4 min cold build
pip install pylongfellow[isrg-rust]              # zstandard runtime dependency

build_isrg_rust_backend.py stages the UniFFI-generated Python module and the cdylib into src/pylongfellow/backends/_zk_cred/ (gitignored). If the module is not built or zstandard is not installed, the backend raises BackendUnavailableError.

Engine init on the isrg-rust backend takes about 18 seconds per role and about 740 MB resident for a v6 1-attribute circuit. Init is lazy and cached on the handle. prove then takes about 1.3 seconds and verify about 0.8 seconds on the reference machine.

The differential tests exchange proofs between the two backends in both directions over the vendored v6 1-attribute circuit, and both backends verify zk-cred-longfellow's committed interop proof, which google/longfellow-zk generated. For the same statement the isrg-rust proof is larger than the google proof (562228 versus 323868 bytes).

zk-cred-longfellow is licensed MPL-2.0. pylongfellow remains Apache-2.0.

Logging

Upstream logs to stderr (not Python logging, and it exposes no callback to bridge). The one control is the PYLONGFELLOW_LOG_LEVEL environment variable, read once when the extension loads — there is no Python API and no runtime reconfiguration, following the TF_CPP_MIN_LOG_LEVEL / GRPC_VERBOSITY convention.

Values (case-insensitive): error, warning, info, silent. The default is warning, which hides upstream's per-call info output but keeps genuine errors and warnings.

Build from source

The vendored upstream is a standard CMake project. Prerequisites (Debian/Ubuntu):

sudo apt install -y cmake libssl-dev libzstd-dev libstdc++-13-dev libgtest-dev libbenchmark-dev
git submodule update --init --recursive

The default c++ (g++) works. Build and test with uv:

uv sync                                   # builds the extension + dev group, writes uv.lock
uv run pytest                             # fast suite
uv run pytest -m "slow or not slow" --cov # full suite incl. real circuit generation

scikit-build-core drives the vendored CMake build and packages the cffi extension. The internal upstream object libraries are built position-independent and linked into a single shared object that cffi binds.

Two gotchas worth knowing:

  • uv sync won't rebuild on a C/C++-source-only change — it keys off version and dependencies, not native source mtimes, so it silently leaves the old .so installed. Force it with uv sync --reinstall-package pylongfellow.
  • Use a current uv. A stale one can serve a Python alpha (e.g. 3.14.0a4) whose GC segfaults at finalization after circuit generation; a shutdown SIGSEGV is the symptom. Update uv and check the interpreter version first.

How wheels are built

  • Wheels: cibuildwheel builds cp311–cp314 × {manylinux, musllinux}, x86_64, with the test suite run inside each build container as the gate; auditwheel repairs them. A source distribution bundling the pinned upstream ships alongside.
  • Publish: PyPI Trusted Publishing (OIDC, no long-lived tokens), which emits PEP 740 build attestations binding each wheel to its source commit.
  • Dev: uv (envs, lock), ruff (lint + format), mypy (strict), pytest, mkdocs.

Upstream

pylongfellow vendors google/longfellow-zk (Apache-2.0) as a git submodule, hard-pinned to a specific commit (currently v0.9, fe83ec6) and built from source into each wheel. It does not float: the upstream ABI and circuits can change between releases, and the test fixtures are pinned to a particular circuit and version.

The isrg-rust backend vendors abetterinternet/zk-cred-longfellow (ISRG, MPL-2.0) as a second git submodule, hard-pinned to 4f3d1b3. It is built on demand by scripts/build_isrg_rust_backend.py and is not built into any wheel. pylongfellow itself is Apache-2.0.

Not affiliated with Google or the European Commission — an independent binding to a public Apache-2.0 library.

License

Apache-2.0, matching upstream.

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

pylongfellow-0.3.0.tar.gz (20.4 MB view details)

Uploaded Source

Built Distributions

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

pylongfellow-0.3.0-cp314-cp314-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.14musllinux: musl 1.2+ x86-64

pylongfellow-0.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

pylongfellow-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ x86-64

pylongfellow-0.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

pylongfellow-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

pylongfellow-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

pylongfellow-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ x86-64

pylongfellow-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file pylongfellow-0.3.0.tar.gz.

File metadata

  • Download URL: pylongfellow-0.3.0.tar.gz
  • Upload date:
  • Size: 20.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pylongfellow-0.3.0.tar.gz
Algorithm Hash digest
SHA256 0fd0b99291e0b2e716c32149f0236946c8e4892e37761a08aadde5d0056a131b
MD5 7c3aa3cc2e4f65e52e866f55cabf72e3
BLAKE2b-256 61c8c01686add4242bebb38f00b27498469fc3ae1e0b4d30d36ee41f12a0f7ae

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0.tar.gz:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp314-cp314-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp314-cp314-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f1b4fcbab9124d5325464fe3ec373972172306ebd1c90b3db744d805d9c00c1b
MD5 41abb0debb7b41f4bd40d6ebf921a7e8
BLAKE2b-256 53e282d1ee4f732c12545652aac968c9723f7ce03d3f57e56836b0079433a3d3

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp314-cp314-musllinux_1_2_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7b2cfb3e60ebb1693a6a958543db42d6920513d6f322eda0d589f3c82357ac87
MD5 ec64e68c2cf5882400cf49893f77adc1
BLAKE2b-256 6e95c248bd8dcd0cddf31cdd2b1e1b2fa3b739afedb448fc16e05a35c1d97266

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c9355c4b3b172a9a091c10d9d4e9b322b9a5c7d2152c729f543d41e11675f3a5
MD5 5084c773ea5279e135e828567205d506
BLAKE2b-256 9de95cdc79f8d62e46a80bbbb883251cbeddbcebfe507536101439cfd9b76882

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 73df18658961e371ae6aeb03c169ff7fe941a719e87114e46d6d4d1d997fc37a
MD5 f7c0c5464ceeea78c32b08ce529569c4
BLAKE2b-256 91384005163588b75812becf30daa1fe874cdea822454a226402a364ff2dd584

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f002f5f5926fab3a56cb0e56814fb4930c708a63b6e5dc2d95bb7f6aa5ace37e
MD5 117148206a6d7e94e1862670478077d1
BLAKE2b-256 66d10f3ff9d215c487c456e96d0cad3f57771cdaf3ef67e9588dc174259faceb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f2a07e5e8e9caadb0e069b544213c615d417b7e32352eee5f5a7a0dd3f75c01c
MD5 a49161306ddf885b72bcbbf82eee1303
BLAKE2b-256 e31529089576c2614550295b819fcfe22b2d54ff7765a818bea885907ff8b807

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 00acc33eeddab5dae1cd52ce839397f57ac66074290ed7b4d3e7c42b3daf014f
MD5 f15b0e5d2d3f7235ea37a67fdafc3e30
BLAKE2b-256 ba04d27db09873743f4875160cc853ed261cd5077e791a2901622090c174c375

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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

File details

Details for the file pylongfellow-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pylongfellow-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9a2a195ced2bb0a46485dd189b192b39c3066cfa9767b474582c9d8427f10d96
MD5 ec39d1b6771d148c2078d51acc833b7d
BLAKE2b-256 173e523eb524e0d85967c433758a2de65b5769174de4698e302870d935d5ed81

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylongfellow-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: release.yml on pipe23-org/pylongfellow

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