strix-verify 0.2.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.
- strix_verify-0.2.0/.coverage.claude.pid24539.XY1Ytvgx.H88aIe1rrr5h +0 -0
- strix_verify-0.2.0/.gitignore +31 -0
- strix_verify-0.2.0/CHANGELOG.md +51 -0
- strix_verify-0.2.0/PARITY-BACKLOG.md +248 -0
- strix_verify-0.2.0/PKG-INFO +300 -0
- strix_verify-0.2.0/README.md +269 -0
- strix_verify-0.2.0/RELEASING.md +52 -0
- strix_verify-0.2.0/SCOPE.md +144 -0
- strix_verify-0.2.0/pyproject.toml +61 -0
- strix_verify-0.2.0/src/strix_verify/__init__.py +206 -0
- strix_verify-0.2.0/src/strix_verify/client.py +81 -0
- strix_verify-0.2.0/src/strix_verify/compliance.py +56 -0
- strix_verify-0.2.0/src/strix_verify/hashing.py +84 -0
- strix_verify-0.2.0/src/strix_verify/jwks.py +154 -0
- strix_verify-0.2.0/src/strix_verify/models.py +241 -0
- strix_verify-0.2.0/src/strix_verify/payload.py +74 -0
- strix_verify-0.2.0/src/strix_verify/signature.py +97 -0
- strix_verify-0.2.0/tests/conftest.py +111 -0
- strix_verify-0.2.0/tests/test_compliance.py +142 -0
- strix_verify-0.2.0/tests/test_conformance_vectors.py +105 -0
- strix_verify-0.2.0/tests/test_hashing.py +139 -0
- strix_verify-0.2.0/tests/test_jwks.py +160 -0
- strix_verify-0.2.0/tests/test_payload.py +182 -0
- strix_verify-0.2.0/tests/test_signature.py +148 -0
- strix_verify-0.2.0/tests/test_verify.py +303 -0
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.eggs/
|
|
7
|
+
*.egg
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
|
|
11
|
+
# Environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
.env
|
|
15
|
+
.env.local
|
|
16
|
+
|
|
17
|
+
# uv
|
|
18
|
+
.uv/
|
|
19
|
+
uv.lock.broken
|
|
20
|
+
|
|
21
|
+
# Tests
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
pytest-cache-files-*/
|
|
24
|
+
.coverage
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# Editor / OS
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
*.swp
|
|
31
|
+
.DS_Store
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `strix-verify` are documented here. This project
|
|
4
|
+
follows semantic versioning (pre-1.0: minor bumps may add functionality or
|
|
5
|
+
fix correctness issues that change verification outcomes).
|
|
6
|
+
|
|
7
|
+
## [0.2.0] — 2026-05-31
|
|
8
|
+
|
|
9
|
+
### Fixed — Academy-form record verification (correctness)
|
|
10
|
+
|
|
11
|
+
`build_canonical_payload` previously reconstructed **only** the Console
|
|
12
|
+
canonical form (string `schemaVersion`/`evidenceId`, `complianceMode`-first
|
|
13
|
+
`regulatoryContext`). It therefore could **not** verify records signed in the
|
|
14
|
+
Academy form — approximately 4,450 historical Academy records — and would have
|
|
15
|
+
reported them as signature failures.
|
|
16
|
+
|
|
17
|
+
This release adds the SE v1 **dual-form discriminator**, matching the JS
|
|
18
|
+
reference verifier (`@strixgov/verifier`):
|
|
19
|
+
|
|
20
|
+
- Records with `sourceApp == "academy-platform"` now serialize `schemaVersion`
|
|
21
|
+
and `evidenceId` as JSON **numbers**, with a `euAiActArticle12`-first
|
|
22
|
+
`regulatoryContext`.
|
|
23
|
+
- Console records are unchanged (JSON strings, `complianceMode`-first).
|
|
24
|
+
- Added the `signedPayload` reconstruction-free passthrough (v1.10.0): when a
|
|
25
|
+
record carries the original signed bytes, they are returned verbatim.
|
|
26
|
+
- Canonical output now uses `ensure_ascii=False` for byte-parity with the JS
|
|
27
|
+
reference on non-ASCII fields.
|
|
28
|
+
|
|
29
|
+
The Console path is byte-for-byte unchanged (the prior golden-vector test
|
|
30
|
+
remains the canary and still passes).
|
|
31
|
+
|
|
32
|
+
### Added — cross-language conformance
|
|
33
|
+
|
|
34
|
+
This verifier is now pinned to the JS reference against a shared,
|
|
35
|
+
language-neutral fixture
|
|
36
|
+
(`packages/strixgov-verifier/test/fixtures/se-v1-conformance-vectors.json`).
|
|
37
|
+
Both implementations read the same fixture and must agree byte-for-byte; a CI
|
|
38
|
+
lane (`SE v1 Cross-Language Conformance`) enforces this on Python 3.10–3.12 and
|
|
39
|
+
Node 20–24. See `tests/test_conformance_vectors.py`.
|
|
40
|
+
|
|
41
|
+
### Notes
|
|
42
|
+
|
|
43
|
+
- No new dependencies. No API changes to `verify_evidence` / `verify_evidence_record`.
|
|
44
|
+
- The golden vectors were not modified; the implementation was corrected to
|
|
45
|
+
match them.
|
|
46
|
+
|
|
47
|
+
## [0.1.0]
|
|
48
|
+
|
|
49
|
+
Initial release. Independent SE v1 verification (Ed25519 + SHA-256), JWKS
|
|
50
|
+
resolution, two-layer verification model, EU AI Act compliance-flag derivation.
|
|
51
|
+
Console canonical form only.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Python verifier — feature-parity backlog
|
|
2
|
+
|
|
3
|
+
Source of truth: `packages/strixgov-verifier/src/index.mjs` (JS verifier).
|
|
4
|
+
This doc tracks where `strix-verify` (Python) lags the JS verifier and
|
|
5
|
+
which gaps are worth closing.
|
|
6
|
+
|
|
7
|
+
> **Read first:** the trust math (Ed25519 + SHA-256 + canonical-JSON)
|
|
8
|
+
> in Python is byte-identical to JS for the surfaces Python implements.
|
|
9
|
+
> The gap is feature breadth, not correctness. Mirror-parity tests on
|
|
10
|
+
> the JS side lock the canonical payload builders; any port has to
|
|
11
|
+
> preserve those bytes exactly.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
`strix-verify` (Python) is the **auditor-facing subset** of the JS
|
|
18
|
+
verifier — not full parity. Canonical statement of the decision,
|
|
19
|
+
deciding principle, and reversal triggers lives in
|
|
20
|
+
[`SCOPE.md`](./SCOPE.md). Read that first if the question is "should
|
|
21
|
+
this be ported at all?"; read this doc for "what's the state of each
|
|
22
|
+
capability?"
|
|
23
|
+
|
|
24
|
+
The one-line summary: in scope = anything an external AI Act auditor
|
|
25
|
+
would independently verify against a public proof URL. Out of scope =
|
|
26
|
+
runtime-integration primitives (tool gateway, wire envelope,
|
|
27
|
+
snapshots).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Current parity (2026-05-18)
|
|
32
|
+
|
|
33
|
+
| Capability | JS export | Python | Status |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| Signed Evidence v1 verify | `verify` (`index.mjs:411`) | `verify_evidence` | Complete |
|
|
36
|
+
| SE v1 canonical payload | `buildCanonicalPayload` (`:275`) | `build_canonical_payload` | Complete |
|
|
37
|
+
| Ed25519 signature | `verifySignature` (`:383`) | `verify_signature` | Complete |
|
|
38
|
+
| SHA-256 hash | `verifyHash` (`:395`) | `verify_hash` | Complete |
|
|
39
|
+
| Evidence fetch | `fetchEvidence` (`:200`) | `fetch_evidence` | Complete (defaults aligned) |
|
|
40
|
+
| JWKS resolve | `fetchPublicKey` (`:158`) | `resolve_key` | Complete |
|
|
41
|
+
| EU AI Act compliance flags | inline | `derive_compliance_flags` | Complete |
|
|
42
|
+
| Redacted-kid resolution | `resolveJwkFromKid` (`:70`) | not implemented | **Gap — P1** |
|
|
43
|
+
| Approval artifact verify | `verifyApprovalArtifact` (`:597`) | not implemented | **Gap — P1** |
|
|
44
|
+
| Approval quorum verify | `verifyApprovalQuorum` (`:688`) | not implemented | **Gap — P2** |
|
|
45
|
+
| Actor Attestation v1 | `verifyAttestation` (`:840`) | not implemented | **Gap — P1 (post AA-1 flip)** |
|
|
46
|
+
| AA-1 composite status | `computeAttestationCompositeStatus` (`:977`) | not implemented | **Gap — P1 (post AA-1 flip)** |
|
|
47
|
+
| AA-1 + evidence combined | `verifyWithAttestations` (`:1036`) | not implemented | **Gap — P1 (post AA-1 flip)** |
|
|
48
|
+
| Tool-gateway receipt verify | `verifyReceipt` (`:1193`) | not implemented | **Gap — P2** |
|
|
49
|
+
| Receipt-chain verify | `verifyReceiptChain` (`:1264`) | not implemented | **Gap — P2** |
|
|
50
|
+
| Snapshot verify | `verifySnapshot` (`:1353`) | not implemented | **Gap — P3** |
|
|
51
|
+
| Tool-gateway proof aggregator | `verifyToolGatewayProof` (`:1434`) | not implemented | **Gap — P3** |
|
|
52
|
+
| Visual SVG verify | `verifyVisual` (`:1623`) | not implemented | **Gap — P3** |
|
|
53
|
+
| Connected-mode wire envelope | `verifyConnectedWireEnvelope` (`:1510`) | not implemented | **Non-goal** |
|
|
54
|
+
| CLI binary | `bin/strix-verify` | not implemented | **Gap — P1** |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Backlog
|
|
59
|
+
|
|
60
|
+
### P1 — auditor-facing, ship before next launch milestone
|
|
61
|
+
|
|
62
|
+
#### CLI binary
|
|
63
|
+
|
|
64
|
+
Mirror `npx @strixgov/verifier@latest 5686` as
|
|
65
|
+
`strix-verify 5686`. Single entry point in `pyproject.toml`
|
|
66
|
+
(`[project.scripts] strix-verify = "strix_verify.cli:main"`).
|
|
67
|
+
Behavior: same exit codes as the JS CLI, same human-readable
|
|
68
|
+
output, plus `--json` for machine consumption. No new
|
|
69
|
+
deployment-specific defaults — inherits `DEFAULT_PROOF_BASE`.
|
|
70
|
+
|
|
71
|
+
Reference: JS CLI lives at `packages/strixgov-verifier/bin/strix-verify.mjs`.
|
|
72
|
+
|
|
73
|
+
#### Redacted-kid resolution
|
|
74
|
+
|
|
75
|
+
JS `resolveJwkFromKid` (`index.mjs:70`) handles the redacted kid
|
|
76
|
+
format `strix-***-2026-04` by suffix-matching the `YYYY-MM`
|
|
77
|
+
segment against the full JWKS. Python's `resolve_key` today
|
|
78
|
+
does exact-string match only.
|
|
79
|
+
|
|
80
|
+
Without this, Python cannot verify any record where
|
|
81
|
+
`signingKeyId` came through `redactSigningKeyId()` — which is
|
|
82
|
+
the entire public-approval-artifact surface (Gap 5 mitigation).
|
|
83
|
+
SE v1 evidence records expose the unredacted kid inside the
|
|
84
|
+
signed `fields.*` block, so basic evidence verification still
|
|
85
|
+
works; this gap shows up the moment Python tries an approval
|
|
86
|
+
artifact.
|
|
87
|
+
|
|
88
|
+
Mirror test: JS pins this with `redaction-promotion.test.mjs`.
|
|
89
|
+
Port: add suffix-match fallback to `resolve_key`, then port the
|
|
90
|
+
same locked redacted-kid fixture into `tests/`.
|
|
91
|
+
|
|
92
|
+
#### Approval artifact verifier
|
|
93
|
+
|
|
94
|
+
Phase 3 ships `SalesApprovalArtifact` with a locked 9-field
|
|
95
|
+
canonical payload. Public REST surface exists today
|
|
96
|
+
(`GET /api/public/approval-artifact/:id`). Auditors will want
|
|
97
|
+
to verify approval authority independently — same trust story
|
|
98
|
+
as evidence records, different schema.
|
|
99
|
+
|
|
100
|
+
Implementation cost: low. Port `buildApprovalCanonicalPayload`
|
|
101
|
+
(`index.mjs:511`) byte-for-byte (field order is contract — see
|
|
102
|
+
CLAUDE.md "Files that require extra care"), reuse the existing
|
|
103
|
+
SE v1 signature/hash machinery.
|
|
104
|
+
|
|
105
|
+
Mirror test: golden vectors from
|
|
106
|
+
`apps/strix-console/tests/sales-pipeline/approval-artifact-parity.test.ts`.
|
|
107
|
+
|
|
108
|
+
#### Actor Attestation v1 verifier
|
|
109
|
+
|
|
110
|
+
Currently dormant in production
|
|
111
|
+
(`STRIX_ACTOR_ATTESTATION_V1=false`). When the flag flips, the
|
|
112
|
+
public `/api/public/verify` response starts carrying an
|
|
113
|
+
`actorClass` block; Python users will see records they cannot
|
|
114
|
+
fully verify.
|
|
115
|
+
|
|
116
|
+
Implementation cost: medium. Port:
|
|
117
|
+
|
|
118
|
+
- `buildActorAttestationPayload` (`index.mjs:805`) — sibling
|
|
119
|
+
artifact joined to SE v1 by `evidenceId`
|
|
120
|
+
- `verifyAttestation` (`:840`) — 8 rules, 11 reason codes
|
|
121
|
+
- `computeAttestationCompositeStatus` (`:977`) — composes
|
|
122
|
+
evidence verification result with attestation outcome
|
|
123
|
+
- `verifyWithAttestations` (`:1036`) — convenience wrapper
|
|
124
|
+
|
|
125
|
+
Contract: `docs/architecture/actor-attestation-v1.md`
|
|
126
|
+
contractVersion 1.0.0 (frozen). Reason codes are public API —
|
|
127
|
+
do not rename when porting.
|
|
128
|
+
|
|
129
|
+
Trigger: AA-1 flag flip. Track against
|
|
130
|
+
`scripts/aa1-postflip-validate.mjs` running GREEN in production.
|
|
131
|
+
|
|
132
|
+
### P2 — system-integrator-facing, port if Python demand surfaces
|
|
133
|
+
|
|
134
|
+
#### Approval quorum verifier
|
|
135
|
+
|
|
136
|
+
`verifyApprovalQuorum` (`index.mjs:688`) verifies the full set
|
|
137
|
+
of approval artifacts for a decision plus quorum rules
|
|
138
|
+
(N-of-M). Auditor demand is real but narrower than single
|
|
139
|
+
approval verification — most external review walks one
|
|
140
|
+
artifact at a time. Port after single-artifact lands.
|
|
141
|
+
|
|
142
|
+
#### Tool-gateway receipt verifier (v1 + v2)
|
|
143
|
+
|
|
144
|
+
`buildReceiptCanonicalPayload` (`index.mjs:1157`) supports
|
|
145
|
+
schemaVersion v1 (11 fields) and v2 (14 fields with
|
|
146
|
+
`policyVersion` / `tenantId` / `environment`).
|
|
147
|
+
`verifyReceipt` (`:1193`) consumes both, dispatching on
|
|
148
|
+
`schemaVersion`.
|
|
149
|
+
|
|
150
|
+
Auditor demand: low today. Tool-gateway receipts live
|
|
151
|
+
client-side in `@strixgov/tool-gateway`; the verification path
|
|
152
|
+
that matters most is the hosted approval/evidence side. Defer
|
|
153
|
+
unless a Python-native tool-gateway consumer materializes.
|
|
154
|
+
|
|
155
|
+
Mirror test if porting:
|
|
156
|
+
`packages/tool-gateway/tests/verifier-parity.test.mjs` — both
|
|
157
|
+
v1 and v2 receipt canonical bytes must match exactly.
|
|
158
|
+
|
|
159
|
+
#### Receipt-chain verifier
|
|
160
|
+
|
|
161
|
+
`verifyReceiptChain` (`index.mjs:1264`). Walks a chain of
|
|
162
|
+
tool-gateway receipts and verifies the sequencing invariant.
|
|
163
|
+
Same demand profile as single-receipt verify. Defer.
|
|
164
|
+
|
|
165
|
+
### P3 — unlikely to be needed in Python
|
|
166
|
+
|
|
167
|
+
#### Snapshot verifier
|
|
168
|
+
|
|
169
|
+
`verifySnapshot` (`index.mjs:1353`) +
|
|
170
|
+
`buildSnapshotCanonicalPayload` (`:1322`) — tool-gateway chain
|
|
171
|
+
snapshots (v0.2 rotation). Internal to tool-gateway operation.
|
|
172
|
+
External auditors do not consume these directly.
|
|
173
|
+
|
|
174
|
+
#### Tool-gateway proof aggregator
|
|
175
|
+
|
|
176
|
+
`verifyToolGatewayProof` (`index.mjs:1434`). Composite verifier
|
|
177
|
+
across receipts + snapshots. Same reasoning as snapshot.
|
|
178
|
+
|
|
179
|
+
#### Visual SVG verifier
|
|
180
|
+
|
|
181
|
+
`extractVisualMetadata` (`index.mjs:1581`) + `verifyVisual`
|
|
182
|
+
(`:1623`) parse the `<strix:*>` metadata block embedded in
|
|
183
|
+
signed visual artifacts. The artifact format is web-facing (a
|
|
184
|
+
shareable signed badge). A Python port would only matter if
|
|
185
|
+
someone wanted to verify the badge from a backend job. CI gate
|
|
186
|
+
`scripts/lint-visual-artifacts-signed.mjs` already locks the
|
|
187
|
+
producer side; consumer-side parity in Python is theoretical.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Non-goals
|
|
192
|
+
|
|
193
|
+
These will not be ported even if asked.
|
|
194
|
+
|
|
195
|
+
| Capability | Why not |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `verifyConnectedWireEnvelope` (`index.mjs:1510`) | The wire envelope is a runtime integration concern (replay defense between tool-gateway and hosted kernel). Auditors verify the resulting evidence record, not the wire that carried it. |
|
|
198
|
+
| `SUPPORTED_WIRE_VERSIONS` (`:1483`) | Same reasoning. |
|
|
199
|
+
| Anything in `packages/tool-gateway/` | Not part of the verifier surface. Tool-gateway has its own JS-only release cadence and the parity tests live there. |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Mirror discipline (if you port anything)
|
|
204
|
+
|
|
205
|
+
Any canonical payload builder you port from JS MUST stay
|
|
206
|
+
byte-identical to the JS source. The audit story collapses if
|
|
207
|
+
JS and Python disagree on a single byte for the same record.
|
|
208
|
+
|
|
209
|
+
Rules:
|
|
210
|
+
|
|
211
|
+
1. **Copy the field order exactly.** Re-ordering a canonical
|
|
212
|
+
payload field invalidates every previously-signed artifact.
|
|
213
|
+
The locked field orders are documented in CLAUDE.md under
|
|
214
|
+
"Files that require extra care."
|
|
215
|
+
2. **Port the locked goldens.** Every JS builder has a frozen
|
|
216
|
+
test vector (`tests/golden-vectors/` in solo-builder-core,
|
|
217
|
+
or inline in the parity test files). Reuse those bytes as
|
|
218
|
+
Python test fixtures — do not regenerate.
|
|
219
|
+
3. **Public API name-stability.** `BindingReason` codes,
|
|
220
|
+
AA-1 reason codes, approval status enums — these are
|
|
221
|
+
public contract. Port them with the same names and the
|
|
222
|
+
same values.
|
|
223
|
+
4. **One canonicalizer.** If you find yourself
|
|
224
|
+
re-implementing JSON serialization, stop. Use a single
|
|
225
|
+
canonical-JSON helper (`payload.py` today) and route every
|
|
226
|
+
new artifact type through it.
|
|
227
|
+
5. **No deployment-specific defaults.** The package must not
|
|
228
|
+
carry any single customer's URL. Hosts default to the
|
|
229
|
+
canonical Strix Platform host only (`www.strixgov.com`),
|
|
230
|
+
which mirrors the JS verifier exactly.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## References
|
|
235
|
+
|
|
236
|
+
- JS verifier: `packages/strixgov-verifier/src/index.mjs`
|
|
237
|
+
- JS launch incident analysis:
|
|
238
|
+
`docs/postmortems/2026-05-15-launch-day-9-bug-stack.md`
|
|
239
|
+
- Load-bearing rules for signed-evidence paths:
|
|
240
|
+
`docs/CONSTRAINTS-FOR-AGENTS.md`
|
|
241
|
+
- Signed Evidence v1 spec:
|
|
242
|
+
`docs/gates/SIGNED-EVIDENCE-V1-AGENT-PROMPT.md`
|
|
243
|
+
- Actor Attestation v1 contract:
|
|
244
|
+
`docs/architecture/actor-attestation-v1.md`
|
|
245
|
+
- Approval artifact canonical schema:
|
|
246
|
+
`apps/strix-console/src/lib/sales-pipeline/approval-artifact.ts`
|
|
247
|
+
- Tool-gateway parity test (if ever porting receipts):
|
|
248
|
+
`packages/tool-gateway/tests/verifier-parity.test.mjs`
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strix-verify
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Independent verification of Strix governance evidence records. Ed25519 + SHA-256 only. No Strix tooling or account required. Public source at github.com/strixgov/strix.
|
|
5
|
+
Project-URL: Homepage, https://strixgov.com
|
|
6
|
+
Project-URL: Repository, https://github.com/strixgov/strix
|
|
7
|
+
Project-URL: Documentation, https://strixgov.com/docs/verify
|
|
8
|
+
Author: Strix Platform Team
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ai-safety,ed25519,evidence,governance,proof,strix,verification
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security :: Cryptography
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: cryptography>=42.0.0
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# strix-verify
|
|
33
|
+
|
|
34
|
+
Independent verification of Strix governance evidence records.
|
|
35
|
+
|
|
36
|
+
No Strix tooling, SDK, or account required. Uses only standard cryptographic
|
|
37
|
+
primitives (Ed25519, SHA-256) via the Python `cryptography` library.
|
|
38
|
+
|
|
39
|
+
**strix-verify is offline-safe.** Save the JWKS once, ship it with the
|
|
40
|
+
evidence, verify on an air-gapped machine. No callbacks. No telemetry.
|
|
41
|
+
No Strix server required at runtime.
|
|
42
|
+
|
|
43
|
+
**Proof Readiness Level 4.5** — Cryptographically Signed + Externally Verifiable
|
|
44
|
+
|
|
45
|
+
**Public source:** [github.com/strixgov/strix](https://github.com/strixgov/strix)
|
|
46
|
+
**PyPI:** [pypi.org/project/strix-verify](https://pypi.org/project/strix-verify)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install strix-verify
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or from source:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd python/strix-verify
|
|
60
|
+
pip install -e .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from strix_verify import verify_evidence
|
|
69
|
+
|
|
70
|
+
result = verify_evidence(
|
|
71
|
+
evidence_id=123,
|
|
72
|
+
proof_base="https://strix.example.com", # your Strix deployment
|
|
73
|
+
jwks_base="https://strixgov.com", # canonical JWKS surface
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print(result.signature_valid) # SignatureStatus.VERIFIED
|
|
77
|
+
print(result.hash_valid) # True
|
|
78
|
+
print(result.compliance.article12_tamper_resistant) # True
|
|
79
|
+
print(result.compliance.article14_human_oversight) # True
|
|
80
|
+
print(result.compliance.article28_provider_obligations) # True
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`proof_base` is required — there is no default. The verifier is neutral
|
|
84
|
+
across Strix deployments and never carries a hardcoded host URL.
|
|
85
|
+
|
|
86
|
+
### Async
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import asyncio
|
|
90
|
+
from strix_verify import fetch_evidence_async, verify_evidence_record
|
|
91
|
+
|
|
92
|
+
async def main():
|
|
93
|
+
record = await fetch_evidence_async(123, proof_base="https://strix.example.com")
|
|
94
|
+
result = verify_evidence_record(record, jwks_base="https://strixgov.com")
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
asyncio.run(main())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Verification Flow
|
|
103
|
+
|
|
104
|
+
1. **Fetch** — Retrieve evidence record from the Strix proof API
|
|
105
|
+
2. **Reconstruct** — Build canonical 13-field payload (locked field order)
|
|
106
|
+
3. **Fetch key** — Resolve Ed25519 public key from JWKS endpoint
|
|
107
|
+
4. **Verify signature** — Ed25519 signature over canonical payload
|
|
108
|
+
5. **Verify hash** — SHA-256 hash of canonical payload
|
|
109
|
+
6. **Derive compliance flags** — EU AI Act flags derived from outcomes (never asserted)
|
|
110
|
+
7. **Return** — `VerificationResult` with all outcomes
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Two-Layer Verification Model
|
|
115
|
+
|
|
116
|
+
### Layer 1 — Cryptographic Validity (`signature_valid`)
|
|
117
|
+
|
|
118
|
+
> "Was this record produced by the holder of the Strix signing key?"
|
|
119
|
+
|
|
120
|
+
| Status | Meaning |
|
|
121
|
+
|--------|---------|
|
|
122
|
+
| `VERIFIED` | Valid Ed25519 signature from a known key |
|
|
123
|
+
| `LEGACY_UNSIGNED` | Pre-signing record (migration before 0039) |
|
|
124
|
+
| `UNVERIFIABLE_KEY` | Signing key ID not found in JWKS or extra_keys |
|
|
125
|
+
| `COMPLIANCE_VIOLATION` | Signature present but invalid |
|
|
126
|
+
| `ERROR` | Network or unexpected failure |
|
|
127
|
+
|
|
128
|
+
### Layer 2 — Deployment Context (`environment_match`, `tenant_match`)
|
|
129
|
+
|
|
130
|
+
> "Is this record appropriate for this deployment context?"
|
|
131
|
+
|
|
132
|
+
Optional checks against stored record fields. Per **SE-14**, verification reads
|
|
133
|
+
`environment` and `tenantId` from the stored evidence record — never from
|
|
134
|
+
environment variables. This prevents false failures when a production record is
|
|
135
|
+
verified in a development context.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
result = verify_evidence(
|
|
139
|
+
evidence_id=123,
|
|
140
|
+
expected_environment="production", # checks stored record field
|
|
141
|
+
expected_tenant_id="tenant-abc", # checks stored record field
|
|
142
|
+
)
|
|
143
|
+
print(result.environment_match) # True/False/None
|
|
144
|
+
print(result.tenant_match) # True/False/None
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Verifying a Local Record
|
|
150
|
+
|
|
151
|
+
If you already have the evidence record dict (from a database, file, etc.):
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from strix_verify import verify_evidence_record
|
|
155
|
+
|
|
156
|
+
result = verify_evidence_record(
|
|
157
|
+
record,
|
|
158
|
+
jwks_base="https://strixgov.com",
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Verifying Offline (Air-Gapped)
|
|
165
|
+
|
|
166
|
+
For audit environments that cannot reach the internet, pre-fetch the JWKS
|
|
167
|
+
once, save it next to the evidence, and verify with no network access at
|
|
168
|
+
runtime:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import json
|
|
172
|
+
from strix_verify import verify_evidence_record
|
|
173
|
+
|
|
174
|
+
with open("evidence-2026-04-19.json") as f:
|
|
175
|
+
record = json.load(f)
|
|
176
|
+
|
|
177
|
+
with open("jwks-snapshot-2026-04.json") as f:
|
|
178
|
+
extra_keys = json.load(f)["keys"]
|
|
179
|
+
|
|
180
|
+
result = verify_evidence_record(
|
|
181
|
+
record,
|
|
182
|
+
extra_keys=extra_keys,
|
|
183
|
+
jwks_base="https://unused.invalid", # never reached when key is in extra_keys
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
When the signing key is already present in `extra_keys`, `jwks_base` is
|
|
188
|
+
never contacted. The verifier resolves the key from the pre-fetched JWK
|
|
189
|
+
list and does the full cryptographic check locally.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Key Rotation Support
|
|
194
|
+
|
|
195
|
+
Historical signing keys can be provided to verify older records after key rotation:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
import json, os
|
|
199
|
+
|
|
200
|
+
extra_keys = json.loads(os.environ.get("STRIX_SIGNING_JWKS_EXTRA", "[]"))
|
|
201
|
+
|
|
202
|
+
result = verify_evidence(
|
|
203
|
+
evidence_id=123,
|
|
204
|
+
extra_keys=extra_keys,
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Key ID format: `strix-{env}-{YYYY-MM}` (e.g., `strix-prod-2026-04`). EU AI Act
|
|
209
|
+
compliance requires a minimum 2-year key retention period.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## EU AI Act Compliance Flags
|
|
214
|
+
|
|
215
|
+
Compliance flags are **derived** from verification outcomes — never read from stored
|
|
216
|
+
fields (invariant CI-5). Altering the `regulatoryContext` block in a signed record
|
|
217
|
+
invalidates the Ed25519 signature.
|
|
218
|
+
|
|
219
|
+
| Flag | Derived From |
|
|
220
|
+
|------|-------------|
|
|
221
|
+
| `article12_tamper_resistant` | `hash_valid AND chain_valid AND signature_valid == VERIFIED` |
|
|
222
|
+
| `article14_human_oversight` | `signature_present` (actor fields cryptographically bound) |
|
|
223
|
+
| `article28_provider_obligations` | `signature_valid == VERIFIED` (evidence from known key) |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Canonical Payload Schema
|
|
228
|
+
|
|
229
|
+
The 13-field locked-order payload that is signed and hashed:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
schemaVersion (always "1")
|
|
233
|
+
evidenceId
|
|
234
|
+
evidenceHash
|
|
235
|
+
proofChainHash
|
|
236
|
+
capabilityId
|
|
237
|
+
action
|
|
238
|
+
actorId
|
|
239
|
+
actorRole
|
|
240
|
+
createdAt
|
|
241
|
+
signingKeyId
|
|
242
|
+
environment
|
|
243
|
+
tenantId
|
|
244
|
+
regulatoryContext { complianceMode, euAiActArticle12, euAiActArticle14, euAiActArticle28 }
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Warning:** Reordering these fields invalidates all existing signatures. This schema
|
|
248
|
+
is locked and versioned. See `tests/test_payload.py::test_golden_vector` for the
|
|
249
|
+
canonical serialization canary.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## JWKS Endpoint
|
|
254
|
+
|
|
255
|
+
Public keys are served at:
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
GET https://strixgov.com/.well-known/strix-jwks.json
|
|
259
|
+
GET https://strixgov.com/.well-known/strix-jwks.json?kid=strix-prod-2026-04
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The endpoint follows RFC 7517. Each key is an OKP/Ed25519 JWK with a `kid` in
|
|
263
|
+
`strix-{env}-{YYYY-MM}` format.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Development
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# Install with dev dependencies
|
|
271
|
+
pip install -e ".[dev]"
|
|
272
|
+
|
|
273
|
+
# Run tests
|
|
274
|
+
pytest
|
|
275
|
+
|
|
276
|
+
# Run tests with coverage
|
|
277
|
+
pytest --cov=strix_verify --cov-report=term-missing
|
|
278
|
+
|
|
279
|
+
# Lint
|
|
280
|
+
ruff check src/ tests/
|
|
281
|
+
|
|
282
|
+
# Type check
|
|
283
|
+
mypy src/
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Architecture Notes
|
|
289
|
+
|
|
290
|
+
- **No Strix SDK dependency** — pure Python + `cryptography` + `httpx`
|
|
291
|
+
- **Ed25519 SPKI DER construction** matches the TypeScript implementation exactly:
|
|
292
|
+
12-byte header `302a300506032b6570032100` + 32 raw key bytes
|
|
293
|
+
- **Never raises** on bad signatures — `verify_signature()` returns `False`
|
|
294
|
+
- **Sync and async** HTTP clients available for all network operations
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT — see LICENSE.
|