Skip to content

The seal hash

Publishing this is the point. A sealed ledger you have to take on faith is a database with better marketing.

recordHash = SHA-256( UTF-8( canonicalDocument ) ) → lowercase hex

where canonicalDocument is a JSON object with these keys, in this order, PascalCase:

{"EntityType":"VoucherInstance:events","EntityId":"<32 hex, no dashes>","CanonicalPayload":"<the stored payload string>","PreviousHash":"<64 hex>","SequenceNumber":1}

Events sealed with an actor use the same shape plus two more keys at the end:

,"ActorConnectedId":"<32 hex>","ActorIsEmployee":true

EntityType is the anchor namespace — "{type}:events", not the bare type. The API returns it as anchorNamespace so you never have to construct it.

The server serializes with .NET’s System.Text.Json default encoder, which does not agree with JSON.stringify.

Escaped as \uXXXX with uppercase hex, even though most are ASCII-safe:

characterbecomes
""
&&
''
++
<<
>>
backtick```
DEL (0x7F)
every non-ASCII character\uXXXX per UTF-16 code unit

Not escaped, though people assume they are: = and /.

Kept in short form: \\, \b, \f, \n, \r, \t.

Two consequences that catch every first attempt:

  • 프로 becomes 프로, not the literal characters.
  • 🎉 becomes 🎉 — a surrogate pair, two escapes, not one.

A naive implementation passes every ASCII test and then reports a genuine voucher as tampered the first time a product is named in Korean, or a note contains an ampersand.

sdk/ts/test/vectors.json in the repository is generated from the real C# hasher and asserted by both implementations. sdk/ts/test/ascii-escape-map.txt records exactly which printable-ASCII characters the encoder escapes — measured over the whole range, not remembered.

If you implement this in another language, run against those vectors. If they fail, your implementation is wrong; the vectors are not a snapshot to be regenerated.

Events older than canonPreservedSinceUtc (2026-08-02 11:00 UTC) lost their canonical string to a jsonb column that normalised the JSON on insert. They can never re-hash. Report them legacy, never tampered — see Verify the seal yourself.