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):
Each receipt row carries the signed canonical fields verification needs, plus two unsigned side channels (
packVersion, kind) it ignores:
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. See The Evidence Layer and Signed Receipts.