Winch Labs

Security

Verifying receipts offline

A hash chain proves internal consistency: no row was altered after it was written, given this chain. It does not prove authenticity — that this chain is ours and not one somebody generated. Signing is what closes that gap.

The public key endpoint

https://<your-instance>/.well-known/garboard-receipts.json

An auditor handed a bundle needs this, and it is the thing most likely to be missing from a compliance conversation. It is served by the instance that signed the receipts.

Verifying

garboard receipt verify receipts.json --keys keys.json

No call to us, and no call to your instance if you fetched the keys once. That is the property worth having: an auditor who must ask the vendor whether the vendor’s records are genuine has learned nothing.

What the chain proves, precisely

Each receipt’s hash is the SHA-256 of its canonical JSON with the Hash field excluded, and the previous receipt’s hash is part of that digest. VerifyChain returns the index of the first bad row, not just a boolean — so a tampered record is located, not merely detected.

Append-only is enforced in more than one place rather than by convention.

Signed vs unsigned is never blurred. An instance with no signing key still writes receipts, and every surface labels them unsigned. There is no state in which an unsigned receipt is reported as verified.

Keys

Variable Purpose
GARBOARD_RECEIPT_KEY A base64 32-byte Ed25519 seed. Generate with garboard receipt keygen.
GARBOARD_RECEIPT_RETIRED_KEYS Previously used keys, so receipts signed with them still verify after a rotation.

Ed25519 held in a secret store rather than a cloud KMS asymmetric key, deliberately: a self-hosted customer can sign without any cloud call, which keeps the offline property real rather than nominal.

Rotating does not invalidate history. Move the old key into the retired list and past receipts keep verifying. Dropping it instead is what breaks them.