wardin-verify is a reference implementation of that spec: a standalone Go tool that recomputes every hash and checks every ED25519 signature locally.
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.)Verify a bundle
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, orseqgap 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:
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_ids 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. |
| 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, inseq order, recompute and check:
-
Canonical bytes — serialize the signed fields in this exact fixed order, each as
key=value\n(no map iteration, no whitespace): -
Chain hash —
thisHash == sha256( canonicalBytes ‖ prevHashBytes ), whereprevHashBytesis the raw (hex-decoded) previous hash — the prior receipt’sthisHash, or the bundleanchor.prevHashfor the first row (empty bytes for true genesis). A mismatch means a field was tampered. -
Chain linkage — the receipt’s
prevHashmust equal the running previous hash (the anchor for the first row, the priorthisHashafter). Andseqmust be contiguous (prevSeq + 1). A break here is a fork or a gap. -
Signature — ED25519-verify
signatureover thethisHashbytes, using thepublicKeyfrom thesigningKeysentry whosekeyIdmatches. Because keys resolve from the bundle, verification survives key rotation with no network call. -
Tenant — every receipt’s
tenantIdmust equal the bundle’stenantId.
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).
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.Why this matters
An auditor can take a bundle, runwardin-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 and Signed Receipts.