> ## 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.

Every log tool asks you to trust *their* 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 exactly the fields verification needs:

| 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).       |

## 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. No log/observability tool can offer that, because none sign their records. See [The Evidence Layer](/concepts/evidence-layer) and [Signed Receipts](/concepts/receipts).
