proofbundle 2.0.0b3__tar.gz → 3.0.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.
Files changed (146) hide show
  1. proofbundle-3.0.0/PKG-INFO +309 -0
  2. proofbundle-3.0.0/README.md +255 -0
  3. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/pyproject.toml +4 -1
  4. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/__init__.py +12 -1
  5. proofbundle-3.0.0/src/proofbundle/_strict_json.py +72 -0
  6. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/eee.py +62 -0
  7. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/anchors.py +124 -16
  8. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/anchors_chia.py +2 -2
  9. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/anchors_markovian.py +29 -12
  10. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/anchors_ots.py +44 -20
  11. proofbundle-3.0.0/src/proofbundle/anchors_rfc3161.py +146 -0
  12. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/bundle.py +126 -17
  13. proofbundle-3.0.0/src/proofbundle/canonical.py +136 -0
  14. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/checkpoint.py +2 -2
  15. proofbundle-3.0.0/src/proofbundle/cli.py +1398 -0
  16. proofbundle-3.0.0/src/proofbundle/decision.py +515 -0
  17. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/evalclaim.py +28 -12
  18. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/experimental/enclave.py +7 -3
  19. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/hf_evals.py +91 -7
  20. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/intoto.py +192 -45
  21. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/kbjwt.py +12 -3
  22. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/merkle.py +9 -1
  23. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/persample.py +8 -1
  24. proofbundle-3.0.0/src/proofbundle/policy.py +663 -0
  25. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/sdjwt.py +19 -3
  26. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/sdjwt_issue.py +25 -12
  27. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/statuslist.py +10 -3
  28. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/tlogproof.py +5 -0
  29. proofbundle-3.0.0/src/proofbundle.egg-info/PKG-INFO +309 -0
  30. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle.egg-info/SOURCES.txt +30 -0
  31. proofbundle-3.0.0/tests/test_anchor_target_trustedtime.py +209 -0
  32. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_anchors_generic.py +44 -0
  33. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_anchors_markovian.py +25 -12
  34. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_anchors_ots.py +27 -17
  35. proofbundle-3.0.0/tests/test_anchors_rfc3161.py +250 -0
  36. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_bundle_robustness.py +38 -2
  37. proofbundle-3.0.0/tests/test_canonical.py +136 -0
  38. proofbundle-3.0.0/tests/test_claims_hygiene.py +194 -0
  39. proofbundle-3.0.0/tests/test_cli.py +182 -0
  40. proofbundle-3.0.0/tests/test_cli_require_anchor.py +308 -0
  41. proofbundle-3.0.0/tests/test_conformance.py +167 -0
  42. proofbundle-3.0.0/tests/test_decision_anchors.py +137 -0
  43. proofbundle-3.0.0/tests/test_decision_cli.py +145 -0
  44. proofbundle-3.0.0/tests/test_decision_evidence.py +84 -0
  45. proofbundle-3.0.0/tests/test_decision_fuzz.py +123 -0
  46. proofbundle-3.0.0/tests/test_decision_hardening.py +137 -0
  47. proofbundle-3.0.0/tests/test_decision_policy.py +183 -0
  48. proofbundle-3.0.0/tests/test_decision_schema.py +103 -0
  49. proofbundle-3.0.0/tests/test_decision_validator_api.py +82 -0
  50. proofbundle-3.0.0/tests/test_decision_verify.py +183 -0
  51. proofbundle-3.0.0/tests/test_docs_truth.py +118 -0
  52. proofbundle-3.0.0/tests/test_dup_key_reject.py +319 -0
  53. proofbundle-3.0.0/tests/test_ed25519_semantics.py +99 -0
  54. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_evalclaim.py +17 -0
  55. proofbundle-3.0.0/tests/test_fork_pr_secret_isolation.py +283 -0
  56. proofbundle-3.0.0/tests/test_hf_entry_verify_eee_digest.py +187 -0
  57. proofbundle-3.0.0/tests/test_intoto_content_root_migration.py +311 -0
  58. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_kbjwt.py +11 -6
  59. proofbundle-3.0.0/tests/test_ok_semantics.py +403 -0
  60. proofbundle-3.0.0/tests/test_oversized_int_guard.py +41 -0
  61. proofbundle-3.0.0/tests/test_pinned_key_validation.py +76 -0
  62. proofbundle-3.0.0/tests/test_policy_explain_lint.py +192 -0
  63. proofbundle-3.0.0/tests/test_predicate_type_enforcement.py +102 -0
  64. proofbundle-3.0.0/tests/test_sdjwt_duplicate_cnf.py +181 -0
  65. proofbundle-3.0.0/tests/test_sdjwt_verify_binding.py +168 -0
  66. proofbundle-3.0.0/tests/test_trust_policy.py +459 -0
  67. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_verify_matrix.py +5 -1
  68. proofbundle-2.0.0b3/PKG-INFO +0 -220
  69. proofbundle-2.0.0b3/README.md +0 -166
  70. proofbundle-2.0.0b3/src/proofbundle/anchors_rfc3161.py +0 -90
  71. proofbundle-2.0.0b3/src/proofbundle/cli.py +0 -590
  72. proofbundle-2.0.0b3/src/proofbundle.egg-info/PKG-INFO +0 -220
  73. proofbundle-2.0.0b3/tests/test_anchors_rfc3161.py +0 -115
  74. proofbundle-2.0.0b3/tests/test_claims_hygiene.py +0 -48
  75. proofbundle-2.0.0b3/tests/test_cli.py +0 -91
  76. proofbundle-2.0.0b3/tests/test_docs_truth.py +0 -46
  77. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/LICENSE +0 -0
  78. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/setup.cfg +0 -0
  79. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/_inspect_registry.py +0 -0
  80. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/_integration.py +0 -0
  81. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/__init__.py +0 -0
  82. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/_provenance.py +0 -0
  83. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/inspect_ai.py +0 -0
  84. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/lm_eval.py +0 -0
  85. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/promptfoo.py +0 -0
  86. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/adapters/samples.py +0 -0
  87. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/anchors_chia_add.py +0 -0
  88. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/beacon.py +0 -0
  89. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/demo.py +0 -0
  90. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/dsse.py +0 -0
  91. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/eee_eval_schema.json +0 -0
  92. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/emit.py +0 -0
  93. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/errors.py +0 -0
  94. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/experimental/__init__.py +0 -0
  95. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/inspect_hook.py +0 -0
  96. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/prereg.py +0 -0
  97. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/py.typed +0 -0
  98. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/pytest_plugin.py +0 -0
  99. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle/signature.py +0 -0
  100. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle.egg-info/dependency_links.txt +0 -0
  101. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle.egg-info/entry_points.txt +0 -0
  102. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle.egg-info/requires.txt +0 -0
  103. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/src/proofbundle.egg-info/top_level.txt +0 -0
  104. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_adapters.py +0 -0
  105. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_adversarial.py +0 -0
  106. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_anchors_chia.py +0 -0
  107. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_anchors_chia_add.py +0 -0
  108. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_anchors_chia_claims.py +0 -0
  109. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_beacon.py +0 -0
  110. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_branch_base_check.py +0 -0
  111. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_bundle.py +0 -0
  112. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_checkpoint.py +0 -0
  113. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_cli_eval.py +0 -0
  114. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_cosignature.py +0 -0
  115. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_cosignature_mldsa.py +0 -0
  116. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_demo.py +0 -0
  117. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_eee.py +0 -0
  118. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_emit.py +0 -0
  119. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_eval_claim_schema.py +0 -0
  120. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_examples.py +0 -0
  121. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_experimental_enclave.py +0 -0
  122. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_fuzz_parsers.py +0 -0
  123. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_hf_evals.py +0 -0
  124. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_inspect_hook.py +0 -0
  125. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto.py +0 -0
  126. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto_claims_hygiene.py +0 -0
  127. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto_dsse.py +0 -0
  128. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto_eval_result.py +0 -0
  129. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto_examples.py +0 -0
  130. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto_spec_diff.py +0 -0
  131. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_intoto_svr.py +0 -0
  132. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_merkle.py +0 -0
  133. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_merkle_property.py +0 -0
  134. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_persample.py +0 -0
  135. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_prereg.py +0 -0
  136. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_promptfoo.py +0 -0
  137. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_provenance.py +0 -0
  138. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_pytest_plugin.py +0 -0
  139. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_rekor_interop.py +0 -0
  140. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_rfc6962_external_vectors.py +0 -0
  141. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_schema.py +0 -0
  142. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_sdjwt_issue.py +0 -0
  143. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_sdjwt_reference.py +0 -0
  144. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_signature.py +0 -0
  145. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_statuslist.py +0 -0
  146. {proofbundle-2.0.0b3 → proofbundle-3.0.0}/tests/test_tlogproof.py +0 -0
@@ -0,0 +1,309 @@
1
+ Metadata-Version: 2.4
2
+ Name: proofbundle
3
+ Version: 3.0.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
+ [![CI](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml/badge.svg)](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
71
+ [![demo reproducible](https://github.com/b7n0de/proofbundle/actions/workflows/demo-reproducible.yml/badge.svg)](https://github.com/b7n0de/proofbundle/actions/workflows/demo-reproducible.yml)
72
+ [![PyPI](https://img.shields.io/pypi/v/proofbundle.svg)](https://pypi.org/project/proofbundle/)
73
+ [![Python](https://img.shields.io/pypi/pyversions/proofbundle.svg)](https://pypi.org/project/proofbundle/)
74
+ [![License: MIT](https://img.shields.io/badge/license-MIT-D6248A.svg)](https://github.com/b7n0de/proofbundle/blob/main/LICENSE)
75
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21230467.svg)](https://doi.org/10.5281/zenodo.21230467)
76
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
77
+ [![Mutation tested](https://img.shields.io/badge/tests-mutation_gated-D6248A.svg)](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. Secure-by-default in 3.0.0 (breaking): an
206
+ unsigned SD-JWT, or one whose disclosures do not bind this bundle, now fails verification (was warn-only).
207
+ - **Transparency-log interop** — C2SP `tlog-checkpoint` / cosignature / `.tlog-proof`, with
208
+ post-quantum **ML-DSA-44** witness cosignatures. Optional Token-Status-List revocation snapshots.
209
+ - **Per-sample audit** — commit to every sample; an auditor challenges random indices (with a fresh
210
+ nonce or a **public randomness beacon**, v1.9) and openings must bind to the signed root. With
211
+ such an auditor-supplied or beacon-bound challenge, 300 samples catch 1% sample-doctoring with 95%
212
+ confidence, regardless of run size — a challenge the issuer chose itself does not give this
213
+ guarantee.
214
+ - **Pre-registration** — `proofbundle prereg <plan>` commits to the protocol before the run, so
215
+ best-of-many publishing becomes visible.
216
+ - **Integrations** — opt-in inspect_ai end-of-task hook and pytest plugin (emit only when
217
+ `PROOFBUNDLE_EMIT=1` / `--proofbundle`), plus a Hugging Face Community Evals bridge. See
218
+ [INTEGRATIONS.md](https://github.com/b7n0de/proofbundle/blob/main/INTEGRATIONS.md), or the end-to-end walkthrough
219
+ [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.
220
+ - **External time anchors** *(v2.0 beta, the `[anchors]` extra)* — an optional `anchors[]` layer that
221
+ attaches external evidence of *when* a commitment or receipt existed, from a party the producer does not
222
+ control. Two built-in types verify offline: **RFC 3161** TSA tokens (against a relying-party-supplied TSA
223
+ root, see the 3.0.0 trust note below) and **OpenTimestamps** Bitcoin proofs (honest pending → confirmed lifecycle). A `register_anchor_type`
224
+ extension interface lets a third party ship its own fail-closed type; two worked examples ship — a
225
+ first-party **`chia-datalayer/v1`** (offline Merkle inclusion of a canonical root under a published Chia
226
+ DataLayer root) and a third-party **`markovian-provenance/v1`** (a wallet-attributable, Bitcoin-anchored
227
+ stamp). **Since 2.1:** a `verify --require-anchor` relying-party gate (optionally narrowed by
228
+ `--anchor-type`) turns "no verifying anchor of that type" into a failure layered over the crypto result
229
+ (exit 3, like `--policy`); a pending anchor does not satisfy it unless `--allow-pending`. Plus RFC 3161
230
+ hardening — the frozen cert chain is validated at the token's own `gen_time`, with optional `policyOid`
231
+ pinning. **Breaking in 3.0.0:** an anchor's TRUST now comes only from the relying party — supply a TSA
232
+ root (`--trusted-tsa-root`) or a Bitcoin block header (`--bitcoin-header`), or the equivalent `anchors`
233
+ policy keys; the bundle's producer-controlled `frozen` block is evidence, never a trust source, so
234
+ `--require-anchor` without relying-party trust material is unmet (exit 3). An anchor stays detached from
235
+ the content root, and the `statement` target is RESERVED for decision receipts.
236
+ See [docs/ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/ANCHORS.md).
237
+ - **Universal content root** *(2.1, `jcs-sha256-v1`, [ADR 0002](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0002-universal-content-root.md))* — one shared primitive now underlies both the
238
+ decision-receipt path and the in-toto eval-result / test-result / SVR exports: SHA-256 over the RFC 8785
239
+ (JCS) canonical bytes of the full pre-signature Statement, so a content root survives counter-signing and
240
+ key rotation. The algorithm is a versioned id signed inside the payload (`contentRootAlg`, default
241
+ `jcs-sha256-v1`); a verifier re-serializes with exactly the declared algorithm, never falls back, and an
242
+ unknown algorithm fails closed. Migration is a compatible evolution, not a cutover: absent `contentRootAlg`
243
+ ⇒ the historic `legacy-sortkeys-json-v0` mode, so every already-signed 2.0.0 receipt keeps verifying
244
+ byte-for-byte. This is **not** a completed universal migration — a CLI flag to select the algorithm is still
245
+ deferred. Independent cross-implementation (MarkovianProtocol) interop is now proven for RFC 8785
246
+ canonicalization + content-root binding (see `conformance/decision/crossimpl/`); the same corpus additionally
247
+ verifies a confirmed Bitcoin anchor (block 957504) offline. The external fixture currently reports 12 findings
248
+ against the enforced `decision-receipt/v0.1` validator — recorded as an expected-fail, not hidden — so full
249
+ schema conformance awaits a further schema-conformant regeneration.
250
+ - **Decision Receipts** *(2.1, vendored `decision-receipt/v0.1` predicate)* — a separate predicate for agent
251
+ *decisions* (not eval metrics): who decided, the proposed action, the policy boundary, digest-bound evidence,
252
+ the verdict (`ALLOW`/`DENY`/`REFUSE`/`ESCALATE`/`DEFER`/`OBSERVE`), and explicitly what was *not* checked.
253
+ `proofbundle decision emit|verify` with a v0.2 trust policy (`trusted_decision_makers`, `accepted_predicate_
254
+ types`). An eval receipt says *the number is authored and intact*; a Decision Receipt says *this decision was
255
+ made by this gate over this evidence* — never that the decision was correct. `actionOutcome=executed` without
256
+ a signed outcome is self-assertion. See
257
+ [docs/predicates/decision-receipt.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/decision-receipt.md)
258
+ and [ADR 0001](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0001-decision-receipt-separate-predicate.md).
259
+
260
+ ## Docs
261
+
262
+ | For… | Read |
263
+ |---|---|
264
+ | Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](https://github.com/b7n0de/proofbundle/blob/main/docs/FAQ.md) |
265
+ | New to this? plain-terms glossary | [docs/GLOSSARY.md](https://github.com/b7n0de/proofbundle/blob/main/docs/GLOSSARY.md) |
266
+ | Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/REVIEWERS.md) |
267
+ | Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/TRUST_ANCHORS.md) |
268
+ | The demos, tier by tier | [docs/DEMO.md](https://github.com/b7n0de/proofbundle/blob/main/docs/DEMO.md) |
269
+ | The normative format + verification order | [SPEC.md](https://github.com/b7n0de/proofbundle/blob/main/SPEC.md) |
270
+ | Honest comparison to Rekor / in-toto / OMS / ValiChord | [INTEROP.md](https://github.com/b7n0de/proofbundle/blob/main/INTEROP.md) |
271
+ | Regulatory mapping (and what to never claim) | [COMPLIANCE.md](https://github.com/b7n0de/proofbundle/blob/main/COMPLIANCE.md) |
272
+ | Funders / role fit | [docs/PROJECT_BRIEF.md](https://github.com/b7n0de/proofbundle/blob/main/docs/PROJECT_BRIEF.md) |
273
+ | 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) |
274
+ | **Preview:** TEE-attestation bridge (v2.0 beta) | [docs/EXPERIMENTAL_ENCLAVE.md](https://github.com/b7n0de/proofbundle/blob/main/docs/EXPERIMENTAL_ENCLAVE.md) |
275
+
276
+ ## Install
277
+
278
+ ```bash
279
+ pip install proofbundle # core: offline verify + plain emit (dependency-free)
280
+ pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
281
+ pip install "proofbundle[inspect]" # inspect_ai adapter + hook
282
+ pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
283
+ ```
284
+
285
+ Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 comes from
286
+ `cryptography`; Merkle hashing is RFC 6962.
287
+
288
+ ## Status & scope
289
+
290
+ Beta, SemVer-committed, with a CI test suite behind a mutation gate + property-based parser fuzzing. Correctness
291
+ is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
292
+ **not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
293
+ by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
294
+ [SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md).
295
+
296
+ ## Contributing
297
+
298
+ 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
299
+ issues are labeled [`good-first-issue`](https://github.com/b7n0de/proofbundle/labels/good-first-issue);
300
+ security findings go through [SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md). The verifier core aims to stay small,
301
+ dependency-light, and correct.
302
+
303
+ ## License
304
+
305
+ MIT — see [LICENSE](https://github.com/b7n0de/proofbundle/blob/main/LICENSE).
306
+
307
+ ---
308
+
309
+ <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,255 @@
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
+ [![CI](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml/badge.svg)](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
17
+ [![demo reproducible](https://github.com/b7n0de/proofbundle/actions/workflows/demo-reproducible.yml/badge.svg)](https://github.com/b7n0de/proofbundle/actions/workflows/demo-reproducible.yml)
18
+ [![PyPI](https://img.shields.io/pypi/v/proofbundle.svg)](https://pypi.org/project/proofbundle/)
19
+ [![Python](https://img.shields.io/pypi/pyversions/proofbundle.svg)](https://pypi.org/project/proofbundle/)
20
+ [![License: MIT](https://img.shields.io/badge/license-MIT-D6248A.svg)](https://github.com/b7n0de/proofbundle/blob/main/LICENSE)
21
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21230467.svg)](https://doi.org/10.5281/zenodo.21230467)
22
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
23
+ [![Mutation tested](https://img.shields.io/badge/tests-mutation_gated-D6248A.svg)](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. Secure-by-default in 3.0.0 (breaking): an
152
+ unsigned SD-JWT, or one whose disclosures do not bind this bundle, now fails verification (was warn-only).
153
+ - **Transparency-log interop** — C2SP `tlog-checkpoint` / cosignature / `.tlog-proof`, with
154
+ post-quantum **ML-DSA-44** witness cosignatures. Optional Token-Status-List revocation snapshots.
155
+ - **Per-sample audit** — commit to every sample; an auditor challenges random indices (with a fresh
156
+ nonce or a **public randomness beacon**, v1.9) and openings must bind to the signed root. With
157
+ such an auditor-supplied or beacon-bound challenge, 300 samples catch 1% sample-doctoring with 95%
158
+ confidence, regardless of run size — a challenge the issuer chose itself does not give this
159
+ guarantee.
160
+ - **Pre-registration** — `proofbundle prereg <plan>` commits to the protocol before the run, so
161
+ best-of-many publishing becomes visible.
162
+ - **Integrations** — opt-in inspect_ai end-of-task hook and pytest plugin (emit only when
163
+ `PROOFBUNDLE_EMIT=1` / `--proofbundle`), plus a Hugging Face Community Evals bridge. See
164
+ [INTEGRATIONS.md](https://github.com/b7n0de/proofbundle/blob/main/INTEGRATIONS.md), or the end-to-end walkthrough
165
+ [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.
166
+ - **External time anchors** *(v2.0 beta, the `[anchors]` extra)* — an optional `anchors[]` layer that
167
+ attaches external evidence of *when* a commitment or receipt existed, from a party the producer does not
168
+ control. Two built-in types verify offline: **RFC 3161** TSA tokens (against a relying-party-supplied TSA
169
+ root, see the 3.0.0 trust note below) and **OpenTimestamps** Bitcoin proofs (honest pending → confirmed lifecycle). A `register_anchor_type`
170
+ extension interface lets a third party ship its own fail-closed type; two worked examples ship — a
171
+ first-party **`chia-datalayer/v1`** (offline Merkle inclusion of a canonical root under a published Chia
172
+ DataLayer root) and a third-party **`markovian-provenance/v1`** (a wallet-attributable, Bitcoin-anchored
173
+ stamp). **Since 2.1:** a `verify --require-anchor` relying-party gate (optionally narrowed by
174
+ `--anchor-type`) turns "no verifying anchor of that type" into a failure layered over the crypto result
175
+ (exit 3, like `--policy`); a pending anchor does not satisfy it unless `--allow-pending`. Plus RFC 3161
176
+ hardening — the frozen cert chain is validated at the token's own `gen_time`, with optional `policyOid`
177
+ pinning. **Breaking in 3.0.0:** an anchor's TRUST now comes only from the relying party — supply a TSA
178
+ root (`--trusted-tsa-root`) or a Bitcoin block header (`--bitcoin-header`), or the equivalent `anchors`
179
+ policy keys; the bundle's producer-controlled `frozen` block is evidence, never a trust source, so
180
+ `--require-anchor` without relying-party trust material is unmet (exit 3). An anchor stays detached from
181
+ the content root, and the `statement` target is RESERVED for decision receipts.
182
+ See [docs/ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/ANCHORS.md).
183
+ - **Universal content root** *(2.1, `jcs-sha256-v1`, [ADR 0002](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0002-universal-content-root.md))* — one shared primitive now underlies both the
184
+ decision-receipt path and the in-toto eval-result / test-result / SVR exports: SHA-256 over the RFC 8785
185
+ (JCS) canonical bytes of the full pre-signature Statement, so a content root survives counter-signing and
186
+ key rotation. The algorithm is a versioned id signed inside the payload (`contentRootAlg`, default
187
+ `jcs-sha256-v1`); a verifier re-serializes with exactly the declared algorithm, never falls back, and an
188
+ unknown algorithm fails closed. Migration is a compatible evolution, not a cutover: absent `contentRootAlg`
189
+ ⇒ the historic `legacy-sortkeys-json-v0` mode, so every already-signed 2.0.0 receipt keeps verifying
190
+ byte-for-byte. This is **not** a completed universal migration — a CLI flag to select the algorithm is still
191
+ deferred. Independent cross-implementation (MarkovianProtocol) interop is now proven for RFC 8785
192
+ canonicalization + content-root binding (see `conformance/decision/crossimpl/`); the same corpus additionally
193
+ verifies a confirmed Bitcoin anchor (block 957504) offline. The external fixture currently reports 12 findings
194
+ against the enforced `decision-receipt/v0.1` validator — recorded as an expected-fail, not hidden — so full
195
+ schema conformance awaits a further schema-conformant regeneration.
196
+ - **Decision Receipts** *(2.1, vendored `decision-receipt/v0.1` predicate)* — a separate predicate for agent
197
+ *decisions* (not eval metrics): who decided, the proposed action, the policy boundary, digest-bound evidence,
198
+ the verdict (`ALLOW`/`DENY`/`REFUSE`/`ESCALATE`/`DEFER`/`OBSERVE`), and explicitly what was *not* checked.
199
+ `proofbundle decision emit|verify` with a v0.2 trust policy (`trusted_decision_makers`, `accepted_predicate_
200
+ types`). An eval receipt says *the number is authored and intact*; a Decision Receipt says *this decision was
201
+ made by this gate over this evidence* — never that the decision was correct. `actionOutcome=executed` without
202
+ a signed outcome is self-assertion. See
203
+ [docs/predicates/decision-receipt.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/decision-receipt.md)
204
+ and [ADR 0001](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0001-decision-receipt-separate-predicate.md).
205
+
206
+ ## Docs
207
+
208
+ | For… | Read |
209
+ |---|---|
210
+ | Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](https://github.com/b7n0de/proofbundle/blob/main/docs/FAQ.md) |
211
+ | New to this? plain-terms glossary | [docs/GLOSSARY.md](https://github.com/b7n0de/proofbundle/blob/main/docs/GLOSSARY.md) |
212
+ | Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/REVIEWERS.md) |
213
+ | Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](https://github.com/b7n0de/proofbundle/blob/main/docs/TRUST_ANCHORS.md) |
214
+ | The demos, tier by tier | [docs/DEMO.md](https://github.com/b7n0de/proofbundle/blob/main/docs/DEMO.md) |
215
+ | The normative format + verification order | [SPEC.md](https://github.com/b7n0de/proofbundle/blob/main/SPEC.md) |
216
+ | Honest comparison to Rekor / in-toto / OMS / ValiChord | [INTEROP.md](https://github.com/b7n0de/proofbundle/blob/main/INTEROP.md) |
217
+ | Regulatory mapping (and what to never claim) | [COMPLIANCE.md](https://github.com/b7n0de/proofbundle/blob/main/COMPLIANCE.md) |
218
+ | Funders / role fit | [docs/PROJECT_BRIEF.md](https://github.com/b7n0de/proofbundle/blob/main/docs/PROJECT_BRIEF.md) |
219
+ | 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) |
220
+ | **Preview:** TEE-attestation bridge (v2.0 beta) | [docs/EXPERIMENTAL_ENCLAVE.md](https://github.com/b7n0de/proofbundle/blob/main/docs/EXPERIMENTAL_ENCLAVE.md) |
221
+
222
+ ## Install
223
+
224
+ ```bash
225
+ pip install proofbundle # core: offline verify + plain emit (dependency-free)
226
+ pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
227
+ pip install "proofbundle[inspect]" # inspect_ai adapter + hook
228
+ pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
229
+ ```
230
+
231
+ Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 comes from
232
+ `cryptography`; Merkle hashing is RFC 6962.
233
+
234
+ ## Status & scope
235
+
236
+ Beta, SemVer-committed, with a CI test suite behind a mutation gate + property-based parser fuzzing. Correctness
237
+ is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
238
+ **not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
239
+ by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
240
+ [SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md).
241
+
242
+ ## Contributing
243
+
244
+ 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
245
+ issues are labeled [`good-first-issue`](https://github.com/b7n0de/proofbundle/labels/good-first-issue);
246
+ security findings go through [SECURITY.md](https://github.com/b7n0de/proofbundle/blob/main/SECURITY.md). The verifier core aims to stay small,
247
+ dependency-light, and correct.
248
+
249
+ ## License
250
+
251
+ MIT — see [LICENSE](https://github.com/b7n0de/proofbundle/blob/main/LICENSE).
252
+
253
+ ---
254
+
255
+ <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.0b3"
7
+ version = "3.0.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"
@@ -22,6 +22,9 @@ keywords = [
22
22
  "rfc6962",
23
23
  ]
24
24
  classifiers = [
25
+ # Stays Beta until the funded external security review passes (Owner decision E1, 2026-07-12):
26
+ # "stable is evidenced, not asserted". The move to "5 - Production/Stable" is a separate,
27
+ # audit-gated milestone (see docs/GRANT_MILESTONES.md), never claimed pre-audit.
25
28
  "Development Status :: 4 - Beta",
26
29
  "Intended Audience :: Developers",
27
30
  "License :: OSI Approved :: MIT License",
@@ -13,10 +13,16 @@ from __future__ import annotations
13
13
 
14
14
  from typing import TYPE_CHECKING
15
15
 
16
- __version__ = "2.0.0b3"
16
+ __version__ = "3.0.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-11"
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