Verify the seal yourself
Every voucher carries an append-only chain of sealed events: minted, handed over, reserved, redeemed. Each event’s hash covers the one before it, so an edit anywhere breaks everything after it.
You can check that without asking us anything.
const result = await bm.integrity.verify(code);// → { verdict: 'verified', chainIntact: true, events: [...] }verify() fetches the chain and recomputes the hashes locally. The word verified is
your conclusion, not our claim.
Two checks, reported separately
Section titled “Two checks, reported separately”They fail for different reasons, and conflating them hides which one broke.
Chain — event N’s previousHash must equal event N−1’s recordHash, and the first
must link to sixty-four zeros. Needs no payloads.
Content — SHA-256 over five canonical pieces must reproduce recordHash:
SHA-256( "{entityType}:events", entityId, payload, previousHash, sequenceNumber )with two more fields when the event was sealed with an actor. Needs the payloads, which only your own key can fetch — the public endpoint withholds them, because a voucher payload contains redeem codes.
Verdicts
Section titled “Verdicts”| verdict | meaning |
|---|---|
verified | everything links and everything re-hashes |
chain-broken | an event does not link to the one before it |
tampered | a payload no longer produces its stored hash |
legacy | see below |
unsealed | an event has not been anchored yet |
unchecked | payloads were not supplied, so only the links were checked |
legacy is not an accusation
Section titled “legacy is not an accusation”Events sealed before 2026-08-02 11:00 UTC were stored in a jsonb column, and Postgres
normalised their JSON on the way in — reordering keys, changing spacing. The canonical
string those rows were hashed from no longer exists, so they can never re-hash, even though
nobody touched them.
Any correct implementation reports those as legacy, never tampered. The safe direction
of error is to call a genuine row legacy; the unsafe one accuses a brand of forgery over a
schema migration.
Doing it in another language
Section titled “Doing it in another language”The algorithm is public and the conformance vectors are in the
repository. The one thing that catches everybody: the server serializes with .NET’s
System.Text.Json default encoder, which does not agree with JSON.stringify.