npm package: @strixgov/verifier
Description: Independent verifier for Strix Signed Evidence v1
Latest version: 0.1.0

# @strixgov/verifier

Independent verifier for Strix Signed Evidence v1.

Lets any third party cryptographically verify a Strix governance evidence record using only:
- A downloaded evidence JSON (from the Trust Center or `/api/proof/:evidenceId`)
- The JWKS public key document served at `/.well-known/strix-jwks.json` (RFC 7517)
- Standard tools (Node.js, `tweetnacl`)

No Strix account, API key, or SDK dependency required.

---

## Install

```bash
npm install
npm run build
```

---

## Verify using local fixtures

```bash
node dist/src/cli.js verify \
  --file fixtures/signed-evidence.sample.json \
  --keys-file fixtures/keys.active.json
```

Expected output:
```
Strix Evidence Verification
---------------------------
Evidence ID:         evi_0001
Record Type:         signed_evidence_v1
Hash Valid:          true
Chain Valid:         true
Signature Present:   true
Signature Valid:     true
Algorithm:           Ed25519
Signing Key ID:      strix-prod-2026-04
Verification Status: VERIFIED
Reason:              All verification checks passed
```

## Verify using a live key endpoint

```bash
node dist/src/cli.js verify \
  --file fixtures/signed-evidence.sample.json \
  --keys https://your-domain.com/.well-known/strix-jwks.json
```

## Emit JSON (machine-readable, exits 0 on VERIFIED, 1 otherwise)

```bash
node dist/src/cli.js verify \
  --file fixtures/signed-evidence.sample.json \
  --keys-file fixtures/keys.active.json \
  --json
```

## Demo flow narrative

```bash
node dist/src/cli.js demo
```

---

## Tamper demo

Change one field in the evidence file and re-run — the signature will fail:

```bash
node dist/src/cli.js verify \
  --file fixtures/signed-evidence.tampered-createdAt.json \
  --keys-file fixtures/keys.active.json
```

Expected output: `Verification Status: FAILED`

---

## Output states

| State | Meaning |
|-------|---------|
| `VERIFIED` | Hash, chain, and Ed25519 signature all pass. Record produced by key holder. |
| `FAILED` | Verification ran but one or more checks failed. Record may be tampered. |
| `UNVERIFIABLE` | Cannot verify. Key missing, unknown, or algorithm unsupported. |
| `LEGACY_UNSIGNED` | Pre-signing record. Hash integrity may still be checked. No cryptographic attribution. |

---

## How it works

1. Load the evidence JSON
2. Recompute `SHA-256(canonicalStringify(canonicalEvidence))` → compare to `evidenceHash`
3. Check `proofChainHash` is a valid SHA-256 hex string
4. Fetch the public key document (JWKS at `/.well-known/strix-jwks.json`), resolve key by `kid` matching `signingKeyId`
5. Resolve the canonical signed payload — from `signedPayload` field when present (preferred), or best-effort reconstruction
6. Verify the Ed25519 signature using `tweetnacl`

### Canonical payload schema (schemaVersion 1, locked field set)

```
action, actorId, actorRole, capabilityId, createdAt, environment,
evidenceHash, evidenceId, proofChainHash, regulatoryContext,
schemaVersion, signingKeyId, tenantId
```

Key points:
- `schemaVersion` is the integer `1` (not the string `"signed_evidence_v1"`)
- `evidenceId` is a number (the database row ID)
- `regulatoryContext` is a nested object: `{ euAiActArticle12, euAiActArticle14, euAiActArticle28, complianceMode }`
- All fields are serialized alphabetically by `canonicalStringify` before signing
- Any change to any field — even a type coercion — invalidates the signature

### Payload resolution strategy

Evidence exports from the Trust Center include a `signedPayload` field — the stored JSON of
exactly what was signed. When present, the verifier re-serializes it through `canonicalStringify`
and verifies the signature against the resulting bytes. This approach is independent of the
verifier's knowledge of the payload schema and is forward-compatible with future field additions.

If `signedPayload` is absent (pre-Trust-Center records), the verifier attempts reconstruction
from top-level fields. This may fail on records that include `actorId`, `actorRole`, or
`regulatoryContext` — those fields are not stored as separate top-level columns in all export
formats.

---

## Key rotation

The `--keys-file` / `--keys` JWKS document may include multiple keys. Retired keys
remain in the document for audit window coverage (recommended: 2 years).

The verifier resolves the correct key by matching the JWKS `kid` field to the evidence record's
`signingKeyId`. Records signed by retired keys remain verifiable as long as the retired key's
JWK entry is present in the document.

---

## Golden vector regression

`fixtures/GOLDEN_VECTOR.json` contains the locked canonical payload string and expected
verification result. If the canonical payload schema changes, the golden vector must be
regenerated with a gate decision:

```bash
npm run gen-vectors
```

---

## Trust notes

This verifier does not invent trust beyond what the evidence schema supports.

- Signed records in signature-enabled environments may be independently verified.
- Legacy unsigned records remain legacy and must not be represented as signed.
- `VERIFIED` means the record was produced by the holder of the named signing key.
  It does not prove execution correctness or that the governed action produced the claimed outcome.