proofbundle 2.0.0b2__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proofbundle-2.1.0/PKG-INFO +286 -0
- proofbundle-2.1.0/README.md +232 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/pyproject.toml +6 -1
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/__init__.py +12 -1
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/anchors.py +73 -13
- proofbundle-2.1.0/src/proofbundle/anchors_chia.py +196 -0
- proofbundle-2.1.0/src/proofbundle/anchors_chia_add.py +154 -0
- proofbundle-2.1.0/src/proofbundle/anchors_markovian.py +121 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/anchors_rfc3161.py +19 -1
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/bundle.py +39 -13
- proofbundle-2.1.0/src/proofbundle/canonical.py +136 -0
- proofbundle-2.1.0/src/proofbundle/cli.py +1177 -0
- proofbundle-2.1.0/src/proofbundle/decision.py +428 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/evalclaim.py +7 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/intoto.py +141 -34
- proofbundle-2.1.0/src/proofbundle/policy.py +437 -0
- proofbundle-2.1.0/src/proofbundle.egg-info/PKG-INFO +286 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle.egg-info/SOURCES.txt +24 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle.egg-info/requires.txt +2 -0
- proofbundle-2.1.0/tests/test_anchors_chia.py +222 -0
- proofbundle-2.1.0/tests/test_anchors_chia_add.py +168 -0
- proofbundle-2.1.0/tests/test_anchors_chia_claims.py +116 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_anchors_generic.py +44 -0
- proofbundle-2.1.0/tests/test_anchors_markovian.py +151 -0
- proofbundle-2.1.0/tests/test_anchors_rfc3161.py +226 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_branch_base_check.py +17 -6
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_bundle_robustness.py +38 -2
- proofbundle-2.1.0/tests/test_canonical.py +136 -0
- proofbundle-2.1.0/tests/test_cli.py +182 -0
- proofbundle-2.1.0/tests/test_cli_require_anchor.py +248 -0
- proofbundle-2.1.0/tests/test_decision_anchors.py +97 -0
- proofbundle-2.1.0/tests/test_decision_cli.py +145 -0
- proofbundle-2.1.0/tests/test_decision_evidence.py +84 -0
- proofbundle-2.1.0/tests/test_decision_fuzz.py +123 -0
- proofbundle-2.1.0/tests/test_decision_hardening.py +137 -0
- proofbundle-2.1.0/tests/test_decision_policy.py +182 -0
- proofbundle-2.1.0/tests/test_decision_schema.py +103 -0
- proofbundle-2.1.0/tests/test_decision_verify.py +115 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_docs_truth.py +28 -0
- proofbundle-2.1.0/tests/test_fork_pr_secret_isolation.py +283 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto_claims_hygiene.py +6 -2
- proofbundle-2.1.0/tests/test_intoto_content_root_migration.py +311 -0
- proofbundle-2.1.0/tests/test_ok_semantics.py +368 -0
- proofbundle-2.1.0/tests/test_trust_policy.py +456 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_verify_matrix.py +5 -1
- proofbundle-2.0.0b2/PKG-INFO +0 -208
- proofbundle-2.0.0b2/README.md +0 -155
- proofbundle-2.0.0b2/src/proofbundle/cli.py +0 -590
- proofbundle-2.0.0b2/src/proofbundle.egg-info/PKG-INFO +0 -208
- proofbundle-2.0.0b2/tests/test_anchors_rfc3161.py +0 -115
- proofbundle-2.0.0b2/tests/test_cli.py +0 -91
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/LICENSE +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/setup.cfg +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/_inspect_registry.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/_integration.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/__init__.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/_provenance.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/eee.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/inspect_ai.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/lm_eval.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/promptfoo.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/adapters/samples.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/anchors_ots.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/beacon.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/checkpoint.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/demo.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/dsse.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/eee_eval_schema.json +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/emit.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/errors.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/experimental/__init__.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/experimental/enclave.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/hf_evals.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/inspect_hook.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/kbjwt.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/merkle.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/persample.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/prereg.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/py.typed +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/pytest_plugin.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/sdjwt.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/sdjwt_issue.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/signature.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/statuslist.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle/tlogproof.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle.egg-info/dependency_links.txt +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle.egg-info/entry_points.txt +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/src/proofbundle.egg-info/top_level.txt +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_adapters.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_adversarial.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_anchors_ots.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_beacon.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_bundle.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_checkpoint.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_claims_hygiene.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_cli_eval.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_cosignature.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_cosignature_mldsa.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_demo.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_eee.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_emit.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_eval_claim_schema.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_evalclaim.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_examples.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_experimental_enclave.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_fuzz_parsers.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_hf_evals.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_inspect_hook.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto_dsse.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto_eval_result.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto_examples.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto_spec_diff.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_intoto_svr.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_kbjwt.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_merkle.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_merkle_property.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_persample.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_prereg.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_promptfoo.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_provenance.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_pytest_plugin.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_rekor_interop.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_rfc6962_external_vectors.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_schema.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_sdjwt_issue.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_sdjwt_reference.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_signature.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_statuslist.py +0 -0
- {proofbundle-2.0.0b2 → proofbundle-2.1.0}/tests/test_tlogproof.py +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proofbundle
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.
|
|
5
|
+
Author: Konrad Gruszka
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://b7n0de.com
|
|
8
|
+
Project-URL: Repository, https://github.com/b7n0de/proofbundle
|
|
9
|
+
Project-URL: Issues, https://github.com/b7n0de/proofbundle/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/b7n0de/proofbundle/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Documentation, https://github.com/b7n0de/proofbundle#readme
|
|
12
|
+
Keywords: cryptography,merkle,transparency-log,ed25519,sd-jwt,verifiable-credentials,attestation,provenance,rfc6962
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Security :: Cryptography
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: cryptography>=42
|
|
27
|
+
Provides-Extra: sdjwt
|
|
28
|
+
Provides-Extra: eval
|
|
29
|
+
Requires-Dist: rfc8785>=0.1.4; extra == "eval"
|
|
30
|
+
Provides-Extra: anchors
|
|
31
|
+
Requires-Dist: rfc3161-client>=1.0.6; extra == "anchors"
|
|
32
|
+
Requires-Dist: opentimestamps>=0.4.5; extra == "anchors"
|
|
33
|
+
Requires-Dist: rfc8785>=0.1.4; extra == "anchors"
|
|
34
|
+
Provides-Extra: adapters
|
|
35
|
+
Provides-Extra: chia
|
|
36
|
+
Provides-Extra: experimental
|
|
37
|
+
Provides-Extra: pq
|
|
38
|
+
Requires-Dist: cryptography>=48; extra == "pq"
|
|
39
|
+
Provides-Extra: pytest
|
|
40
|
+
Requires-Dist: pytest>=7; extra == "pytest"
|
|
41
|
+
Provides-Extra: inspect
|
|
42
|
+
Requires-Dist: inspect_ai<0.4,>=0.3.112; extra == "inspect"
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
45
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
46
|
+
Requires-Dist: jsonschema>=4; extra == "dev"
|
|
47
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
48
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
49
|
+
Requires-Dist: hypothesis>=6; extra == "dev"
|
|
50
|
+
Requires-Dist: rfc8785>=0.1.4; extra == "dev"
|
|
51
|
+
Requires-Dist: sd-jwt>=0.10; extra == "dev"
|
|
52
|
+
Requires-Dist: inspect_ai<0.4,>=0.3.112; extra == "dev"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
<div align="center">
|
|
56
|
+
|
|
57
|
+
<picture>
|
|
58
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/b7n0de/proofbundle/main/assets/b7n0de-logo-dark.svg">
|
|
59
|
+
<img alt="b7n0de, Verified AI Work" src="https://raw.githubusercontent.com/b7n0de/proofbundle/main/assets/b7n0de-logo.svg" height="60">
|
|
60
|
+
</picture>
|
|
61
|
+
|
|
62
|
+
<h1>proofbundle</h1>
|
|
63
|
+
|
|
64
|
+
**AI eval results need receipts.**
|
|
65
|
+
|
|
66
|
+
Turn an AI evaluation result into one portable, offline-verifiable receipt. It proves *who signed
|
|
67
|
+
these exact bytes* and *that nothing changed since* — not that the number is true. Ed25519 + RFC 6962
|
|
68
|
+
Merkle, one file, no server, no network.
|
|
69
|
+
|
|
70
|
+
[](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
|
|
71
|
+
[](https://github.com/b7n0de/proofbundle/actions/workflows/demo-reproducible.yml)
|
|
72
|
+
[](https://pypi.org/project/proofbundle/)
|
|
73
|
+
[](https://pypi.org/project/proofbundle/)
|
|
74
|
+
[](https://github.com/b7n0de/proofbundle/blob/main/LICENSE)
|
|
75
|
+
[](https://doi.org/10.5281/zenodo.21230467)
|
|
76
|
+
[](https://github.com/astral-sh/ruff)
|
|
77
|
+
[](https://github.com/b7n0de/proofbundle/blob/main/scripts/mutation_check.py)
|
|
78
|
+
<!-- SLSA / PEP 740 attestation badges follow once the first attested release lands, see RELEASE.md. -->
|
|
79
|
+
|
|
80
|
+
**Reviewing this for adoption?** Start with the 30-minute adversarial audit path: **[docs/REVIEWERS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/REVIEWERS.md)**.
|
|
81
|
+
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
## 60-second try (offline)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install "proofbundle[eval]"
|
|
88
|
+
proofbundle demo # honest receipt => OK, six tampers each => FAILED, sample swap caught
|
|
89
|
+
# Inspect-native (METR Task Standard / UK-AISI ecosystem, mockllm, no API key):
|
|
90
|
+
git clone https://github.com/b7n0de/proofbundle && cd proofbundle
|
|
91
|
+
pip install -e ".[eval,inspect]" && make demo # or `make full-demo` for log -> receipt -> verify
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## The problem
|
|
95
|
+
|
|
96
|
+
Every AI eval number you read — a safety benchmark, a capability score, a leaderboard entry — is an
|
|
97
|
+
**unverifiable claim**. You trust the lab. There's no portable way to check, offline, that a result
|
|
98
|
+
was signed by a stated party, hasn't been altered, and covers the samples it claims.
|
|
99
|
+
|
|
100
|
+
proofbundle is that check. It's a small MIT-licensed Python tool (a compact, auditable trusted core,
|
|
101
|
+
depends only on [`cryptography`](https://cryptography.io)) that turns a result into a signed
|
|
102
|
+
receipt anyone can verify from a single file — and it's honest about the line it does not cross.
|
|
103
|
+
|
|
104
|
+
## What the demo shows
|
|
105
|
+
|
|
106
|
+
You'll see an honest receipt verify `=> OK`, then six independent tampers each verify `FAILED`, then
|
|
107
|
+
a swapped sample get caught — all in memory. `proofbundle demo` exits non-zero if any tamper slips through,
|
|
108
|
+
so it's also a self-test. Full walkthrough: **[docs/DEMO.md](https://github.com/b7n0de/proofbundle/blob/main/docs/DEMO.md)**.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# verify a real hosted receipt without writing any code:
|
|
112
|
+
curl -fsSL https://raw.githubusercontent.com/b7n0de/proofbundle/main/examples/example_bundle.json -o receipt.json
|
|
113
|
+
proofbundle verify receipt.json # CRYPTO: OK (the verify itself runs fully offline)
|
|
114
|
+
|
|
115
|
+
# your own receipt, from a signed payload:
|
|
116
|
+
proofbundle emit --payload-file result.json --new-key signer.key --out receipt.json
|
|
117
|
+
proofbundle verify receipt.json # exit 0 = crypto OK, 1 = crypto/verification failure, 2 = malformed
|
|
118
|
+
|
|
119
|
+
# apply YOUR trust decision — verify makes NO trust decision on its own:
|
|
120
|
+
proofbundle verify receipt.json --policy trust_policy.json # POLICY: OK | FAIL (exit 3) | NOT_EVALUATED
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Inspect-native? (METR Task Standard / UK-AISI ecosystem)
|
|
124
|
+
|
|
125
|
+
The receipt layer runs directly on [Inspect AI](https://inspect.aisi.org.uk/) — and the proof is
|
|
126
|
+
reproducible offline in minutes:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# setup as in the 60-second try above (clone + pip install -e ".[eval,inspect]"), then:
|
|
130
|
+
make full-demo # a genuine inspect_ai eval log (mockllm: offline, no API key, no GPU)
|
|
131
|
+
# -> signed receipt next to the log -> proofbundle verify => OK
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
In your own pipeline the end-of-task hook signs every run automatically. Walkthrough:
|
|
135
|
+
**[docs/INSPECT_HAPPY_PATH.md](https://github.com/b7n0de/proofbundle/blob/main/docs/INSPECT_HAPPY_PATH.md)** · worked example:
|
|
136
|
+
**[examples/inspect_receipt.py](https://github.com/b7n0de/proofbundle/blob/main/examples/inspect_receipt.py)**.
|
|
137
|
+
|
|
138
|
+
## What a receipt proves — and what it doesn't
|
|
139
|
+
|
|
140
|
+
| ✅ It proves | ❌ It does **not** prove |
|
|
141
|
+
|---|---|
|
|
142
|
+
| These exact bytes were signed by this key (**authorship**) | That the number is **true** |
|
|
143
|
+
| Nothing changed since signing (**integrity**, Ed25519 + RFC 6962) | That the **issuer is honest** |
|
|
144
|
+
| The result is attributable to a stated issuer | That the **eval was well-designed** |
|
|
145
|
+
| A threshold was met while hiding the model/dataset (salted commitments) | That there was **no cherry-picking** — unless pre-registered |
|
|
146
|
+
| Optionally: individual samples, offline-auditable (per-sample Merkle) | That the **computation was correct** — that needs a TEE or independent reproduction |
|
|
147
|
+
|
|
148
|
+
This boundary is the point, not a weakness. A receipt makes a claim **attributable, tamper-evident,
|
|
149
|
+
and — with pre-registration and per-sample auditing — bounded and spot-checkable**. Full detail:
|
|
150
|
+
**[THREAT_MODEL.md](https://github.com/b7n0de/proofbundle/blob/main/THREAT_MODEL.md)**.
|
|
151
|
+
|
|
152
|
+
## Post-quantum posture (honest, two layers)
|
|
153
|
+
|
|
154
|
+
proofbundle is **not** "quantum-proof" or "quantum-safe" as a whole. It combines two cryptographic layers
|
|
155
|
+
with very different quantum exposure, and it is honest about both:
|
|
156
|
+
|
|
157
|
+
- **Quantum-robust (hash-based)** — SHA-256, RFC 6962 / 9162 Merkle inclusion, RFC 8785 canonicalization,
|
|
158
|
+
and, among the external time anchors, the OpenTimestamps (Bitcoin hash-chain) and `chia-datalayer/v1`
|
|
159
|
+
(Merkle inclusion) types. Grover only halves the effective bit-strength (SHA-256 keeps a ~128-bit quantum
|
|
160
|
+
margin, which NIST currently treats as adequate), so these stay secure.
|
|
161
|
+
- **Quantum-vulnerable (elliptic-curve / classical PKI, Shor)** — the Ed25519 receipt signature; for the
|
|
162
|
+
`chia-datalayer` anchor, Chia's BLS12-381 wallet layer; and, for the RFC 3161 anchor, the TSA's own
|
|
163
|
+
classical (RSA/ECDSA) certificate-chain signature. A large enough quantum computer could forge any of these.
|
|
164
|
+
|
|
165
|
+
The attack that matters is not decryption but **back-dated forgery**: an attacker with a quantum computer
|
|
166
|
+
could mint a fake signature on a tampered receipt. The defense — **when a receipt carries a hash-based time
|
|
167
|
+
anchor** (optional, the `[anchors]` beta extra: OpenTimestamps or `chia-datalayer`) — is that the anchor
|
|
168
|
+
proves the original receipt existed *before* that capability, so a forged receipt has no matching anchor.
|
|
169
|
+
That protects the evidence long-term even if the signature layer later breaks. A plain receipt with no anchor
|
|
170
|
+
does not carry this property.
|
|
171
|
+
|
|
172
|
+
On the witness side, C2SP checkpoints already carry post-quantum **ML-DSA-44** (FIPS 204) cosignatures
|
|
173
|
+
(`proofbundle[pq]`); a post-quantum *payload* signature — crypto-agility for the receipt itself — is on the
|
|
174
|
+
roadmap.
|
|
175
|
+
|
|
176
|
+
## In plain language
|
|
177
|
+
|
|
178
|
+
A proofbundle receipt is the cash-register receipt of an AI test result: it shows who claimed the
|
|
179
|
+
number and that nobody quietly changed it afterwards. It does not show the test was good — the way a
|
|
180
|
+
cash-register receipt does not show the meal was good — but without a receipt there is nothing to
|
|
181
|
+
check at all.
|
|
182
|
+
|
|
183
|
+
## How it fits together
|
|
184
|
+
|
|
185
|
+
*(diagram renders on GitHub — [view it there](https://github.com/b7n0de/proofbundle#how-it-fits-together); PyPI shows the source)*
|
|
186
|
+
|
|
187
|
+
```mermaid
|
|
188
|
+
flowchart LR
|
|
189
|
+
H["eval harness<br/>inspect_ai · lm-eval · promptfoo · pytest"] --> A["adapter → signed claim<br/>salted commitments · provenance · samples root"]
|
|
190
|
+
A --> R["receipt<br/>one portable file"]
|
|
191
|
+
R --> V{{"proofbundle verify — offline"}}
|
|
192
|
+
V --> C["signature · Merkle inclusion · SD-JWT/KB ·<br/>witness quorum · status list · sample openings"]
|
|
193
|
+
C --> OK(["CRYPTO: OK / FAILED"])
|
|
194
|
+
style V fill:#D6248A,stroke:#D6248A,color:#fff
|
|
195
|
+
style OK fill:#D6248A,stroke:#D6248A,color:#fff
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## What's in the box
|
|
199
|
+
|
|
200
|
+
- **Core** — Ed25519 signature + RFC 6962 / 9162 Merkle inclusion, verified fully offline. Checks a
|
|
201
|
+
real [Sigstore Rekor](https://docs.sigstore.dev/) proof, so correctness isn't self-referential.
|
|
202
|
+
- **Eval receipts** — a signed claim (`metric ⋈ threshold`, `n`, salted model/dataset commitments,
|
|
203
|
+
assurance level, provenance) from your run. See [EVAL_CLAIM.md](https://github.com/b7n0de/proofbundle/blob/main/EVAL_CLAIM.md).
|
|
204
|
+
- **Selective disclosure** — SD-JWT ([RFC 9901](https://datatracker.ietf.org/doc/rfc9901/)) with Key
|
|
205
|
+
Binding: prove a threshold while withholding the exact score.
|
|
206
|
+
- **Transparency-log interop** — C2SP `tlog-checkpoint` / cosignature / `.tlog-proof`, with
|
|
207
|
+
post-quantum **ML-DSA-44** witness cosignatures. Optional Token-Status-List revocation snapshots.
|
|
208
|
+
- **Per-sample audit** — commit to every sample; an auditor challenges random indices (with a fresh
|
|
209
|
+
nonce or a **public randomness beacon**, v1.9) and openings must bind to the signed root. With
|
|
210
|
+
such an auditor-supplied or beacon-bound challenge, 300 samples catch 1% sample-doctoring with 95%
|
|
211
|
+
confidence, regardless of run size — a challenge the issuer chose itself does not give this
|
|
212
|
+
guarantee.
|
|
213
|
+
- **Pre-registration** — `proofbundle prereg <plan>` commits to the protocol before the run, so
|
|
214
|
+
best-of-many publishing becomes visible.
|
|
215
|
+
- **Integrations** — opt-in inspect_ai end-of-task hook and pytest plugin (emit only when
|
|
216
|
+
`PROOFBUNDLE_EMIT=1` / `--proofbundle`), plus a Hugging Face Community Evals bridge. See
|
|
217
|
+
[INTEGRATIONS.md](https://github.com/b7n0de/proofbundle/blob/main/INTEGRATIONS.md), or the end-to-end walkthrough
|
|
218
|
+
[docs/INSPECT_HAPPY_PATH.md](https://github.com/b7n0de/proofbundle/blob/main/docs/INSPECT_HAPPY_PATH.md) — run an eval, get a receipt, verify it offline.
|
|
219
|
+
- **External time anchors** *(v2.0 beta, the `[anchors]` extra)* — an optional `anchors[]` layer that
|
|
220
|
+
attaches external evidence of *when* a commitment or receipt existed, from a party the producer does not
|
|
221
|
+
control. Two built-in types verify offline: **RFC 3161** TSA tokens (against a frozen cert chain) and
|
|
222
|
+
**OpenTimestamps** Bitcoin proofs (honest pending → confirmed lifecycle). A `register_anchor_type`
|
|
223
|
+
extension interface lets a third party ship its own fail-closed type; two worked examples ship — a
|
|
224
|
+
first-party **`chia-datalayer/v1`** (offline Merkle inclusion of a canonical root under a published Chia
|
|
225
|
+
DataLayer root) and a third-party **`markovian-provenance/v1`** (a wallet-attributable, Bitcoin-anchored
|
|
226
|
+
stamp). See [docs/ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/ANCHORS.md).
|
|
227
|
+
- **Decision Receipts** *(2.1, vendored `decision-receipt/v0.1` predicate)* — a separate predicate for agent
|
|
228
|
+
*decisions* (not eval metrics): who decided, the proposed action, the policy boundary, digest-bound evidence,
|
|
229
|
+
the verdict (`ALLOW`/`DENY`/`REFUSE`/`ESCALATE`/`DEFER`/`OBSERVE`), and explicitly what was *not* checked.
|
|
230
|
+
`proofbundle decision emit|verify` with a v0.2 trust policy (`trusted_decision_makers`, `accepted_predicate_
|
|
231
|
+
types`). An eval receipt says *the number is authored and intact*; a Decision Receipt says *this decision was
|
|
232
|
+
made by this gate over this evidence* — never that the decision was correct. `actionOutcome=executed` without
|
|
233
|
+
a signed outcome is self-assertion. See
|
|
234
|
+
[docs/predicates/decision-receipt.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/decision-receipt.md)
|
|
235
|
+
and [ADR 0001](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0001-decision-receipt-separate-predicate.md).
|
|
236
|
+
|
|
237
|
+
## Docs
|
|
238
|
+
|
|
239
|
+
| For… | Read |
|
|
240
|
+
|---|---|
|
|
241
|
+
| Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](https://github.com/b7n0de/proofbundle/blob/main/docs/FAQ.md) |
|
|
242
|
+
| New to this? plain-terms glossary | [docs/GLOSSARY.md](https://github.com/b7n0de/proofbundle/blob/main/docs/GLOSSARY.md) |
|
|
243
|
+
| Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/REVIEWERS.md) |
|
|
244
|
+
| Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/TRUST_ANCHORS.md) |
|
|
245
|
+
| The demos, tier by tier | [docs/DEMO.md](https://github.com/b7n0de/proofbundle/blob/main/docs/DEMO.md) |
|
|
246
|
+
| The normative format + verification order | [SPEC.md](https://github.com/b7n0de/proofbundle/blob/main/SPEC.md) |
|
|
247
|
+
| Honest comparison to Rekor / in-toto / OMS / ValiChord | [INTEROP.md](https://github.com/b7n0de/proofbundle/blob/main/INTEROP.md) |
|
|
248
|
+
| Regulatory mapping (and what to never claim) | [COMPLIANCE.md](https://github.com/b7n0de/proofbundle/blob/main/COMPLIANCE.md) |
|
|
249
|
+
| Funders / role fit | [docs/PROJECT_BRIEF.md](https://github.com/b7n0de/proofbundle/blob/main/docs/PROJECT_BRIEF.md) |
|
|
250
|
+
| External time anchors + the bring-your-own-type extension interface (v2.0 beta) | [docs/ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/ANCHORS.md) |
|
|
251
|
+
| **Preview:** TEE-attestation bridge (v2.0 beta) | [docs/EXPERIMENTAL_ENCLAVE.md](https://github.com/b7n0de/proofbundle/blob/main/docs/EXPERIMENTAL_ENCLAVE.md) |
|
|
252
|
+
|
|
253
|
+
## Install
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pip install proofbundle # core: offline verify + plain emit (dependency-free)
|
|
257
|
+
pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
|
|
258
|
+
pip install "proofbundle[inspect]" # inspect_ai adapter + hook
|
|
259
|
+
pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 comes from
|
|
263
|
+
`cryptography`; Merkle hashing is RFC 6962.
|
|
264
|
+
|
|
265
|
+
## Status & scope
|
|
266
|
+
|
|
267
|
+
Beta, SemVer-committed, with a CI test suite behind a mutation gate + property-based parser fuzzing. Correctness
|
|
268
|
+
is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
|
|
269
|
+
**not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
|
|
270
|
+
by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
|
|
271
|
+
[SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md).
|
|
272
|
+
|
|
273
|
+
## Contributing
|
|
274
|
+
|
|
275
|
+
See [CONTRIBUTING.md](https://github.com/b7n0de/proofbundle/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/b7n0de/proofbundle/blob/main/CODE_OF_CONDUCT.md). Good first
|
|
276
|
+
issues are labeled [`good-first-issue`](https://github.com/b7n0de/proofbundle/labels/good-first-issue);
|
|
277
|
+
security findings go through [SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md). The verifier core aims to stay small,
|
|
278
|
+
dependency-light, and correct.
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT — see [LICENSE](https://github.com/b7n0de/proofbundle/blob/main/LICENSE).
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
<p align="center"><sub>proofbundle is part of <b>b7n0de</b>, Verified AI Work · <a href="https://b7n0de.com">b7n0de.com</a></sub></p>
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/b7n0de/proofbundle/main/assets/b7n0de-logo-dark.svg">
|
|
5
|
+
<img alt="b7n0de, Verified AI Work" src="https://raw.githubusercontent.com/b7n0de/proofbundle/main/assets/b7n0de-logo.svg" height="60">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
<h1>proofbundle</h1>
|
|
9
|
+
|
|
10
|
+
**AI eval results need receipts.**
|
|
11
|
+
|
|
12
|
+
Turn an AI evaluation result into one portable, offline-verifiable receipt. It proves *who signed
|
|
13
|
+
these exact bytes* and *that nothing changed since* — not that the number is true. Ed25519 + RFC 6962
|
|
14
|
+
Merkle, one file, no server, no network.
|
|
15
|
+
|
|
16
|
+
[](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
|
|
17
|
+
[](https://github.com/b7n0de/proofbundle/actions/workflows/demo-reproducible.yml)
|
|
18
|
+
[](https://pypi.org/project/proofbundle/)
|
|
19
|
+
[](https://pypi.org/project/proofbundle/)
|
|
20
|
+
[](https://github.com/b7n0de/proofbundle/blob/main/LICENSE)
|
|
21
|
+
[](https://doi.org/10.5281/zenodo.21230467)
|
|
22
|
+
[](https://github.com/astral-sh/ruff)
|
|
23
|
+
[](https://github.com/b7n0de/proofbundle/blob/main/scripts/mutation_check.py)
|
|
24
|
+
<!-- SLSA / PEP 740 attestation badges follow once the first attested release lands, see RELEASE.md. -->
|
|
25
|
+
|
|
26
|
+
**Reviewing this for adoption?** Start with the 30-minute adversarial audit path: **[docs/REVIEWERS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/REVIEWERS.md)**.
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## 60-second try (offline)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install "proofbundle[eval]"
|
|
34
|
+
proofbundle demo # honest receipt => OK, six tampers each => FAILED, sample swap caught
|
|
35
|
+
# Inspect-native (METR Task Standard / UK-AISI ecosystem, mockllm, no API key):
|
|
36
|
+
git clone https://github.com/b7n0de/proofbundle && cd proofbundle
|
|
37
|
+
pip install -e ".[eval,inspect]" && make demo # or `make full-demo` for log -> receipt -> verify
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The problem
|
|
41
|
+
|
|
42
|
+
Every AI eval number you read — a safety benchmark, a capability score, a leaderboard entry — is an
|
|
43
|
+
**unverifiable claim**. You trust the lab. There's no portable way to check, offline, that a result
|
|
44
|
+
was signed by a stated party, hasn't been altered, and covers the samples it claims.
|
|
45
|
+
|
|
46
|
+
proofbundle is that check. It's a small MIT-licensed Python tool (a compact, auditable trusted core,
|
|
47
|
+
depends only on [`cryptography`](https://cryptography.io)) that turns a result into a signed
|
|
48
|
+
receipt anyone can verify from a single file — and it's honest about the line it does not cross.
|
|
49
|
+
|
|
50
|
+
## What the demo shows
|
|
51
|
+
|
|
52
|
+
You'll see an honest receipt verify `=> OK`, then six independent tampers each verify `FAILED`, then
|
|
53
|
+
a swapped sample get caught — all in memory. `proofbundle demo` exits non-zero if any tamper slips through,
|
|
54
|
+
so it's also a self-test. Full walkthrough: **[docs/DEMO.md](https://github.com/b7n0de/proofbundle/blob/main/docs/DEMO.md)**.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# verify a real hosted receipt without writing any code:
|
|
58
|
+
curl -fsSL https://raw.githubusercontent.com/b7n0de/proofbundle/main/examples/example_bundle.json -o receipt.json
|
|
59
|
+
proofbundle verify receipt.json # CRYPTO: OK (the verify itself runs fully offline)
|
|
60
|
+
|
|
61
|
+
# your own receipt, from a signed payload:
|
|
62
|
+
proofbundle emit --payload-file result.json --new-key signer.key --out receipt.json
|
|
63
|
+
proofbundle verify receipt.json # exit 0 = crypto OK, 1 = crypto/verification failure, 2 = malformed
|
|
64
|
+
|
|
65
|
+
# apply YOUR trust decision — verify makes NO trust decision on its own:
|
|
66
|
+
proofbundle verify receipt.json --policy trust_policy.json # POLICY: OK | FAIL (exit 3) | NOT_EVALUATED
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Inspect-native? (METR Task Standard / UK-AISI ecosystem)
|
|
70
|
+
|
|
71
|
+
The receipt layer runs directly on [Inspect AI](https://inspect.aisi.org.uk/) — and the proof is
|
|
72
|
+
reproducible offline in minutes:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# setup as in the 60-second try above (clone + pip install -e ".[eval,inspect]"), then:
|
|
76
|
+
make full-demo # a genuine inspect_ai eval log (mockllm: offline, no API key, no GPU)
|
|
77
|
+
# -> signed receipt next to the log -> proofbundle verify => OK
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In your own pipeline the end-of-task hook signs every run automatically. Walkthrough:
|
|
81
|
+
**[docs/INSPECT_HAPPY_PATH.md](https://github.com/b7n0de/proofbundle/blob/main/docs/INSPECT_HAPPY_PATH.md)** · worked example:
|
|
82
|
+
**[examples/inspect_receipt.py](https://github.com/b7n0de/proofbundle/blob/main/examples/inspect_receipt.py)**.
|
|
83
|
+
|
|
84
|
+
## What a receipt proves — and what it doesn't
|
|
85
|
+
|
|
86
|
+
| ✅ It proves | ❌ It does **not** prove |
|
|
87
|
+
|---|---|
|
|
88
|
+
| These exact bytes were signed by this key (**authorship**) | That the number is **true** |
|
|
89
|
+
| Nothing changed since signing (**integrity**, Ed25519 + RFC 6962) | That the **issuer is honest** |
|
|
90
|
+
| The result is attributable to a stated issuer | That the **eval was well-designed** |
|
|
91
|
+
| A threshold was met while hiding the model/dataset (salted commitments) | That there was **no cherry-picking** — unless pre-registered |
|
|
92
|
+
| Optionally: individual samples, offline-auditable (per-sample Merkle) | That the **computation was correct** — that needs a TEE or independent reproduction |
|
|
93
|
+
|
|
94
|
+
This boundary is the point, not a weakness. A receipt makes a claim **attributable, tamper-evident,
|
|
95
|
+
and — with pre-registration and per-sample auditing — bounded and spot-checkable**. Full detail:
|
|
96
|
+
**[THREAT_MODEL.md](https://github.com/b7n0de/proofbundle/blob/main/THREAT_MODEL.md)**.
|
|
97
|
+
|
|
98
|
+
## Post-quantum posture (honest, two layers)
|
|
99
|
+
|
|
100
|
+
proofbundle is **not** "quantum-proof" or "quantum-safe" as a whole. It combines two cryptographic layers
|
|
101
|
+
with very different quantum exposure, and it is honest about both:
|
|
102
|
+
|
|
103
|
+
- **Quantum-robust (hash-based)** — SHA-256, RFC 6962 / 9162 Merkle inclusion, RFC 8785 canonicalization,
|
|
104
|
+
and, among the external time anchors, the OpenTimestamps (Bitcoin hash-chain) and `chia-datalayer/v1`
|
|
105
|
+
(Merkle inclusion) types. Grover only halves the effective bit-strength (SHA-256 keeps a ~128-bit quantum
|
|
106
|
+
margin, which NIST currently treats as adequate), so these stay secure.
|
|
107
|
+
- **Quantum-vulnerable (elliptic-curve / classical PKI, Shor)** — the Ed25519 receipt signature; for the
|
|
108
|
+
`chia-datalayer` anchor, Chia's BLS12-381 wallet layer; and, for the RFC 3161 anchor, the TSA's own
|
|
109
|
+
classical (RSA/ECDSA) certificate-chain signature. A large enough quantum computer could forge any of these.
|
|
110
|
+
|
|
111
|
+
The attack that matters is not decryption but **back-dated forgery**: an attacker with a quantum computer
|
|
112
|
+
could mint a fake signature on a tampered receipt. The defense — **when a receipt carries a hash-based time
|
|
113
|
+
anchor** (optional, the `[anchors]` beta extra: OpenTimestamps or `chia-datalayer`) — is that the anchor
|
|
114
|
+
proves the original receipt existed *before* that capability, so a forged receipt has no matching anchor.
|
|
115
|
+
That protects the evidence long-term even if the signature layer later breaks. A plain receipt with no anchor
|
|
116
|
+
does not carry this property.
|
|
117
|
+
|
|
118
|
+
On the witness side, C2SP checkpoints already carry post-quantum **ML-DSA-44** (FIPS 204) cosignatures
|
|
119
|
+
(`proofbundle[pq]`); a post-quantum *payload* signature — crypto-agility for the receipt itself — is on the
|
|
120
|
+
roadmap.
|
|
121
|
+
|
|
122
|
+
## In plain language
|
|
123
|
+
|
|
124
|
+
A proofbundle receipt is the cash-register receipt of an AI test result: it shows who claimed the
|
|
125
|
+
number and that nobody quietly changed it afterwards. It does not show the test was good — the way a
|
|
126
|
+
cash-register receipt does not show the meal was good — but without a receipt there is nothing to
|
|
127
|
+
check at all.
|
|
128
|
+
|
|
129
|
+
## How it fits together
|
|
130
|
+
|
|
131
|
+
*(diagram renders on GitHub — [view it there](https://github.com/b7n0de/proofbundle#how-it-fits-together); PyPI shows the source)*
|
|
132
|
+
|
|
133
|
+
```mermaid
|
|
134
|
+
flowchart LR
|
|
135
|
+
H["eval harness<br/>inspect_ai · lm-eval · promptfoo · pytest"] --> A["adapter → signed claim<br/>salted commitments · provenance · samples root"]
|
|
136
|
+
A --> R["receipt<br/>one portable file"]
|
|
137
|
+
R --> V{{"proofbundle verify — offline"}}
|
|
138
|
+
V --> C["signature · Merkle inclusion · SD-JWT/KB ·<br/>witness quorum · status list · sample openings"]
|
|
139
|
+
C --> OK(["CRYPTO: OK / FAILED"])
|
|
140
|
+
style V fill:#D6248A,stroke:#D6248A,color:#fff
|
|
141
|
+
style OK fill:#D6248A,stroke:#D6248A,color:#fff
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## What's in the box
|
|
145
|
+
|
|
146
|
+
- **Core** — Ed25519 signature + RFC 6962 / 9162 Merkle inclusion, verified fully offline. Checks a
|
|
147
|
+
real [Sigstore Rekor](https://docs.sigstore.dev/) proof, so correctness isn't self-referential.
|
|
148
|
+
- **Eval receipts** — a signed claim (`metric ⋈ threshold`, `n`, salted model/dataset commitments,
|
|
149
|
+
assurance level, provenance) from your run. See [EVAL_CLAIM.md](https://github.com/b7n0de/proofbundle/blob/main/EVAL_CLAIM.md).
|
|
150
|
+
- **Selective disclosure** — SD-JWT ([RFC 9901](https://datatracker.ietf.org/doc/rfc9901/)) with Key
|
|
151
|
+
Binding: prove a threshold while withholding the exact score.
|
|
152
|
+
- **Transparency-log interop** — C2SP `tlog-checkpoint` / cosignature / `.tlog-proof`, with
|
|
153
|
+
post-quantum **ML-DSA-44** witness cosignatures. Optional Token-Status-List revocation snapshots.
|
|
154
|
+
- **Per-sample audit** — commit to every sample; an auditor challenges random indices (with a fresh
|
|
155
|
+
nonce or a **public randomness beacon**, v1.9) and openings must bind to the signed root. With
|
|
156
|
+
such an auditor-supplied or beacon-bound challenge, 300 samples catch 1% sample-doctoring with 95%
|
|
157
|
+
confidence, regardless of run size — a challenge the issuer chose itself does not give this
|
|
158
|
+
guarantee.
|
|
159
|
+
- **Pre-registration** — `proofbundle prereg <plan>` commits to the protocol before the run, so
|
|
160
|
+
best-of-many publishing becomes visible.
|
|
161
|
+
- **Integrations** — opt-in inspect_ai end-of-task hook and pytest plugin (emit only when
|
|
162
|
+
`PROOFBUNDLE_EMIT=1` / `--proofbundle`), plus a Hugging Face Community Evals bridge. See
|
|
163
|
+
[INTEGRATIONS.md](https://github.com/b7n0de/proofbundle/blob/main/INTEGRATIONS.md), or the end-to-end walkthrough
|
|
164
|
+
[docs/INSPECT_HAPPY_PATH.md](https://github.com/b7n0de/proofbundle/blob/main/docs/INSPECT_HAPPY_PATH.md) — run an eval, get a receipt, verify it offline.
|
|
165
|
+
- **External time anchors** *(v2.0 beta, the `[anchors]` extra)* — an optional `anchors[]` layer that
|
|
166
|
+
attaches external evidence of *when* a commitment or receipt existed, from a party the producer does not
|
|
167
|
+
control. Two built-in types verify offline: **RFC 3161** TSA tokens (against a frozen cert chain) and
|
|
168
|
+
**OpenTimestamps** Bitcoin proofs (honest pending → confirmed lifecycle). A `register_anchor_type`
|
|
169
|
+
extension interface lets a third party ship its own fail-closed type; two worked examples ship — a
|
|
170
|
+
first-party **`chia-datalayer/v1`** (offline Merkle inclusion of a canonical root under a published Chia
|
|
171
|
+
DataLayer root) and a third-party **`markovian-provenance/v1`** (a wallet-attributable, Bitcoin-anchored
|
|
172
|
+
stamp). See [docs/ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/ANCHORS.md).
|
|
173
|
+
- **Decision Receipts** *(2.1, vendored `decision-receipt/v0.1` predicate)* — a separate predicate for agent
|
|
174
|
+
*decisions* (not eval metrics): who decided, the proposed action, the policy boundary, digest-bound evidence,
|
|
175
|
+
the verdict (`ALLOW`/`DENY`/`REFUSE`/`ESCALATE`/`DEFER`/`OBSERVE`), and explicitly what was *not* checked.
|
|
176
|
+
`proofbundle decision emit|verify` with a v0.2 trust policy (`trusted_decision_makers`, `accepted_predicate_
|
|
177
|
+
types`). An eval receipt says *the number is authored and intact*; a Decision Receipt says *this decision was
|
|
178
|
+
made by this gate over this evidence* — never that the decision was correct. `actionOutcome=executed` without
|
|
179
|
+
a signed outcome is self-assertion. See
|
|
180
|
+
[docs/predicates/decision-receipt.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/decision-receipt.md)
|
|
181
|
+
and [ADR 0001](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0001-decision-receipt-separate-predicate.md).
|
|
182
|
+
|
|
183
|
+
## Docs
|
|
184
|
+
|
|
185
|
+
| For… | Read |
|
|
186
|
+
|---|---|
|
|
187
|
+
| Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](https://github.com/b7n0de/proofbundle/blob/main/docs/FAQ.md) |
|
|
188
|
+
| New to this? plain-terms glossary | [docs/GLOSSARY.md](https://github.com/b7n0de/proofbundle/blob/main/docs/GLOSSARY.md) |
|
|
189
|
+
| Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/REVIEWERS.md) |
|
|
190
|
+
| Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/TRUST_ANCHORS.md) |
|
|
191
|
+
| The demos, tier by tier | [docs/DEMO.md](https://github.com/b7n0de/proofbundle/blob/main/docs/DEMO.md) |
|
|
192
|
+
| The normative format + verification order | [SPEC.md](https://github.com/b7n0de/proofbundle/blob/main/SPEC.md) |
|
|
193
|
+
| Honest comparison to Rekor / in-toto / OMS / ValiChord | [INTEROP.md](https://github.com/b7n0de/proofbundle/blob/main/INTEROP.md) |
|
|
194
|
+
| Regulatory mapping (and what to never claim) | [COMPLIANCE.md](https://github.com/b7n0de/proofbundle/blob/main/COMPLIANCE.md) |
|
|
195
|
+
| Funders / role fit | [docs/PROJECT_BRIEF.md](https://github.com/b7n0de/proofbundle/blob/main/docs/PROJECT_BRIEF.md) |
|
|
196
|
+
| External time anchors + the bring-your-own-type extension interface (v2.0 beta) | [docs/ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/ANCHORS.md) |
|
|
197
|
+
| **Preview:** TEE-attestation bridge (v2.0 beta) | [docs/EXPERIMENTAL_ENCLAVE.md](https://github.com/b7n0de/proofbundle/blob/main/docs/EXPERIMENTAL_ENCLAVE.md) |
|
|
198
|
+
|
|
199
|
+
## Install
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pip install proofbundle # core: offline verify + plain emit (dependency-free)
|
|
203
|
+
pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
|
|
204
|
+
pip install "proofbundle[inspect]" # inspect_ai adapter + hook
|
|
205
|
+
pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 comes from
|
|
209
|
+
`cryptography`; Merkle hashing is RFC 6962.
|
|
210
|
+
|
|
211
|
+
## Status & scope
|
|
212
|
+
|
|
213
|
+
Beta, SemVer-committed, with a CI test suite behind a mutation gate + property-based parser fuzzing. Correctness
|
|
214
|
+
is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
|
|
215
|
+
**not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
|
|
216
|
+
by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
|
|
217
|
+
[SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md).
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
See [CONTRIBUTING.md](https://github.com/b7n0de/proofbundle/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/b7n0de/proofbundle/blob/main/CODE_OF_CONDUCT.md). Good first
|
|
222
|
+
issues are labeled [`good-first-issue`](https://github.com/b7n0de/proofbundle/labels/good-first-issue);
|
|
223
|
+
security findings go through [SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md). The verifier core aims to stay small,
|
|
224
|
+
dependency-light, and correct.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT — see [LICENSE](https://github.com/b7n0de/proofbundle/blob/main/LICENSE).
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
<p align="center"><sub>proofbundle is part of <b>b7n0de</b>, Verified AI Work · <a href="https://b7n0de.com">b7n0de.com</a></sub></p>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proofbundle"
|
|
7
|
-
version = "2.0
|
|
7
|
+
version = "2.1.0"
|
|
8
8
|
description = "Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -52,6 +52,11 @@ eval = ["rfc8785>=0.1.4"]
|
|
|
52
52
|
anchors = ["rfc3161-client>=1.0.6", "opentimestamps>=0.4.5", "rfc8785>=0.1.4"]
|
|
53
53
|
# The lm-eval adapter reads exported results.json (no import) → pure stdlib.
|
|
54
54
|
adapters = []
|
|
55
|
+
# chia-datalayer/v1 anchor WRITER (anchors_chia_add). Needs no extra Python package — it drives the
|
|
56
|
+
# stable `chia rpc data_layer|full_node` CLI of a locally installed, cert-authed Chia node (the DataLayer
|
|
57
|
+
# node is the real dependency, not a wheel). Declared so `pip install proofbundle[chia]` is a documented,
|
|
58
|
+
# discoverable install target. VERIFYING an anchor offline (anchors_chia) needs neither this nor a node.
|
|
59
|
+
chia = []
|
|
55
60
|
# EXPERIMENTAL (v2.0 preview): the TEE-attestation bridge (proofbundle.experimental.enclave).
|
|
56
61
|
# Opt-in and clearly unstable — API/wire-format may change or be removed without deprecation.
|
|
57
62
|
# No new runtime dependency (pure stdlib + the core `cryptography`); the extra exists to signal
|
|
@@ -13,10 +13,16 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
|
-
__version__ = "2.0
|
|
16
|
+
__version__ = "2.1.0"
|
|
17
|
+
|
|
18
|
+
# The `proofbundle/v0.1` normative spec revision this build implements — kept in sync with the
|
|
19
|
+
# `Revision:` line at the top of SPEC.md by tests/test_docs_truth.py (WP-B1, closes #28). Bump
|
|
20
|
+
# both together whenever SPEC.md's normative text changes (not on every package release).
|
|
21
|
+
SPEC_REVISION = "2026-07-10"
|
|
17
22
|
|
|
18
23
|
__all__ = [
|
|
19
24
|
"__version__",
|
|
25
|
+
"SPEC_REVISION",
|
|
20
26
|
"SCHEMA",
|
|
21
27
|
"verify_bundle",
|
|
22
28
|
"load_bundle",
|
|
@@ -39,6 +45,8 @@ __all__ = [
|
|
|
39
45
|
"prereg_hash",
|
|
40
46
|
"verify_prereg",
|
|
41
47
|
"beacon_audit_challenge",
|
|
48
|
+
"canonicalize_statement",
|
|
49
|
+
"statement_content_root",
|
|
42
50
|
"VerificationResult",
|
|
43
51
|
"Check",
|
|
44
52
|
"ProofBundleError",
|
|
@@ -65,6 +73,8 @@ _LAZY = {
|
|
|
65
73
|
"prereg_hash": ".prereg",
|
|
66
74
|
"verify_prereg": ".prereg",
|
|
67
75
|
"beacon_audit_challenge": ".beacon",
|
|
76
|
+
"canonicalize_statement": ".canonical",
|
|
77
|
+
"statement_content_root": ".canonical",
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
if TYPE_CHECKING: # static analysers + IDEs see the real names/types; runtime stays lazy
|
|
@@ -77,6 +87,7 @@ if TYPE_CHECKING: # static analysers + IDEs see the real names/types; runtime s
|
|
|
77
87
|
from .persample import (audit_challenge, build_sample_tree, sample_opening,
|
|
78
88
|
verify_sample_opening)
|
|
79
89
|
from .beacon import beacon_audit_challenge
|
|
90
|
+
from .canonical import canonicalize_statement, statement_content_root
|
|
80
91
|
from .prereg import prereg_hash, verify_prereg
|
|
81
92
|
from .statuslist import verify_status_snapshot
|
|
82
93
|
from .tlogproof import verify_tlog_proof
|