Skip to main content

Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.

Project description

b7n0de, Verified AI Work

proofbundle

Emit and verify, fully offline, portable evidence that a piece of data was signed and anchored in a tamper-evident log — and optionally carries a selectively disclosable credential. Pure Python, no server, no daemon, one JSON file.

CI PyPI Python Downloads License: MIT Ruff SLSA build provenance PyPI attestations

At a glance: proofbundle emit signs and anchors a payload; proofbundle verify checks one self-contained bundle.json with three offline cryptographic checks → OK or FAILED. No network, no daemon, no own crypto. 63 tests.

Contents

Why

Cryptographic evidence today usually needs a running service to check it. Sigstore Rekor, Certificate Transparency and other transparency logs are excellent, but verifying an inclusion proof normally means talking to a log server or wiring up Go tooling. There is no small, portable, Python-native verifier that takes one self-contained file and answers a simple question offline:

Were these exact bytes signed by this key, and anchored under this Merkle root, yes or no.

proofbundle is that verifier — and, since v0.2, the matching emitter. It is the verification half of a larger idea: turning a reproducible result (for example an AI evaluation run) into a signed, third-party-verifiable, selectively disclosable receipt. The verifier shipped first, small and correct, so it could be reviewed and trusted on its own; emit_bundle now creates bundles that verify_bundle accepts, fully offline on both sides.

What it verifies

A bundle is a single JSON document. proofbundle checks, offline:

  1. ed25519-signature — the payload was signed by the stated Ed25519 key
  2. merkle-inclusion — the payload is anchored under the stated tree root, using an RFC 6962 / RFC 9162 inclusion proof (the same primitive as Rekor and Certificate Transparency)
  3. sd-jwt (optional) — an embedded SD-JWT selective-disclosure credential is well formed, and if an issuer key is given, correctly issuer-signed

The verifier treats the payload as opaque bytes. It proves that these exact bytes were signed and anchored, not what they mean. That is on purpose: it keeps the trusted core tiny.

How it fits together

flowchart LR
    P["payload bytes"]
    P -->|"Ed25519 sign"| S["signature"]
    P -->|"RFC 6962 anchor"| M["Merkle inclusion proof"]
    SD["SD-JWT VC (optional)"] -.-> B
    S --> B["bundle.json"]
    M --> B
    B --> V{{"proofbundle verify"}}
    V --> C1["ed25519-signature"]
    V --> C2["merkle-inclusion"]
    V --> C3["sd-jwt (optional)"]
    C1 --> R{"all checks pass?"}
    C2 --> R
    C3 --> R
    R -->|yes| OK(["=> OK   exit 0"])
    R -->|no| FAIL(["=> FAILED   exit 1"])

    style V fill:#D6248A,stroke:#D6248A,color:#fff
    style OK fill:#D6248A,stroke:#D6248A,color:#fff
    style FAIL fill:#ef4444,stroke:#ef4444,color:#fff

Install

pip install proofbundle

Requires Python 3.9+ and cryptography. Signature math is delegated to cryptography; this project never rolls its own crypto. The Merkle and SD-JWT logic is pure standard library.

SD-JWT support is an optional extra (it adds no runtime dependency beyond the core cryptography, so the trusted core stays lean):

pip install "proofbundle[sdjwt]"

Quickstart

# generate a real example bundle with throwaway keys
python examples/make_example.py

# verify it
proofbundle verify examples/example_bundle.json
proofbundle verify output: four PASS checks and OK

Machine-readable output and a non-zero exit code on failure:

proofbundle verify --json bundle.json   # exit 0 = ok, 1 = failed, 2 = malformed

Emit a bundle of your own (v0.2): sign a payload with a fresh key and anchor it, then verify it anywhere, offline.

proofbundle emit --payload-file result.json --new-key signer.key --out bundle.json
proofbundle verify bundle.json

Library use:

from proofbundle import verify_bundle

result = verify_bundle("bundle.json")
print(result.ok)          # True / False
for check in result.checks:
    print(check.name, check.ok, check.detail)

Verify a consistency proof between two log states directly:

from proofbundle import verify_consistency
verify_consistency(first_size, second_size, proof, first_root, second_root)  # -> bool

Interoperability

proofbundle uses the same RFC 6962 / RFC 9162 Merkle primitive as Sigstore Rekor and Certificate Transparency, so its verify_inclusion checks a real proof from a live transparency log, not just its own bundles. examples/rekor_interop.py verifies a real Sigstore Rekor inclusion proof (a committed fixture, logIndex 25579 in a 4.16-million-entry tree) fully offline, and documents the field mapping from the Rekor bundle and its C2SP tlog-checkpoint signed note to proofbundle's merkle object. Correctness is also checked against external RFC 6962 test vectors vendored from transparency-dev/merkle (see tests/fixtures/), plus Hypothesis property tests.

Bundle format (proofbundle/v0.1)

The format is specified normatively in SPEC.md (fields, encodings, RFC 6962 hashing, verification order) with a machine-readable JSON Schema at schemas/proofbundle_v0_1.schema.json.

{
  "schema": "proofbundle/v0.1",
  "payload_b64": "<the exact bytes that were signed and anchored>",
  "signature": { "alg": "ed25519", "public_key_b64": "...", "sig_b64": "..." },
  "merkle": {
    "hash_alg": "sha256-rfc6962",
    "leaf_index": 1,
    "tree_size": 4,
    "inclusion_proof_b64": ["...", "..."],
    "root_b64": "..."
  },
  "sd_jwt_vc": { "compact": "<sd-jwt>", "issuer_public_key_b64": "..." }
}

sd_jwt_vc is optional. Base64 fields are standard base64; the SD-JWT compact string uses base64url as per the spec.

Security notes and scope, stated honestly

The scope is deliberately narrow. It does exactly what it says and no more:

  • Ed25519 signatures only, for both the payload and the optional SD-JWT issuer signature.
  • SD-JWT: the SD-JWT core is now RFC 9901 (November 2025); this verifies that every presented disclosure is committed in the issuer-signed payload, and the issuer signature (EdDSA) if a key is supplied. It does not verify a Key Binding JWT, an X.509 or trust-list chain, status lists, or vct type metadata. SD-JWT VC (the credential-type profile) is still an IETF draft (draft-ietf-oauth-sd-jwt-vc); full VC conformance is on the roadmap.
  • The verifier does not fetch anything. Trust anchors (the signer key, the expected root) are inputs you supply out of band.
  • No custom cryptography. Ed25519 comes from cryptography; Merkle hashing is RFC 6962.

If you find a correctness or security issue, please open an issue or see SECURITY.md.

Eval receipts

Since v0.4, proofbundle turns a reproducible eval run into a signed, Merkle-anchored receipt that proves suite S comparator threshold T, passed while carrying only salted commitments to the model and dataset identifiers — never the weights, the data, or the plaintext names. A third party verifies the threshold was met, offline, from one file, without ever seeing the model or the test set.

pip install "proofbundle[eval]"          # emit side needs an RFC 8785 canonicalizer
proofbundle emit-eval --claim claim.json --out receipt.json --new-key signer.key
proofbundle verify receipt.json          # a receipt is a normal bundle
proofbundle show-eval receipt.json       # verify + print the claim (issuer-bound)

The claim format is specified in EVAL_CLAIM.md; the emit path uses RFC 8785 JCS canonicalization, the verify path stays dependency-free. Honest scope: a receipt proves passed against threshold and hides the model/dataset via salted commitments — it does not prove the evaluation was well designed or that the score itself is correct. Those are human judgements; what it removes is the need to simply trust the number.

A verification layer for trustworthy eval logs

The UK AISI inspect_ai team names an open gap (arXiv:2507.06893): a database of trustworthy evaluation results with proper provenance tracking. proofbundle is the missing signature + selective-disclosure layer for exactly that — complementary to metadata aggregation (Every Eval Ever) and documentation taxonomies (Eval Factsheets), not a competitor. See INTEROP.md for how it maps to OpenSSF Model Signing, CycloneDX ML-BOM, and in-toto.

  • Two framework adapterspip install "proofbundle[inspect]" reads a UK AISI inspect_ai eval log via the stable read_eval_log API (lazy import). proofbundle.adapters.from_lm_eval_results reads a real EleutherAI lm-evaluation-harness results_*.json (the genuine acc,none filter-suffix format) and captures run provenance — no framework import either way.
  • in-toto Statement v1proofbundle.intoto.to_intoto_statement(claim, root_b64=…) emits the receipt as an in-toto statement with a self-hosted predicate type. The subject digest is an honest salted commitment under a custom key, never sha256 (see PREDICATE.md).
  • SD-JWT issuance (RFC 9901) — proofbundle.sdjwt_issue.issue_sd_jwt(claim, signer, root_b64=…, exact_score=…) issues the receipt so a holder can disclose passed + threshold while withholding the exact score and the identifier openings. The signed bundle payload is the source of truth; the SD-JWT is a derived, bundle-bound view, verified by proofbundle's own verifier and the sd-jwt-python reference.

Every release ships PEP 740 attestations (Trusted Publishing) + an SLSA build-provenance attestation — see SECURITY.md.

Roadmap

  • v0.1 — the offline verifier plus a real example bundle.
  • v0.2 — the emitter: emit_bundle / proofbundle emit.
  • v0.3 — external RFC 6962 conformance vectors + real Sigstore Rekor interop.
  • v0.4 — the eval-receipt emitter (emit_eval_receipt / proofbundle emit-eval), salted commitments, issuer binding.
  • v0.5 — inspect_ai adapter (stable API), in-toto Statement v1 view, SD-JWT issuance (RFC 9901).
  • v0.6 — a second eval adapter (lm-evaluation-harness, real format + provenance), INTEROP.md, CITATION.cff, PEP 740 attestations documented.
  • v0.7 (current release) — citability polish: ORCID in CITATION.cff, a Zenodo DOI placeholder (assigned on release), and a draft in-toto ML-eval predicate proposal.
  • Deferred (explicitly not yet built) — SD-JWT VC conformance + vct metadata, Key-Binding JWT, status lists / revocation, an official in-toto PR, DSSE / a full in-toto client.

Contributing

See CONTRIBUTING.md and the Code of Conduct. Good first issues are labeled good-first-issue. The verifier core aims to stay small, dependency-light and correct.

License

MIT, see LICENSE.


proofbundle is part of b7n0de, Verified AI Work · b7n0de.com

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

proofbundle-0.7.0.tar.gz (41.5 kB view details)

Uploaded Source

Built Distribution

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

proofbundle-0.7.0-py3-none-any.whl (31.4 kB view details)

Uploaded Python 3

File details

Details for the file proofbundle-0.7.0.tar.gz.

File metadata

  • Download URL: proofbundle-0.7.0.tar.gz
  • Upload date:
  • Size: 41.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for proofbundle-0.7.0.tar.gz
Algorithm Hash digest
SHA256 ddf835ebed395918f59ff225e69195620eaaf10d615d00dc82bd52d512df46e9
MD5 1812a34b518ea4565ea4e1fb0e877b7f
BLAKE2b-256 a0f49514f0cc7089569234faed3d2d2b396934cc149d6bc8f2b8858e24d66cf3

See more details on using hashes here.

Provenance

The following attestation bundles were made for proofbundle-0.7.0.tar.gz:

Publisher: release.yml on b7n0de/proofbundle

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

File details

Details for the file proofbundle-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: proofbundle-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 31.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for proofbundle-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ce5433fdf317a26a2676357e1943c35bfbf3fb4b942570c7d635c4429e79f9d2
MD5 1a3bb72bb32adc439073aacb3a697512
BLAKE2b-256 2dfc6c34e54efb9fdb728f1729c522f8f5958f9572162218481d971efa019f9e

See more details on using hashes here.

Provenance

The following attestation bundles were made for proofbundle-0.7.0-py3-none-any.whl:

Publisher: release.yml on b7n0de/proofbundle

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