Verify a Receipt
Prove what happened and what it cost — verify a signed Work Receipt offline, against published keys, without calling Ductor.
A Receipt is the artifact you hand an auditor, a customer, or a counterparty when they ask what exactly happened, and what did it cost. This guide is the counterparty's side: given a receipt, prove it is authentic — offline, with nothing but the receipt bytes and Ductor's published public keys.
- work
lead_9f42· converted- route
dec_01j8k…· thompson_sampling@v3 · explanation attached- tools
- 2 invocations · manifest
sha256:ab12…9e - cost
57 250 000µUSD- consent
trustedform:cert_77a…- settlement
stl_31c…· committed
What you are verifying
The signed unit is a canonical byte string — a versioned, deterministic serialization of the receipt's envelope. Three design choices make offline verification actually mean something:
- Everything that matters is inside the signature
- The algorithm and signing-key id are bound into the signed bytes under a domain-separation constant, so a signature can never be replayed onto different claims, a different scheme, or a different system's payloads.
- Lifecycle state is outside it
- Whether a receipt has settled is derived from immutable facts, never written into signed content — so a receipt settling later cannot disturb a signature you already verified.
- Stored bytes are the truth
- Verification runs against the exact canonical bytes that were signed, persisted alongside the receipt. You never re-serialize and hope your JSON matches.
The procedure
The whole path, and the two places a forged or altered receipt is caught:
Every failure edge is terminal: there is no fallback key, no re-serialization attempt, and no partial pass.
Fetch the published keys. Ductor serves its receipt-signing public keys as
a standard JWKS document — Ed25519 keys (OKP/EdDSA), each with a key id and
a validity window:
curl -s https://api.ductor.io/.well-known/work-receipts/jwks.jsonCache it. Everything after this step is offline.
Select the key. The receipt's key_id names the key that signed it. An
unknown key id fails verification — no fallback, no "try them all".
Check the window against issuance. The key must have been valid when the
receipt was issued — issued_at inside [not_before, expires_at). Retiring a
key never invalidates the receipts it legitimately signed; a revoked key
disappears from the JWKS entirely.
Recompute the hash, then verify the signature. SHA-256 over the canonical
bytes must equal the receipt's payload_hash — the independent tamper check —
and the Ed25519 signature must verify over those same bytes with the selected
public key.
sum := sha256.Sum256(canonical)
if hex.EncodeToString(sum[:]) != receipt.PayloadHash {
return Tampered
}
ok := ed25519.Verify(publicKey, canonical, signature)The verdict is structured, not boolean — each check answers separately:
Prop
Type
A hosted second opinion exists — VerifyWorkReceipt on the API, and the
governed work_receipt.verify tool for agents — running exactly this
procedure. It is a convenience, not a dependency.
Reading what you verified
A verified receipt is a set of claims, each anchored to deeper evidence:
workeridentifies who did the work — including, for an agent, the pinned definition (id, version, hash) it executed under. For non-assignment outcomes — a rejection, a drop —workeris explicitlynull, and that is a valid receipt: proof of what didn't happen is still proof.routecarries the decision id and a digest of its explanation — the snapshotted why, not a live reconstruction.toolscarries the content hash of the exposure manifest the agent acted under, joining the per-invocation receipts from the governed tool surface.costis exact:int64micro-units, withtotalequal to the sum of its breakdown by construction.settlementreferences the journal transactions that moved the money — arriving at issuance when settlement already committed, or later as a signed amendment that never mutates the original.
Amendments chain: a claw-back, a late settlement, each is a new signed receipt referencing its predecessor, and the chain reads newest-last with the original intact. Verify each link the same way.
A receipt shared outside the tenant is a distinct, re-signed redacted object — issued under a revocable, expiring grant, resolved by an opaque high-entropy token. It verifies on its own signature. The full receipt is never public, and a redacted receipt is never a blanked copy of it.