Metadata-Version: 2.4
Name: strix-verify
Version: 0.2.0
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.
Project-URL: Homepage, https://strixgov.com
Project-URL: Repository, https://github.com/strixgov/strix
Project-URL: Documentation, https://strixgov.com/docs/verify
Author: Strix Platform Team
License: MIT
Keywords: ai-safety,ed25519,evidence,governance,proof,strix,verification
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: cryptography>=42.0.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

# strix-verify

Independent verification of Strix governance evidence records.

No Strix tooling, SDK, or account required. Uses only standard cryptographic
primitives (Ed25519, SHA-256) via the Python `cryptography` library.

**strix-verify is offline-safe.** Save the JWKS once, ship it with the
evidence, verify on an air-gapped machine. No callbacks. No telemetry.
No Strix server required at runtime.

**Proof Readiness Level 4.5** — Cryptographically Signed + Externally Verifiable

**Public source:** [github.com/strixgov/strix](https://github.com/strixgov/strix)
**PyPI:** [pypi.org/project/strix-verify](https://pypi.org/project/strix-verify)

---

## Installation

```bash
pip install strix-verify
```

Or from source:

```bash
cd python/strix-verify
pip install -e .
```

---

## Quick Start

```python
from strix_verify import verify_evidence

result = verify_evidence(
    evidence_id=123,
    proof_base="https://strix.example.com",  # your Strix deployment
    jwks_base="https://strixgov.com",        # canonical JWKS surface
)

print(result.signature_valid)                          # SignatureStatus.VERIFIED
print(result.hash_valid)                               # True
print(result.compliance.article12_tamper_resistant)    # True
print(result.compliance.article14_human_oversight)     # True
print(result.compliance.article28_provider_obligations) # True
```

`proof_base` is required — there is no default. The verifier is neutral
across Strix deployments and never carries a hardcoded host URL.

### Async

```python
import asyncio
from strix_verify import fetch_evidence_async, verify_evidence_record

async def main():
    record = await fetch_evidence_async(123, proof_base="https://strix.example.com")
    result = verify_evidence_record(record, jwks_base="https://strixgov.com")
    return result

asyncio.run(main())
```

---

## Verification Flow

1. **Fetch** — Retrieve evidence record from the Strix proof API
2. **Reconstruct** — Build canonical 13-field payload (locked field order)
3. **Fetch key** — Resolve Ed25519 public key from JWKS endpoint
4. **Verify signature** — Ed25519 signature over canonical payload
5. **Verify hash** — SHA-256 hash of canonical payload
6. **Derive compliance flags** — EU AI Act flags derived from outcomes (never asserted)
7. **Return** — `VerificationResult` with all outcomes

---

## Two-Layer Verification Model

### Layer 1 — Cryptographic Validity (`signature_valid`)

> "Was this record produced by the holder of the Strix signing key?"

| Status | Meaning |
|--------|---------|
| `VERIFIED` | Valid Ed25519 signature from a known key |
| `LEGACY_UNSIGNED` | Pre-signing record (migration before 0039) |
| `UNVERIFIABLE_KEY` | Signing key ID not found in JWKS or extra_keys |
| `COMPLIANCE_VIOLATION` | Signature present but invalid |
| `ERROR` | Network or unexpected failure |

### Layer 2 — Deployment Context (`environment_match`, `tenant_match`)

> "Is this record appropriate for this deployment context?"

Optional checks against stored record fields. Per **SE-14**, verification reads
`environment` and `tenantId` from the stored evidence record — never from
environment variables. This prevents false failures when a production record is
verified in a development context.

```python
result = verify_evidence(
    evidence_id=123,
    expected_environment="production",   # checks stored record field
    expected_tenant_id="tenant-abc",     # checks stored record field
)
print(result.environment_match)  # True/False/None
print(result.tenant_match)       # True/False/None
```

---

## Verifying a Local Record

If you already have the evidence record dict (from a database, file, etc.):

```python
from strix_verify import verify_evidence_record

result = verify_evidence_record(
    record,
    jwks_base="https://strixgov.com",
)
```

---

## Verifying Offline (Air-Gapped)

For audit environments that cannot reach the internet, pre-fetch the JWKS
once, save it next to the evidence, and verify with no network access at
runtime:

```python
import json
from strix_verify import verify_evidence_record

with open("evidence-2026-04-19.json") as f:
    record = json.load(f)

with open("jwks-snapshot-2026-04.json") as f:
    extra_keys = json.load(f)["keys"]

result = verify_evidence_record(
    record,
    extra_keys=extra_keys,
    jwks_base="https://unused.invalid",  # never reached when key is in extra_keys
)
```

When the signing key is already present in `extra_keys`, `jwks_base` is
never contacted. The verifier resolves the key from the pre-fetched JWK
list and does the full cryptographic check locally.

---

## Key Rotation Support

Historical signing keys can be provided to verify older records after key rotation:

```python
import json, os

extra_keys = json.loads(os.environ.get("STRIX_SIGNING_JWKS_EXTRA", "[]"))

result = verify_evidence(
    evidence_id=123,
    extra_keys=extra_keys,
)
```

Key ID format: `strix-{env}-{YYYY-MM}` (e.g., `strix-prod-2026-04`). EU AI Act
compliance requires a minimum 2-year key retention period.

---

## EU AI Act Compliance Flags

Compliance flags are **derived** from verification outcomes — never read from stored
fields (invariant CI-5). Altering the `regulatoryContext` block in a signed record
invalidates the Ed25519 signature.

| Flag | Derived From |
|------|-------------|
| `article12_tamper_resistant` | `hash_valid AND chain_valid AND signature_valid == VERIFIED` |
| `article14_human_oversight` | `signature_present` (actor fields cryptographically bound) |
| `article28_provider_obligations` | `signature_valid == VERIFIED` (evidence from known key) |

---

## Canonical Payload Schema

The 13-field locked-order payload that is signed and hashed:

```
schemaVersion (always "1")
evidenceId
evidenceHash
proofChainHash
capabilityId
action
actorId
actorRole
createdAt
signingKeyId
environment
tenantId
regulatoryContext { complianceMode, euAiActArticle12, euAiActArticle14, euAiActArticle28 }
```

**Warning:** Reordering these fields invalidates all existing signatures. This schema
is locked and versioned. See `tests/test_payload.py::test_golden_vector` for the
canonical serialization canary.

---

## JWKS Endpoint

Public keys are served at:

```
GET https://strixgov.com/.well-known/strix-jwks.json
GET https://strixgov.com/.well-known/strix-jwks.json?kid=strix-prod-2026-04
```

The endpoint follows RFC 7517. Each key is an OKP/Ed25519 JWK with a `kid` in
`strix-{env}-{YYYY-MM}` format.

---

## Development

```bash
# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=strix_verify --cov-report=term-missing

# Lint
ruff check src/ tests/

# Type check
mypy src/
```

---

## Architecture Notes

- **No Strix SDK dependency** — pure Python + `cryptography` + `httpx`
- **Ed25519 SPKI DER construction** matches the TypeScript implementation exactly:
  12-byte header `302a300506032b6570032100` + 32 raw key bytes
- **Never raises** on bad signatures — `verify_signature()` returns `False`
- **Sync and async** HTTP clients available for all network operations

---

## License

MIT — see LICENSE.
