Open specification
steadywrk.receipt-chain/v1
A hash-chained, optionally-signed record format. This document is complete enough to implement a verifier against without reading STEADYWRK’s source — which is the point, because asking someone to recompute a chain is only a real invitation if they can do it without our cooperation.
Live instance: /api/public/receipt-chain. Every response carries this algorithm inline, so the JSON is self-describing even to a reader who never finds this page.
1. The algorithm
Normative. These are the exact strings the endpoint serves, imported here rather than restated.
- leaf
- receipt_hash = SHA256(canonical_json(receipt)) — canonical_json sorts object keys, no whitespace (equivalently: jq -cS)
- link
- chain_n = SHA256(chain_{n-1} | n | ts | receipt_hash), with chain_0 = SHA256(genesis_tag) and ts = receipt.completed (empty string when null)
- order
- links are sorted by (ts, phase, receipt_hash) ascending — a total, machine-independent order, so the same receipt set always yields the same head
- implementation
- packages/ledger (genesisChain, computeChain) — the same chain functions the foresight seal ledger uses
Canonical JSON means object keys sorted at every depth and no whitespace — byte-for-byte what jq -cS produces. Two implementations that disagree about key ordering will compute different leaf hashes and therefore a different head, so this is the detail to get right first.
2. Response fields
| Field | Type | Required | Meaning |
|---|---|---|---|
| schema | string | yes | Exactly "steadywrk.receipt-chain/v1". A verifier that does not recognise the value must stop and say so rather than guess at the format. |
| genesis_tag | string | yes | The domain separator the chain starts from. chain_0 is its SHA-256. |
| genesis_chain | hex string (64) | yes | SHA256(genesis_tag). Published so a reader can check the starting point rather than assume it — recompute it; do not trust it. |
| count | integer | yes | Number of links. Also the first component of the signed payload. |
| head | hex string (64) | yes | The final chain value. This is the number to record today and re-check later. |
| links[] | array | yes | Each entry carries { n, ts, receipt_hash, chain, receipt }. The receipt is the full public-safe record; the other four are derivable from it and from the preceding link, which is what makes the feed checkable rather than merely readable. |
| algorithm | object | yes | The four normative strings above, served inline so the response is self-describing. |
| integrity.signing | object | yes | When enabled is true: algorithm, key_id, signed_payload_format and signature_b64. When false: a stated reason. A response that omits this entirely is not conforming — "unsigned" must be said, never left to be inferred from absence. |
3. The signature layer
Optional to the format, load-bearing to the trust story. A response may be unsigned — but it must say it is unsigned, with a reason. Absence is never permitted to stand in for a statement.
- algorithm
- Ed25519
- payload
- `${count}|${head}` as UTF-8, with no trailing newline. The pipe is significant and is what keeps this payload space disjoint from the Trust Rail one (a 64-char hex fingerprint can never contain a pipe), so no signature from either protocol can be replayed as the other.
- encoding
- signature_b64 is standard base64.
- key
- Ed25519 public key, published as base64 SPKI DER in the response and as multibase in /.well-known/did.json.
- pinning
- A conforming verifier MUST check the signature against a key it already holds, not against public_key_spki_b64 from the same response. Verifying a response with a key that response supplied proves only that the response agrees with itself. Where the reader obtains that key out of band is, today, an open problem STEADYWRK has not solved — see the limits below.
The current key, key id receipt-chain-ed25519-v1:
MCowBQYDK2VwAyEAs7p9TfaWI8zE21UVGE2nF1y1puZuRF2lHV2Fa7VRcJY=4. Limits a conforming verifier must report
This section is normative. A verifier that recomputes the head correctly and then describes its result as independent verification is not conforming — the recomputation is sound and the provenance is not. A spec that documents only its guarantees trains implementers to over-trust the format.
- Recomputation proves internal consistency, not unrewritten history.
- The same party serves the data and the head. Anyone holding the private key can edit a receipt, re-chain everything after it, re-sign, and produce a feed that verifies perfectly. The format cannot prevent this and does not claim to.
- Tamper-evidence comes from the READER, not the feed.
- Record a head with the date you saw it, and re-check it later. A head you recorded that no longer appears is the only signal in this system that catches the rewrite above. A verifier should tell its user to do this rather than imply the check it just ran was sufficient.
- The signature binds a key-holder to a head, not a head to a history.
- It establishes that whoever holds the private key endorsed this exact head. It says nothing about whether the head describes an unedited past.
- A matching hash does not make a receipt true.
- It proves the bytes hash to the published digest. Whether the work a receipt describes actually happened is a claim about STEADYWRK, not about SHA-256.
- The public key currently has no out-of-band source.
- As of 2026-08-11 the pinned key can only be obtained from steadywrk.app — the repository is private and the verifier is unpublished. A cold external check confirmed this. Until a copy exists somewhere STEADYWRK does not serve, a conforming verifier should report that its key provenance is unestablished, and MUST NOT describe its result as independent verification.
- External timestamps exist but are not yet reachable.
- Each head is timestamped by two unrelated RFC 3161 authorities, and each token verifies offline against its own certificate chain. The tokens are held in a private repository, so no third party can currently obtain one. They should be treated as absent until published.
5. Versioning
steadywrk.receipt-chain/v1 is frozen. Any change to leaf hashing, link construction, or ordering produces a different head from the same receipts — which would silently invalidate every head anyone had recorded, the exact failure this format exists to make impossible. Such a change ships as steadywrk.receipt-chain/v2 under a new schema value, served alongside v1 rather than in place of it.
A verifier should refuse to check a payload whose schema it does not recognise, and say so, rather than assume the format it knows.
Implementing this
Everything above is sufficient — no repository access needed. Two things worth doing that the format cannot make you do: test your verifier against deliberately broken input (a verifier only ever observed passing is not evidence — corrupt a receipt, corrupt the head, swap the key, and confirm each one flips your verdict), and record a head with the date you saw it. The second is the only check in this system that catches a rewrite.
See also /verify-us for the step-by-step procedure and the current list of what cannot be checked yet.