Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.
Project description
proofbundle
An offline verifier for AI eval receipts. Standards-native: Ed25519 signature, RFC 6962 transparency-log Merkle anchoring, optional SD-JWT (RFC 9901) selective disclosure, aligned to the in-toto test-result predicate. One portable JSON file, no server, no network.
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. 96 tests.
Contents
- Why
- What it verifies
- How it fits together
- Install
- Quickstart
- Demo
- Interoperability
- Bundle format
- Eval receipts
- Security notes and scope
- Roadmap
- Contributing
- License
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:
- ed25519-signature — the payload was signed by the stated Ed25519 key
- 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)
- 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
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
Demo — a real eval log to a verified receipt, offline
pip install "proofbundle[eval,inspect]"
make demo # or: bash scripts/demo.sh
make demo runs end-to-end with no network, no API key, no GPU: it takes genuine eval logs — an
inspect_ai mockllm/model .eval log and an lm-evaluation-harness --model dummy results.json
(committed under tests/fixtures/, generated offline) — turns each into a signed, Merkle-anchored
proofbundle receipt, and verifies it to => OK. The scores are random (a dummy model); the point is
that the artifact is signed and offline-verifiable, with model and dataset kept as salted commitments.
See examples/inspect_receipt.py and
examples/lm_eval_receipt.py.
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
vcttype 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.
Honesty guardrail (the exact scope). A receipt attests the authenticity and integrity of a claimed result and its context — these exact bytes, signed by this key, anchored under this root, with model/dataset kept as salted commitments. It does not attest the correctness of the computation, and it cannot detect cherry-picking of the eval. Whether the eval was well designed, whether the suite measures what it claims, and whether the number was computed honestly are separate questions. Trusted-execution approaches such as Attestable Audits target computation-correctness with a different (hardware) trust model; proofbundle is the lightweight, hardware-free path to a portable, tamper-evident, selectively disclosable result artifact.
How this differs from a bare hash or a TEE. A plain SHA-256 of a log commits to bytes but carries no signature, no tamper-evident anchor, and no selective disclosure (an attestation-exporter idea along those lines, inspect_evals PR #1610, was closed as belonging a layer above the framework — which is exactly where proofbundle sits). A TEE proves the computation ran untampered but needs specific hardware. proofbundle adds Ed25519 + RFC 6962 Merkle + SD-JWT selective disclosure over one portable file, offline.
A verification layer for trustworthy eval logs
The maintainers of inspect_evals (Arcadia Impact, funded by the UK AI Safety Institute) name 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.
How it fits — standards-native, and honest about the neighbours. proofbundle attests that a claimed evaluation result is authentic, tamper-evident, and selectively disclosable. It does not attest that the evaluation was computed correctly or that results were not cherry-picked — proving faithful computation is the domain of TEE approaches such as Attestable Audits. It is complementary to its neighbours, named fairly: Every Eval Ever standardizes eval metadata but adds no cryptography (proofbundle ships an EEE→receipt converter); OpenSSF Model Signing signs model weights, not eval results; ValiChord provides blind peer consensus and an attested log on a Holochain network (its v1 attestation library uses a simple SHA-256 Merkle tree, no signature, no SD-JWT, no in-toto). proofbundle is the lightweight, standards-native piece between them: a portable receipt a third party verifies offline, with selective disclosure so an auditor can prove a threshold was met without revealing the model or the data. See INTEROP.md.
- Three framework bridges —
pip install "proofbundle[inspect]"reads a UK AISI inspect_ai eval log via the stableread_eval_logAPI (lazy import).proofbundle.adapters.from_lm_eval_resultsreads a real EleutherAI lm-evaluation-harnessresults_*.json(the genuineacc,nonefilter-suffix format).proofbundle.adapters.from_eee_dataset(v0.9) reads an Every Eval Ever v0.2.2 aggregate JSON and builds a signed receipt — validated against the vendored EEE schema, with no runtime import ofevery_eval_ever(it needs Python 3.12; proofbundle stays 3.9+). - in-toto test-result export, DSSE-signed (v0.9) —
proofbundle.intoto.export_intoto_dsse(claim, signer)emits the receipt as a DSSE-signed in-toto Statement v1 with the generictest-result/v0.1predicate (result PASSED/FAILED,configurationResourceDescriptors), so a generic in-toto verifier understands it. Alongside the self-hosted-predicateto_intoto_statement(see PREDICATE.md). Metric details live inannotations(test-result has no native metric field); the model/dataset stay salted commitments, neversha256. - C2SP tlog-checkpoint (v0.9) —
proofbundle.checkpoint.sign_checkpoint(origin, tree_size, root, …)emits a valid C2SP signed note over the RFC 6962 Merkle root, making a receipt witness-network / transparency-log compatible. Pure serialization over the Ed25519 key already in use — no new crypto. - SD-JWT issuance (RFC 9901, verified Nov 2025) —
proofbundle.sdjwt_issue.issue_sd_jwt(claim, signer, root_b64=…, exact_score=…)issues the receipt so a holder can disclosepassed+thresholdwhile withholding the exact score and the identifier openings. The digest mechanic is RFC 9901 §4.2.3 (base64url of SHA-256 over the base64url-encoded Disclosure), cross-checked against thesd-jwt-pythonreference. The signed bundle payload is always the source of truth; the SD-JWT and the in-toto export are derived, bundle-bound views.
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 — citability polish (ORCID, Zenodo DOI placeholder, in-toto proposal draft); v0.7.1 hardened verifier robustness + CI on Python 3.9 after a holistic review.
- v0.8 — an offline
make demo(real eval log -> signed receipt -> verified), a sharpened honesty guardrail (authenticity/integrity, not computation-correctness), and outreach drafts. - v0.9 (current release) — the standards moat: a DSSE-signed in-toto
test-resultexport, a C2SP tlog-checkpoint over the RFC 6962 root, an Every Eval Ever converter, and standards-native repositioning. - Deferred (explicitly not yet built) — SD-JWT VC conformance +
vctmetadata, 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file proofbundle-0.9.0.tar.gz.
File metadata
- Download URL: proofbundle-0.9.0.tar.gz
- Upload date:
- Size: 62.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5a686f79fa9e8adc15b38a32ae05c567c63bb917f6659bf4c27a9e99f22c5a6
|
|
| MD5 |
f64ea1b20e71c83dfa75d879a8e3a411
|
|
| BLAKE2b-256 |
7bd6c4d4d87180c90f1ddf84ba8da22d051ce8f07da0c193c91386fb3a91c577
|
Provenance
The following attestation bundles were made for proofbundle-0.9.0.tar.gz:
Publisher:
release.yml on b7n0de/proofbundle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proofbundle-0.9.0.tar.gz -
Subject digest:
a5a686f79fa9e8adc15b38a32ae05c567c63bb917f6659bf4c27a9e99f22c5a6 - Sigstore transparency entry: 2042590432
- Sigstore integration time:
-
Permalink:
b7n0de/proofbundle@affcc10192a4856cf76b694d7eb7fb1daf54810f -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/b7n0de
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@affcc10192a4856cf76b694d7eb7fb1daf54810f -
Trigger Event:
push
-
Statement type:
File details
Details for the file proofbundle-0.9.0-py3-none-any.whl.
File metadata
- Download URL: proofbundle-0.9.0-py3-none-any.whl
- Upload date:
- Size: 49.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec507a1f4fbe608cd1a5d268d95f57578f09825fea4f31097bd9bcab6ddbdc83
|
|
| MD5 |
4569261519ff4b619147d7b13fd22351
|
|
| BLAKE2b-256 |
217a7dc6c30652278a86c89135f7a465f0208788da233ea2c664c3be4d4244b4
|
Provenance
The following attestation bundles were made for proofbundle-0.9.0-py3-none-any.whl:
Publisher:
release.yml on b7n0de/proofbundle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proofbundle-0.9.0-py3-none-any.whl -
Subject digest:
ec507a1f4fbe608cd1a5d268d95f57578f09825fea4f31097bd9bcab6ddbdc83 - Sigstore transparency entry: 2042591421
- Sigstore integration time:
-
Permalink:
b7n0de/proofbundle@affcc10192a4856cf76b694d7eb7fb1daf54810f -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/b7n0de
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@affcc10192a4856cf76b694d7eb7fb1daf54810f -
Trigger Event:
push
-
Statement type: