DefendableLedger · Verify a Receipt
The real verification path
Section titled “The real verification path”There are two real things to verify today, and they verify differently. Be precise about which one you have.
| surface | what it is | how it verifies |
|---|---|---|
| DefendableCloud (LIVE, api.defendablecloud.com) | per-org hash chain in Postgres | GET /ledger/verify — server-side recompute, authenticated |
| DefendableRouter (v0.1, local, not deployed) | flat checksummed receipts in JSONL | recompute checksum_sha256 per line |
Verify the cloud chain · GET /ledger/verify
Section titled “Verify the cloud chain · GET /ledger/verify”The live, authoritative verifier is the cloud endpoint. It is authenticated and server-side — scoped to the caller’s org, recomputed inside the API. It is not an anonymous client-side WebCrypto verifier.
curl -H "Authorization: Bearer $DC_API_KEY" \ https://api.defendablecloud.com/ledger/verifyWhat it does (app/routes/public.py · verify_ledger):
- Loads the org’s receipts ordered by
org_seqascending. - For each receipt, recomputes
receipt_sha256 = sha256_hex(orjson.dumps(payload, OPT_SORT_KEYS))and compares to the stored value. - Checks
org_seq == index(no sequence gaps). - Checks
parent_hashpoints at the prior receipt’sreceipt_sha256(genesis parent = sixty-four zeros).
Response shape:
{ "ok": true, "receipts_checked": 42, "errors": []}On tamper, ok flips to false and errors[] pinpoints the offending org_seq:
{ "ok": false, "receipts_checked": 42, "errors": [ { "org_seq": 17, "error": "hash mismatch" } ]}Error kinds: hash mismatch, sequence gap (expected N), broken parent link. (errors[] is capped at the first 20.)
Verify a router receipt · checksum_sha256
Section titled “Verify a router receipt · checksum_sha256”DefendableRouter writes flat receipts to data/receipts/YYYY-MM-DD.receipts.jsonl, one per line. Each line carries a checksum_sha256 over the canonical JSON of the receipt excluding the checksum field itself. These are per-line checksummed, NOT hash-chained — there is no parent_hash, no cross-line link.
The real canonicalizer (defendable_router/core/receipts.py):
def canonical_json(payload): # _normalize: Decimal -> "{:.2f}" string, datetime -> isoformat() return json.dumps(_normalize(payload), sort_keys=True, separators=(",", ":"), ensure_ascii=True)
def checksum_receipt(payload_without_checksum): return hashlib.sha256(canonical_json(payload_without_checksum).encode()).hexdigest()To verify a router receipt by hand:
- Take the receipt line, drop the
checksum_sha256field. - Re-derive any
Decimalas"{:.2f}"and any timestamp as ISO 8601. - Serialize with
sort_keys=True,separators=(",", ":"),ensure_ascii=True. sha256the UTF-8 bytes.- Compare to the stored
checksum_sha256.
Note this is a different canonicalizer from the cloud’s orjson OPT_SORT_KEYS. Do not assume one tool verifies both.
Roadmap items
Section titled “Roadmap items”🐝 Recompute the hash · check the chain · books and records · to the shed.