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.
- proofbundle-0.3.0/LICENSE +21 -0
- proofbundle-0.3.0/PKG-INFO +291 -0
- proofbundle-0.3.0/README.md +256 -0
- proofbundle-0.3.0/pyproject.toml +67 -0
- proofbundle-0.3.0/setup.cfg +4 -0
- proofbundle-0.3.0/src/proofbundle/__init__.py +30 -0
- proofbundle-0.3.0/src/proofbundle/bundle.py +109 -0
- proofbundle-0.3.0/src/proofbundle/cli.py +89 -0
- proofbundle-0.3.0/src/proofbundle/emit.py +138 -0
- proofbundle-0.3.0/src/proofbundle/errors.py +54 -0
- proofbundle-0.3.0/src/proofbundle/merkle.py +186 -0
- proofbundle-0.3.0/src/proofbundle/py.typed +0 -0
- proofbundle-0.3.0/src/proofbundle/sdjwt.py +134 -0
- proofbundle-0.3.0/src/proofbundle/signature.py +29 -0
- proofbundle-0.3.0/src/proofbundle.egg-info/PKG-INFO +291 -0
- proofbundle-0.3.0/src/proofbundle.egg-info/SOURCES.txt +27 -0
- proofbundle-0.3.0/src/proofbundle.egg-info/dependency_links.txt +1 -0
- proofbundle-0.3.0/src/proofbundle.egg-info/entry_points.txt +2 -0
- proofbundle-0.3.0/src/proofbundle.egg-info/requires.txt +11 -0
- proofbundle-0.3.0/src/proofbundle.egg-info/top_level.txt +1 -0
- proofbundle-0.3.0/tests/test_bundle.py +67 -0
- proofbundle-0.3.0/tests/test_cli.py +40 -0
- proofbundle-0.3.0/tests/test_emit.py +54 -0
- proofbundle-0.3.0/tests/test_merkle.py +60 -0
- proofbundle-0.3.0/tests/test_merkle_property.py +64 -0
- proofbundle-0.3.0/tests/test_rekor_interop.py +33 -0
- proofbundle-0.3.0/tests/test_rfc6962_external_vectors.py +73 -0
- proofbundle-0.3.0/tests/test_schema.py +41 -0
- proofbundle-0.3.0/tests/test_signature.py +36 -0
|
@@ -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
|
+
[](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
|
|
50
|
+
[](https://pypi.org/project/proofbundle/)
|
|
51
|
+
[](https://pypi.org/project/proofbundle/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
[](https://github.com/astral-sh/ruff)
|
|
54
|
+
[](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 exit 0"])
|
|
128
|
+
R -->|no| FAIL(["=> FAILED 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 · <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
|
+
[](https://github.com/b7n0de/proofbundle/actions/workflows/ci.yml)
|
|
15
|
+
[](https://pypi.org/project/proofbundle/)
|
|
16
|
+
[](https://pypi.org/project/proofbundle/)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
[](https://github.com/astral-sh/ruff)
|
|
19
|
+
[](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 exit 0"])
|
|
93
|
+
R -->|no| FAIL(["=> FAILED 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 · <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,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
|
+
]
|