> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wardin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Offline Verification

> Verify a Wardin Evidence Bundle's ED25519 signatures and hash chain locally — no network, no Wardin account, no trust in us.

Most audit logs ask you to trust the vendor's dashboard that the record is real. Wardin signs each receipt, so you can prove the chain is authentic **yourself** — offline, with no Wardin account, and without trusting us. That's the whole point of the evidence layer: the proof doesn't depend on the vendor.

The **bundle format and the exact verification algorithm are documented below**, so you — or an auditor — can reimplement the check in any language, depending on **nothing from us**. `wardin-verify` is a reference implementation of that spec: a standalone Go tool that recomputes every hash and checks every ED25519 signature locally.

<Note>
  The `wardin-verify` binary and its source are being prepared for public release.
  Until then, the format + algorithm below are the source of truth — a faithful
  reimplementation verifies real bundles independently. (Wardin builds it from the
  `apps/gateway/cmd/verifier` package via `task build:verifier`.)
</Note>

## Verify a bundle

```bash theme={null}
# Export a bundle (any tenant admin key), then verify it — fully offline
curl "https://api.wardin.ai/v1/compliance/evidence-bundle?range=30d" \
  -H "Authorization: Bearer wardin_sk_ADMIN_KEY" > bundle.json

wardin-verify bundle.json
# ✓ INTEGRITY VERIFIED — all N receipts are untampered and signed by their declared keys.
```

* `wardin-verify --json bundle.json` — machine-readable report.
* `cat bundle.json | wardin-verify` — read from stdin.
* Exit code `0` = every receipt verified (and, with `--keys`, every key pinned); `1` = a signature, hash, chain link, or `seq` gap failed; `2` = the bundle could not be read/parsed.

### Integrity vs. authenticity — pin the keys

A clean result proves **integrity**: the chain is internally consistent, untampered, and each receipt is signed by the key the bundle *declares* for it. It does **not** by itself prove **authenticity** — the bundle carries its own key registry, so a forged bundle could be internally consistent under a fresh keypair.

To close that gap, `wardin-verify` prints each signing key's **fingerprint** (`sha256(publicKey)` truncated). Pin them against Wardin's published key registry, or fail the run unless they match:

```bash theme={null}
wardin-verify --keys=faf01f61caa36c4d,<other-fp> bundle.json
# ✗ KEY PIN FAILED — signing key(s) NOT in your pinned set: [...]
```

## The Evidence Bundle format

A bundle is a single JSON object. Fields (camelCase):

| Field              | Description                                                                                                                                                                        |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bundleVersion`    | Format version (`"1"`).                                                                                                                                                            |
| `generatedAt`      | ISO-8601 timestamp of export.                                                                                                                                                      |
| `tenantId`         | The tenant the receipts belong to.                                                                                                                                                 |
| `range`            | `{ label, days, fromSeq, toSeq, fromTs, toTs, receiptCount }` — the exported segment.                                                                                              |
| `anchor`           | `{ type, prevHash, note }` — the starting `prev_hash` the chain is verified from (see below).                                                                                      |
| `signingKeys`      | `[{ keyId, publicKey, validFrom, validTo, revokedAt }]` — every key the receipts reference. `publicKey` is a base64 raw 32-byte ED25519 key. Multiple entries cover key rotations. |
| `unresolvedKeyIds` | `key_id`s referenced by receipts with no resolvable public key — their receipts verify as **unverifiable**. Empty when complete.                                                   |
| `packs`            | Compliance packs in force (`framework`, `version`, `packHash`, …) — informational.                                                                                                 |
| `receipts`         | The signed receipt rows (below), ordered by `seq`.                                                                                                                                 |
| `disclaimer`       | Honesty posture — surfaced verbatim.                                                                                                                                               |

Each **receipt** row carries the signed canonical fields verification needs, plus two unsigned side channels (`packVersion`, `kind`) it ignores:

| Field                                                 | Notes                                                                         |
| ----------------------------------------------------- | ----------------------------------------------------------------------------- |
| `seq`                                                 | Per-tenant monotonic sequence (chain order).                                  |
| `requestId`, `tenantId`, `actor`, `provider`, `model` | Canonical string fields.                                                      |
| `promptTokens`, `completionTokens`                    | Unsigned integers.                                                            |
| `costUsd`                                             | Float, formatted to **6 decimal places** in the canonical form.               |
| `checks`                                              | Ordered `[{ name, result }]` — the enforcement decisions.                     |
| `tsMillis`                                            | Receipt timestamp as **epoch milliseconds** (UTC).                            |
| `prevHash`                                            | Previous receipt's `thisHash` (hex); empty string for genesis.                |
| `thisHash`                                            | This receipt's chain hash (hex).                                              |
| `signature`                                           | Base64 ED25519 signature over `thisHash`.                                     |
| `keyId`                                               | Which `signingKeys` entry verifies this row.                                  |
| `packVersion`                                         | Compliance pack version in force (unsigned side channel).                     |
| `kind`                                                | `model_call` or `tool_call` (unsigned side channel; ignored by verification). |

## The verification algorithm

For each receipt, in `seq` order, recompute and check:

1. **Canonical bytes** — serialize the signed fields in this exact fixed order, each as `key=value\n` (no map iteration, no whitespace):

   ```
   request_id=<requestId>\n
   tenant_id=<tenantId>\n
   actor=<actor>\n
   provider=<provider>\n
   model=<model>\n
   prompt_tokens=<promptTokens>\n
   completion_tokens=<completionTokens>\n
   cost_usd=<costUsd, formatted to 6 decimals>\n
   checks=<name=result joined by ",", in order>\n
   ts=<tsMillis>\n
   ```

2. **Chain hash** — `thisHash == sha256( canonicalBytes ‖ prevHashBytes )`, where `prevHashBytes` is the raw (hex-decoded) previous hash — the prior receipt's `thisHash`, or the bundle `anchor.prevHash` for the first row (empty bytes for true genesis). A mismatch means a field was tampered.

3. **Chain linkage** — the receipt's `prevHash` must equal the running previous hash (the anchor for the first row, the prior `thisHash` after). And `seq` must be contiguous (`prevSeq + 1`). A break here is a fork or a gap.

4. **Signature** — ED25519-verify `signature` over the `thisHash` bytes, using the `publicKey` from the `signingKeys` entry whose `keyId` matches. Because keys resolve from the bundle, verification survives key rotation with no network call.

5. **Tenant** — every receipt's `tenantId` must equal the bundle's `tenantId`.

Any receipt that fails any step is reported with its `seq`; the tool exits non-zero. The canonical serialization is byte-identical to what the gateway signs, so a bundle that verifies here is authentic byte-for-byte (subject to the key-pinning note above).

<Note>
  **Trust anchor.** Verification trusts the bundle's `anchor.prevHash` as the
  starting link. A full audit should confirm the segment begins at true genesis
  (`seq` 1) or cross-check the anchor against an out-of-band record. A future
  release publishes a signed Merkle root as the anchor.
</Note>

## Why this matters

An auditor can take a bundle, run `wardin-verify` (or their own reimplementation from the spec above), and establish — with **no access to Wardin, no account, and no trust in us** — that every governed request's signed record is intact and authentic. See [The Evidence Layer](/concepts/evidence-layer) and [Signed Receipts](/concepts/receipts).
