proofbundle 0.3.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Konrad Gruszka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: proofbundle
3
+ Version: 0.3.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 :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Security :: Cryptography
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: cryptography>=42
26
+ Provides-Extra: sdjwt
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7; extra == "dev"
29
+ Requires-Dist: ruff>=0.5; extra == "dev"
30
+ Requires-Dist: jsonschema>=4; extra == "dev"
31
+ Requires-Dist: mypy>=1.8; extra == "dev"
32
+ Requires-Dist: build>=1; extra == "dev"
33
+ Requires-Dist: hypothesis>=6; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ <div align="center">
37
+
38
+ <picture>
39
+ <source media="(prefers-color-scheme: dark)" srcset="assets/b7n0de-logo-dark.svg">
40
+ <img alt="b7n0de, Verified AI Work" src="assets/b7n0de-logo.svg" height="60">
41
+ </picture>
42
+
43
+ <h1>proofbundle</h1>
44
+
45
+ **Emit and verify, fully offline, portable evidence that a piece of data was
46
+ signed and anchored in a tamper-evident log — and optionally carries a
47
+ selectively disclosable credential. Pure Python, no server, no daemon, one JSON file.**
48
+
49
+ [![CI](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml/badge.svg)](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
50
+ [![PyPI](https://img.shields.io/pypi/v/proofbundle.svg?color=D6248A)](https://pypi.org/project/proofbundle/)
51
+ [![Python](https://img.shields.io/pypi/pyversions/proofbundle.svg?color=D6248A)](https://pypi.org/project/proofbundle/)
52
+ [![License: MIT](https://img.shields.io/badge/license-MIT-D6248A.svg)](LICENSE)
53
+ [![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)
54
+ [![SLSA build provenance](https://img.shields.io/badge/SLSA-build_provenance-D6248A.svg)](https://slsa.dev)
55
+
56
+ </div>
57
+
58
+ **At a glance:** `proofbundle emit` signs and anchors a payload; `proofbundle
59
+ verify` checks one self-contained `bundle.json` with three offline cryptographic
60
+ checks → `OK` or `FAILED`. No network, no daemon, no own crypto. 25 tests.
61
+
62
+ ## Contents
63
+
64
+ - [Why](#why)
65
+ - [What it verifies](#what-it-verifies)
66
+ - [How it fits together](#how-it-fits-together)
67
+ - [Install](#install)
68
+ - [Quickstart](#quickstart)
69
+ - [Interoperability](#interoperability)
70
+ - [Bundle format](#bundle-format-proofbundlev01)
71
+ - [Security notes and scope](#security-notes-and-scope-stated-honestly)
72
+ - [Roadmap](#roadmap)
73
+ - [Contributing](#contributing)
74
+ - [License](#license)
75
+
76
+ ## Why
77
+
78
+ Cryptographic evidence today usually needs a running service to check it.
79
+ Sigstore Rekor, Certificate Transparency and other transparency logs are
80
+ excellent, but verifying an inclusion proof normally means talking to a log
81
+ server or wiring up Go tooling. There is no small, portable, Python-native
82
+ verifier that takes one self-contained file and answers a simple question
83
+ offline:
84
+
85
+ *Were these exact bytes signed by this key, and anchored under this Merkle root,
86
+ yes or no.*
87
+
88
+ `proofbundle` is that verifier — and, since v0.2, the matching emitter. It is
89
+ the verification half of a larger idea: turning a reproducible result (for
90
+ example an AI evaluation run) into a signed, third-party-verifiable, selectively
91
+ disclosable receipt. The verifier shipped first, small and correct, so it could
92
+ be reviewed and trusted on its own; `emit_bundle` now creates bundles that
93
+ `verify_bundle` accepts, fully offline on both sides.
94
+
95
+ ## What it verifies
96
+
97
+ A bundle is a single JSON document. `proofbundle` checks, offline:
98
+
99
+ 1. **ed25519-signature** — the payload was signed by the stated Ed25519 key
100
+ 2. **merkle-inclusion** — the payload is anchored under the stated tree root,
101
+ using an RFC 6962 / RFC 9162 inclusion proof (the same primitive as Rekor and
102
+ Certificate Transparency)
103
+ 3. **sd-jwt** (optional) — an embedded SD-JWT selective-disclosure credential is
104
+ well formed, and if an issuer key is given, correctly issuer-signed
105
+
106
+ The verifier treats the payload as opaque bytes. It proves that these exact
107
+ bytes were signed and anchored, not what they mean. That is on purpose: it keeps
108
+ the trusted core tiny.
109
+
110
+ ## How it fits together
111
+
112
+ ```mermaid
113
+ flowchart LR
114
+ P["payload bytes"]
115
+ P -->|"Ed25519 sign"| S["signature"]
116
+ P -->|"RFC 6962 anchor"| M["Merkle inclusion proof"]
117
+ SD["SD-JWT VC (optional)"] -.-> B
118
+ S --> B["bundle.json"]
119
+ M --> B
120
+ B --> V{{"proofbundle verify"}}
121
+ V --> C1["ed25519-signature"]
122
+ V --> C2["merkle-inclusion"]
123
+ V --> C3["sd-jwt (optional)"]
124
+ C1 --> R{"all checks pass?"}
125
+ C2 --> R
126
+ C3 --> R
127
+ R -->|yes| OK(["=> OK &nbsp; exit 0"])
128
+ R -->|no| FAIL(["=> FAILED &nbsp; exit 1"])
129
+
130
+ style V fill:#D6248A,stroke:#D6248A,color:#fff
131
+ style OK fill:#D6248A,stroke:#D6248A,color:#fff
132
+ style FAIL fill:#ef4444,stroke:#ef4444,color:#fff
133
+ ```
134
+
135
+ ## Install
136
+
137
+ ```bash
138
+ pip install proofbundle
139
+ ```
140
+
141
+ Requires Python 3.9+ and [`cryptography`](https://cryptography.io). Signature
142
+ math is delegated to `cryptography`; this project never rolls its own crypto.
143
+ The Merkle and SD-JWT logic is pure standard library.
144
+
145
+ SD-JWT support is an optional extra (it adds no runtime dependency beyond the
146
+ core `cryptography`, so the trusted core stays lean):
147
+
148
+ ```bash
149
+ pip install "proofbundle[sdjwt]"
150
+ ```
151
+
152
+ ## Quickstart
153
+
154
+ ```bash
155
+ # generate a real example bundle with throwaway keys
156
+ python examples/make_example.py
157
+
158
+ # verify it
159
+ proofbundle verify examples/example_bundle.json
160
+ ```
161
+
162
+ <div align="center">
163
+ <img src="assets/demo.svg" alt="proofbundle verify output: four PASS checks and OK" width="680">
164
+ </div>
165
+
166
+ Machine-readable output and a non-zero exit code on failure:
167
+
168
+ ```bash
169
+ proofbundle verify --json bundle.json # exit 0 = ok, 1 = failed, 2 = malformed
170
+ ```
171
+
172
+ Emit a bundle of your own (v0.2): sign a payload with a fresh key and anchor it,
173
+ then verify it anywhere, offline.
174
+
175
+ ```bash
176
+ proofbundle emit --payload-file result.json --new-key signer.key --out bundle.json
177
+ proofbundle verify bundle.json
178
+ ```
179
+
180
+ Library use:
181
+
182
+ ```python
183
+ from proofbundle import verify_bundle
184
+
185
+ result = verify_bundle("bundle.json")
186
+ print(result.ok) # True / False
187
+ for check in result.checks:
188
+ print(check.name, check.ok, check.detail)
189
+ ```
190
+
191
+ Verify a consistency proof between two log states directly:
192
+
193
+ ```python
194
+ from proofbundle import verify_consistency
195
+ verify_consistency(first_size, second_size, proof, first_root, second_root) # -> bool
196
+ ```
197
+
198
+ ## Interoperability
199
+
200
+ proofbundle uses the same RFC 6962 / RFC 9162 Merkle primitive as
201
+ [Sigstore Rekor](https://docs.sigstore.dev/) and Certificate Transparency, so its
202
+ `verify_inclusion` checks a real proof from a live transparency log, not just its
203
+ own bundles. [`examples/rekor_interop.py`](examples/rekor_interop.py) verifies a
204
+ real Sigstore Rekor inclusion proof (a committed fixture, `logIndex` 25579 in a
205
+ 4.16-million-entry tree) **fully offline**, and documents the field mapping from
206
+ the Rekor bundle and its C2SP `tlog-checkpoint` signed note to proofbundle's
207
+ `merkle` object. Correctness is also checked against external RFC 6962 test
208
+ vectors vendored from
209
+ [transparency-dev/merkle](https://github.com/transparency-dev/merkle) (see
210
+ `tests/fixtures/`), plus Hypothesis property tests.
211
+
212
+ ## Bundle format (`proofbundle/v0.1`)
213
+
214
+ The format is specified normatively in [SPEC.md](SPEC.md) (fields, encodings,
215
+ RFC 6962 hashing, verification order) with a machine-readable JSON Schema at
216
+ [`schemas/proofbundle_v0_1.schema.json`](schemas/proofbundle_v0_1.schema.json).
217
+
218
+ ```json
219
+ {
220
+ "schema": "proofbundle/v0.1",
221
+ "payload_b64": "<the exact bytes that were signed and anchored>",
222
+ "signature": { "alg": "ed25519", "public_key_b64": "...", "sig_b64": "..." },
223
+ "merkle": {
224
+ "hash_alg": "sha256-rfc6962",
225
+ "leaf_index": 1,
226
+ "tree_size": 4,
227
+ "inclusion_proof_b64": ["...", "..."],
228
+ "root_b64": "..."
229
+ },
230
+ "sd_jwt_vc": { "compact": "<sd-jwt>", "issuer_public_key_b64": "..." }
231
+ }
232
+ ```
233
+
234
+ `sd_jwt_vc` is optional. Base64 fields are standard base64; the SD-JWT compact
235
+ string uses base64url as per the spec.
236
+
237
+ ## Security notes and scope, stated honestly
238
+
239
+ This is v0.1. It does exactly what it says and no more:
240
+
241
+ - Ed25519 signatures only, for both the payload and the optional SD-JWT issuer
242
+ signature.
243
+ - SD-JWT: the SD-JWT core is now [RFC 9901](https://datatracker.ietf.org/doc/rfc9901/)
244
+ (Dec 2025); this verifies that every presented disclosure is committed in the
245
+ issuer-signed payload, and the issuer signature (EdDSA) if a key is supplied. It
246
+ does **not** verify a Key Binding JWT, an X.509 or trust-list chain, status
247
+ lists, or `vct` type metadata. **SD-JWT VC** (the credential-type profile) is
248
+ still an IETF draft ([draft-ietf-oauth-sd-jwt-vc](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/));
249
+ full VC conformance is on the roadmap.
250
+ - The verifier does not fetch anything. Trust anchors (the signer key, the
251
+ expected root) are inputs you supply out of band.
252
+ - No custom cryptography. Ed25519 comes from `cryptography`; Merkle hashing is
253
+ RFC 6962.
254
+
255
+ If you find a correctness or security issue, please open an issue or see
256
+ [SECURITY.md](SECURITY.md).
257
+
258
+ ## Roadmap
259
+
260
+ - **v0.1** — the offline verifier plus a real example bundle.
261
+ - **v0.2 (current release)** — the emitter: `emit_bundle` signs a payload with
262
+ Ed25519 and anchors it as the last leaf of an RFC 6962 Merkle tree, producing
263
+ a bundle that `verify_bundle` accepts. Available as `proofbundle emit`.
264
+ - **v0.3** — an eval-receipt emitter: wrap one evaluation framework run
265
+ ([Inspect AI](https://github.com/UKGovernmentBEIS/inspect_ai),
266
+ [lm-evaluation-harness](https://github.com/EleutherAI/lm-evaluation-harness))
267
+ into a signed receipt whose payload is a minimal canonical claim, for example
268
+ `{"suite": "...", "threshold": 0.8, "passed": true}`, optionally wrapped as an
269
+ SD-JWT VC so a holder can disclose *passed above threshold* without revealing
270
+ the model, weights or dataset, and carrying a cluster-bootstrap confidence
271
+ interval, a multiple-testing correction and a preregistration hash.
272
+
273
+ That last step is the point: today no widely used AI project turns a
274
+ reproducible evaluation result into a signed, third-party-verifiable,
275
+ selectively disclosable receipt. This repository is the trustworthy verification
276
+ core that makes it possible.
277
+
278
+ ## Contributing
279
+
280
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and the
281
+ [Code of Conduct](CODE_OF_CONDUCT.md). Good first issues are labeled
282
+ [`good-first-issue`](https://github.com/b7n0de/proofbundle/labels/good-first-issue).
283
+ The verifier core aims to stay small, dependency-light and correct.
284
+
285
+ ## License
286
+
287
+ MIT, see [LICENSE](LICENSE).
288
+
289
+ ---
290
+
291
+ <p align="center"><sub>proofbundle is part of <b>b7n0de</b>, Verified AI Work &middot; <a href="https://b7n0de.com">b7n0de.com</a></sub></p>
@@ -0,0 +1,256 @@
1
+ <div align="center">
2
+
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="assets/b7n0de-logo-dark.svg">
5
+ <img alt="b7n0de, Verified AI Work" src="assets/b7n0de-logo.svg" height="60">
6
+ </picture>
7
+
8
+ <h1>proofbundle</h1>
9
+
10
+ **Emit and verify, fully offline, portable evidence that a piece of data was
11
+ signed and anchored in a tamper-evident log — and optionally carries a
12
+ selectively disclosable credential. Pure Python, no server, no daemon, one JSON file.**
13
+
14
+ [![CI](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml/badge.svg)](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
15
+ [![PyPI](https://img.shields.io/pypi/v/proofbundle.svg?color=D6248A)](https://pypi.org/project/proofbundle/)
16
+ [![Python](https://img.shields.io/pypi/pyversions/proofbundle.svg?color=D6248A)](https://pypi.org/project/proofbundle/)
17
+ [![License: MIT](https://img.shields.io/badge/license-MIT-D6248A.svg)](LICENSE)
18
+ [![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)
19
+ [![SLSA build provenance](https://img.shields.io/badge/SLSA-build_provenance-D6248A.svg)](https://slsa.dev)
20
+
21
+ </div>
22
+
23
+ **At a glance:** `proofbundle emit` signs and anchors a payload; `proofbundle
24
+ verify` checks one self-contained `bundle.json` with three offline cryptographic
25
+ checks → `OK` or `FAILED`. No network, no daemon, no own crypto. 25 tests.
26
+
27
+ ## Contents
28
+
29
+ - [Why](#why)
30
+ - [What it verifies](#what-it-verifies)
31
+ - [How it fits together](#how-it-fits-together)
32
+ - [Install](#install)
33
+ - [Quickstart](#quickstart)
34
+ - [Interoperability](#interoperability)
35
+ - [Bundle format](#bundle-format-proofbundlev01)
36
+ - [Security notes and scope](#security-notes-and-scope-stated-honestly)
37
+ - [Roadmap](#roadmap)
38
+ - [Contributing](#contributing)
39
+ - [License](#license)
40
+
41
+ ## Why
42
+
43
+ Cryptographic evidence today usually needs a running service to check it.
44
+ Sigstore Rekor, Certificate Transparency and other transparency logs are
45
+ excellent, but verifying an inclusion proof normally means talking to a log
46
+ server or wiring up Go tooling. There is no small, portable, Python-native
47
+ verifier that takes one self-contained file and answers a simple question
48
+ offline:
49
+
50
+ *Were these exact bytes signed by this key, and anchored under this Merkle root,
51
+ yes or no.*
52
+
53
+ `proofbundle` is that verifier — and, since v0.2, the matching emitter. It is
54
+ the verification half of a larger idea: turning a reproducible result (for
55
+ example an AI evaluation run) into a signed, third-party-verifiable, selectively
56
+ disclosable receipt. The verifier shipped first, small and correct, so it could
57
+ be reviewed and trusted on its own; `emit_bundle` now creates bundles that
58
+ `verify_bundle` accepts, fully offline on both sides.
59
+
60
+ ## What it verifies
61
+
62
+ A bundle is a single JSON document. `proofbundle` checks, offline:
63
+
64
+ 1. **ed25519-signature** — the payload was signed by the stated Ed25519 key
65
+ 2. **merkle-inclusion** — the payload is anchored under the stated tree root,
66
+ using an RFC 6962 / RFC 9162 inclusion proof (the same primitive as Rekor and
67
+ Certificate Transparency)
68
+ 3. **sd-jwt** (optional) — an embedded SD-JWT selective-disclosure credential is
69
+ well formed, and if an issuer key is given, correctly issuer-signed
70
+
71
+ The verifier treats the payload as opaque bytes. It proves that these exact
72
+ bytes were signed and anchored, not what they mean. That is on purpose: it keeps
73
+ the trusted core tiny.
74
+
75
+ ## How it fits together
76
+
77
+ ```mermaid
78
+ flowchart LR
79
+ P["payload bytes"]
80
+ P -->|"Ed25519 sign"| S["signature"]
81
+ P -->|"RFC 6962 anchor"| M["Merkle inclusion proof"]
82
+ SD["SD-JWT VC (optional)"] -.-> B
83
+ S --> B["bundle.json"]
84
+ M --> B
85
+ B --> V{{"proofbundle verify"}}
86
+ V --> C1["ed25519-signature"]
87
+ V --> C2["merkle-inclusion"]
88
+ V --> C3["sd-jwt (optional)"]
89
+ C1 --> R{"all checks pass?"}
90
+ C2 --> R
91
+ C3 --> R
92
+ R -->|yes| OK(["=> OK &nbsp; exit 0"])
93
+ R -->|no| FAIL(["=> FAILED &nbsp; exit 1"])
94
+
95
+ style V fill:#D6248A,stroke:#D6248A,color:#fff
96
+ style OK fill:#D6248A,stroke:#D6248A,color:#fff
97
+ style FAIL fill:#ef4444,stroke:#ef4444,color:#fff
98
+ ```
99
+
100
+ ## Install
101
+
102
+ ```bash
103
+ pip install proofbundle
104
+ ```
105
+
106
+ Requires Python 3.9+ and [`cryptography`](https://cryptography.io). Signature
107
+ math is delegated to `cryptography`; this project never rolls its own crypto.
108
+ The Merkle and SD-JWT logic is pure standard library.
109
+
110
+ SD-JWT support is an optional extra (it adds no runtime dependency beyond the
111
+ core `cryptography`, so the trusted core stays lean):
112
+
113
+ ```bash
114
+ pip install "proofbundle[sdjwt]"
115
+ ```
116
+
117
+ ## Quickstart
118
+
119
+ ```bash
120
+ # generate a real example bundle with throwaway keys
121
+ python examples/make_example.py
122
+
123
+ # verify it
124
+ proofbundle verify examples/example_bundle.json
125
+ ```
126
+
127
+ <div align="center">
128
+ <img src="assets/demo.svg" alt="proofbundle verify output: four PASS checks and OK" width="680">
129
+ </div>
130
+
131
+ Machine-readable output and a non-zero exit code on failure:
132
+
133
+ ```bash
134
+ proofbundle verify --json bundle.json # exit 0 = ok, 1 = failed, 2 = malformed
135
+ ```
136
+
137
+ Emit a bundle of your own (v0.2): sign a payload with a fresh key and anchor it,
138
+ then verify it anywhere, offline.
139
+
140
+ ```bash
141
+ proofbundle emit --payload-file result.json --new-key signer.key --out bundle.json
142
+ proofbundle verify bundle.json
143
+ ```
144
+
145
+ Library use:
146
+
147
+ ```python
148
+ from proofbundle import verify_bundle
149
+
150
+ result = verify_bundle("bundle.json")
151
+ print(result.ok) # True / False
152
+ for check in result.checks:
153
+ print(check.name, check.ok, check.detail)
154
+ ```
155
+
156
+ Verify a consistency proof between two log states directly:
157
+
158
+ ```python
159
+ from proofbundle import verify_consistency
160
+ verify_consistency(first_size, second_size, proof, first_root, second_root) # -> bool
161
+ ```
162
+
163
+ ## Interoperability
164
+
165
+ proofbundle uses the same RFC 6962 / RFC 9162 Merkle primitive as
166
+ [Sigstore Rekor](https://docs.sigstore.dev/) and Certificate Transparency, so its
167
+ `verify_inclusion` checks a real proof from a live transparency log, not just its
168
+ own bundles. [`examples/rekor_interop.py`](examples/rekor_interop.py) verifies a
169
+ real Sigstore Rekor inclusion proof (a committed fixture, `logIndex` 25579 in a
170
+ 4.16-million-entry tree) **fully offline**, and documents the field mapping from
171
+ the Rekor bundle and its C2SP `tlog-checkpoint` signed note to proofbundle's
172
+ `merkle` object. Correctness is also checked against external RFC 6962 test
173
+ vectors vendored from
174
+ [transparency-dev/merkle](https://github.com/transparency-dev/merkle) (see
175
+ `tests/fixtures/`), plus Hypothesis property tests.
176
+
177
+ ## Bundle format (`proofbundle/v0.1`)
178
+
179
+ The format is specified normatively in [SPEC.md](SPEC.md) (fields, encodings,
180
+ RFC 6962 hashing, verification order) with a machine-readable JSON Schema at
181
+ [`schemas/proofbundle_v0_1.schema.json`](schemas/proofbundle_v0_1.schema.json).
182
+
183
+ ```json
184
+ {
185
+ "schema": "proofbundle/v0.1",
186
+ "payload_b64": "<the exact bytes that were signed and anchored>",
187
+ "signature": { "alg": "ed25519", "public_key_b64": "...", "sig_b64": "..." },
188
+ "merkle": {
189
+ "hash_alg": "sha256-rfc6962",
190
+ "leaf_index": 1,
191
+ "tree_size": 4,
192
+ "inclusion_proof_b64": ["...", "..."],
193
+ "root_b64": "..."
194
+ },
195
+ "sd_jwt_vc": { "compact": "<sd-jwt>", "issuer_public_key_b64": "..." }
196
+ }
197
+ ```
198
+
199
+ `sd_jwt_vc` is optional. Base64 fields are standard base64; the SD-JWT compact
200
+ string uses base64url as per the spec.
201
+
202
+ ## Security notes and scope, stated honestly
203
+
204
+ This is v0.1. It does exactly what it says and no more:
205
+
206
+ - Ed25519 signatures only, for both the payload and the optional SD-JWT issuer
207
+ signature.
208
+ - SD-JWT: the SD-JWT core is now [RFC 9901](https://datatracker.ietf.org/doc/rfc9901/)
209
+ (Dec 2025); this verifies that every presented disclosure is committed in the
210
+ issuer-signed payload, and the issuer signature (EdDSA) if a key is supplied. It
211
+ does **not** verify a Key Binding JWT, an X.509 or trust-list chain, status
212
+ lists, or `vct` type metadata. **SD-JWT VC** (the credential-type profile) is
213
+ still an IETF draft ([draft-ietf-oauth-sd-jwt-vc](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/));
214
+ full VC conformance is on the roadmap.
215
+ - The verifier does not fetch anything. Trust anchors (the signer key, the
216
+ expected root) are inputs you supply out of band.
217
+ - No custom cryptography. Ed25519 comes from `cryptography`; Merkle hashing is
218
+ RFC 6962.
219
+
220
+ If you find a correctness or security issue, please open an issue or see
221
+ [SECURITY.md](SECURITY.md).
222
+
223
+ ## Roadmap
224
+
225
+ - **v0.1** — the offline verifier plus a real example bundle.
226
+ - **v0.2 (current release)** — the emitter: `emit_bundle` signs a payload with
227
+ Ed25519 and anchors it as the last leaf of an RFC 6962 Merkle tree, producing
228
+ a bundle that `verify_bundle` accepts. Available as `proofbundle emit`.
229
+ - **v0.3** — an eval-receipt emitter: wrap one evaluation framework run
230
+ ([Inspect AI](https://github.com/UKGovernmentBEIS/inspect_ai),
231
+ [lm-evaluation-harness](https://github.com/EleutherAI/lm-evaluation-harness))
232
+ into a signed receipt whose payload is a minimal canonical claim, for example
233
+ `{"suite": "...", "threshold": 0.8, "passed": true}`, optionally wrapped as an
234
+ SD-JWT VC so a holder can disclose *passed above threshold* without revealing
235
+ the model, weights or dataset, and carrying a cluster-bootstrap confidence
236
+ interval, a multiple-testing correction and a preregistration hash.
237
+
238
+ That last step is the point: today no widely used AI project turns a
239
+ reproducible evaluation result into a signed, third-party-verifiable,
240
+ selectively disclosable receipt. This repository is the trustworthy verification
241
+ core that makes it possible.
242
+
243
+ ## Contributing
244
+
245
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and the
246
+ [Code of Conduct](CODE_OF_CONDUCT.md). Good first issues are labeled
247
+ [`good-first-issue`](https://github.com/b7n0de/proofbundle/labels/good-first-issue).
248
+ The verifier core aims to stay small, dependency-light and correct.
249
+
250
+ ## License
251
+
252
+ MIT, see [LICENSE](LICENSE).
253
+
254
+ ---
255
+
256
+ <p align="center"><sub>proofbundle is part of <b>b7n0de</b>, Verified AI Work &middot; <a href="https://b7n0de.com">b7n0de.com</a></sub></p>
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "proofbundle"
7
+ version = "0.3.0"
8
+ description = "Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Konrad Gruszka" }]
13
+ keywords = [
14
+ "cryptography",
15
+ "merkle",
16
+ "transparency-log",
17
+ "ed25519",
18
+ "sd-jwt",
19
+ "verifiable-credentials",
20
+ "attestation",
21
+ "provenance",
22
+ "rfc6962",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Security :: Cryptography",
34
+ ]
35
+ dependencies = ["cryptography>=42"]
36
+
37
+ [project.optional-dependencies]
38
+ # SD-JWT support is an optional capability. It adds NO runtime dependency beyond the core
39
+ # `cryptography` (EdDSA + stdlib), so `pip install proofbundle[sdjwt]` keeps the trusted core
40
+ # lean; the extra documents intent and is forward-compatible if SD-JWT ever needs a heavier lib.
41
+ sdjwt = []
42
+ dev = ["pytest>=7", "ruff>=0.5", "jsonschema>=4", "mypy>=1.8", "build>=1", "hypothesis>=6"]
43
+
44
+ [project.urls]
45
+ Homepage = "https://b7n0de.com"
46
+ Repository = "https://github.com/b7n0de/proofbundle"
47
+ Issues = "https://github.com/b7n0de/proofbundle/issues"
48
+ Changelog = "https://github.com/b7n0de/proofbundle/blob/main/CHANGELOG.md"
49
+ Documentation = "https://github.com/b7n0de/proofbundle#readme"
50
+
51
+ [project.scripts]
52
+ proofbundle = "proofbundle.cli:main"
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["src"]
56
+
57
+ [tool.setuptools.package-data]
58
+ proofbundle = ["py.typed"]
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+
63
+ [tool.mypy]
64
+ python_version = "3.10"
65
+ files = ["src"]
66
+ ignore_missing_imports = true
67
+ warn_unused_ignores = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ """proofbundle, an offline verifier for portable cryptographic evidence bundles.
2
+
3
+ Verify, fully offline and in pure Python, that a payload was Ed25519 signed and
4
+ anchored under an RFC 6962 Merkle root, with optional SD-JWT selective
5
+ disclosure. The verification half of a signed, third-party-verifiable evidence
6
+ receipt.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .bundle import SCHEMA, load_bundle, verify_bundle
12
+ from .emit import emit_bundle, generate_signer
13
+ from .errors import Check, ProofBundleError, VerificationResult
14
+ from .merkle import verify_consistency, verify_inclusion
15
+
16
+ __version__ = "0.3.0"
17
+
18
+ __all__ = [
19
+ "__version__",
20
+ "SCHEMA",
21
+ "verify_bundle",
22
+ "load_bundle",
23
+ "emit_bundle",
24
+ "generate_signer",
25
+ "verify_inclusion",
26
+ "verify_consistency",
27
+ "VerificationResult",
28
+ "Check",
29
+ "ProofBundleError",
30
+ ]