Manav.id
Compliance · 5 min read

The case for specifying the receipt format openly

The case for specifying the receipt format openly

An enterprise adopting a proprietary receipt format acquires a dependency on the vendor's existence for as long as the records must be verifiable. That is a strange property for an audit artefact.

Why does the receipt format have to be open?

Because evidence only one party can verify is not evidence in the sense that matters. If a court, a regulator or a counterparty has to call the vendor to check a receipt, the vendor's continued existence and cooperation are load-bearing parts of the proof.

Key takeaways
  • A receipt's value depends on third parties being able to verify it, which requires a format they can implement independently.
  • The specification is small — a statement structure, a canonicalisation, a signature layer and a verification procedure.
  • Publishing it costs the author a differentiator and buys the property that makes the artefact worth anything.

Why a proprietary format undermines its own purpose

Proprietary formatOne implementationVerification is a serviceVendor outlives the evidence, or does notAdversary cannot check independentlyLock-in is the productOpen specificationAny implementationVerification is arithmeticFormat outlives the vendorBoth sides can checkInteroperability is the productvs

The reason to produce a receipt is that someone other than you can check it: an auditor, a regulator, a counterparty, a court.

VerifierProprietary formatOpen specification
Your own systemsWorksWorks
Your auditorNeeds the vendor's toolCan implement or use any tool
A regulatorDepends on the vendorIndependent
An opposing party in litigationMust accept the vendor's toolCan write their own and compare
You, in eight yearsDepends on the vendor existingIndependent

The fourth row is the one that decides it. Evidence an opposing party must take on trust from the producing party's vendor is weak evidence, and a competent opponent will say so.

What needs specifying

Four layers, none large.

  1. Statement structure. Required fields, types, and what each means. Enough that two implementations produce the same object from the same facts.
  2. Canonicalisation. Reference RFC 8785 rather than inventing one.
  3. Signature layer. How the authenticator assertion and the issuer countersignature are carried and what each covers.
  4. Verification procedure. The ordered checks, and what each failure means.

Layer four is the one usually under-specified and the one that matters most in a dispute. Two implementations that check different things will disagree about the same receipt.

The rules that make it usable

A specification of this kind earns its keep by being restrictive.

RuleRationale
One signature algorithmNo negotiation, no confusion attacks
One canonicalisationReproducible digests
Required fields, not optional onesNo absent-field logic in verifiers
Masking permitted only with the full value signedTruncation must not be the authoritative form
Short expiry and single use by defaultReplay and staleness closed at the format level
Explicit versioning, no compatibility branchesVerifiers stay small

Each of these removes a choice, and each removed choice is code that never has to exist in any verifier anyone writes.

What publishing costs the author

Worth being direct about, because the argument is more credible when the cost is named.

In exchange, the receipts become worth something to the parties who need to rely on them. That trade is favourable only if you believe the format's value comes from adoption rather than from exclusivity.

What it does not need to specify

Restraint matters as much as coverage. A specification that grows to cover everything becomes unimplementable.

  1. How statements are rendered to humans — implementation-specific and rightly so
  2. Which actions should be gated — a policy question for each deployment
  3. How keys are managed operationally — beyond how they are published
  4. Storage, transport or retention — orthogonal to the format

The boundary is what a verifier needs in order to check a receipt. Everything else belongs in guidance rather than in the specification.

What to publish alongside

A specification without these is an aspiration.

The last is the one that separates a specification from marketing. Saying plainly that a receipt proves a credential signed a statement — and not that the signer understood it, or that the statement was true — is what makes the rest of the claims credible.

The fields, concretely

Minimum statement fields
FieldTypePurpose
vintegerFormat version. Refuse unknown versions.
typstringStatement type, so verifiers can route.
issURLIssuer, and where the key set is published.
substringSubject identifier, stable across statements.
iat / expintegerIssued-at and expiry, seconds since epoch.
noncebase64urlChallenge, so a statement cannot be replayed.
effectobjectWhat will happen, in structured form.
rendered_hashstringHash of exactly what the human was shown.

A test vector you can run

Canonicalisation is where interoperable implementations usually diverge, so the specification should ship a vector rather than a description. Take this statement body:

{
  "v": 1,
  "typ": "statement",
  "iss": "https://example.org",
  "sub": "did:key:z6MkExampleSubjectIdentifier",
  "iat": 1750000000,
  "exp": 1750000300,
  "nonce": "Zm9vYmFyYmF6cXV4",
  "effect": {
    "kind": "payment.release",
    "to": "ACME Supplies Ltd",
    "amount": "48250.00",
    "currency": "GBP"
  },
  "rendered_hash": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
}

Serialised with keys sorted lexicographically, no insignificant whitespace, and UTF-8 output, the canonical form is:

{"effect":{"amount":"48250.00","currency":"GBP","kind":"payment.release","to":"ACME Supplies Ltd"},"exp":1750000300,"iat":1750000000,"iss":"https://example.org","nonce":"Zm9vYmFyYmF6cXV4","rendered_hash":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae","sub":"did:key:z6MkExampleSubjectIdentifier","typ":"statement","v":1}

Its SHA-256 digest is:

hex:    928d769e0bbf38cbbad68b40fd367b30ada716b771f64656475f7695923b6722
base64url: ko12ngu_OMu61otA_TZ7MK2nFrdx9kZWR192lZI7ZyI

Any implementation that produces a different digest for this input has a canonicalisation bug, and will produce signatures that other implementations reject. That is the entire value of shipping a vector: the disagreement surfaces in a test rather than in a dispute.

What publishing costs, and what it does not

Objections and honest limits

“Publishing the format gives away the product.” The format is not the product. Issuance, key management, revocation, integration and support are. A published format makes the artefact worth relying on, which is what makes the rest saleable.

“An open format invites weak implementations.” It does, which is why the specification should state the rules that make interoperability testable — one canonicalisation, one signature suite, explicit rejection of unknown versions — and ship vectors that catch a wrong implementation immediately.

What the specification should not cover: how a subject identifier is chosen, what an effect object contains for a given domain, or how keys are rotated. Those are deployment decisions, and standardising them prematurely makes the format narrower without making it safer.

What to publish alongside the specification

  1. A reference verifier, permissively licensed. Reading code settles arguments that prose does not.
  2. Test vectors, including failure cases. Expired, wrong key, mutated body.
  3. The canonicalisation rule, with a worked example. This is where implementations diverge.
  4. A versioning policy. So unknown versions are refused, not guessed at.
  5. A public key set at a stable URL. Verification with no callback to you.

Terms used here

Canonicalisation
Producing one deterministic byte sequence from a data structure, so signatures are reproducible.
Test vector
A fixed input with a known correct output, used to catch implementation divergence.
Non-repudiation
The property that a signer cannot credibly deny having produced a statement.

Frequently asked questions

Why can't a proprietary format work? The receipt's purpose is verification by third parties. Evidence an opposing party must take on trust from your vendor is weak evidence.

What is most often under-specified? The verification procedure. Two implementations checking different things will disagree about the same receipt, which is exactly what a dispute exposes.

What does publishing cost? The format stops being a differentiator, switching becomes cheap, and design decisions become publicly contestable. That trade is favourable only if value comes from adoption.

What should be left out? Rendering, which actions to gate, operational key management and storage. The boundary is what a verifier needs to check a receipt.

Why ship test vectors rather than prose? Because canonicalisation bugs are invisible in prose and obvious against a vector. The disagreement surfaces in a test instead of a dispute.

What should the specification leave out? Subject identifier schemes, domain-specific effect contents and key rotation policy — deployment decisions that would make the format narrower without making it safer.

Does publishing the format lose the business? The format is not the business. Issuance, key management, revocation and support are, and they are only worth buying if the artefact is worth relying on.

Where this fits in Manav

Manav's statement format and its verifier are published, so anyone can check a receipt with no call to us — including after we are gone.

Read the specification →

Sources and further reading