Blog · engineering

Declaring how we hash: a small standard for the bytes behind a ten-second proof

If two implementations hash the binding of a ten-second presentation over different bytes, a valid proof fails or a wrong one passes. MintID now declares its rule — RFC 8785 canonicalization, then SHA-256 — cites every digest as a typed reference, and hashes Merkle-tree entries as raw bytes. Recorded as bIP-0003; existing integrations see the same bytes, and the rule follows an individual IETF submission on SCITT payload binding.

Updated 2026-10-04: profile availability and headings.

Why MintID now names its canonicalization algorithm, cites every digest with a type, and prepares its verdicts to be checked by anyone — without asking anyone to trust us.

A MintID presentation lives for ten seconds. In that window a verifier sends a signed challenge, the holder answers with a zero-knowledge proof bound to that exact challenge, and the verifier decides. Everything about the binding — which origin, which verifier key, which nonce, which policy, which finalized block — is folded into one 32-byte digest. If two implementations compute that digest over different bytes, a perfectly valid proof fails, or worse, a proof made for one challenge is accepted for another.

So the boring question matters: which bytes, exactly?

One language wrote the bytes, so only one language could check them

Until now the answer was implicit: “the JSON our reference implementation emits” — sorted keys, no whitespace. It worked because one language wrote it. It stops working the moment a second language has to reproduce it byte for byte, which is precisely what our verifier SDKs in other languages do.

MintID now names its algorithm: RFC 8785, then SHA-256

MintID now declares its canonicalization algorithm. Every JSON object we hash, sign or cite by digest is canonicalized with the JSON Canonicalization Scheme of RFC 8785, then hashed with SHA-256. There is a name for that combination in a recent IETF document, Canonicalization Declaration for SCITT Signed Statements (draft-mih-sokolov-scitt-payload-binding), an individual submission circulated on the SCITT working group’s list, and we use its vocabulary: the algorithm is called jcs; digests are cited as typed digest references — a reference says what kind of object it points at and how the digest was made; and anything placed in a Merkle tree or a transparency log is hashed as raw bytes, never as hexadecimal text. Small rules, each with a specific failure mode behind it.

What changed for existing integrations: nothing. For the payloads MintID actually sends, the new rule produces the same bytes as the old one. Every frozen conformance vector is unchanged. What changed is that an implementer in Go, TypeScript or PHP can now point at an RFC instead of at our Python.

Verdicts a third party can timestamp and check later

A MintID verifier does not only decide; it can publish. An x402 explorer that re-checks a seller’s human-backing every few hours could publish its own signed verdict: “human-backed at this assurance level, verified at this time, re-checked every N hours.” The same draft describes how such a statement should be packaged — a COSE signed statement (RFC 9052) with a content-derived identifier — so that a transparency service can issue a receipt for it and any third party can later verify that the verdict existed, unchanged, at that time. MintID does not run a transparency service and does not need one: verification of a MintID claim carries no protocol fee, and anyone can recompute a verdict’s inputs against the chain. A receipt adds independent, time-stamped evidence on top. Our x402 profile now records this packaging as the shape our published verdicts will take.

The proof and the chain stay exactly as they were

The zero-knowledge proof itself is a binary object and is not canonicalized as JSON; the rule covers what the proof is bound to, not the proof. No on-chain logic changes. No new field carries anything about a holder: typed references point at public objects — a status root, an issuer’s public definition, a challenge — and nothing else.

Naming the algorithm is the cheapest interoperability there is

Identity infrastructure is judged on interoperability and on what can be verified without trusting the operator. Naming the algorithm is the cheapest possible investment in both. The profile is recorded as bIP-0003, available on request, and the draft we align with is an individual submission we will track as it evolves; where an RFC already exists, we adopt it.

References

What changed for existing MintID integrations?
Nothing. For the payloads MintID actually sends, the declared rule — RFC 8785 canonicalization followed by SHA-256 — produces the same bytes as the previous implicit rule. Every frozen conformance vector is unchanged. What changed is that an implementer in another language can now point at an RFC instead of at the Python reference implementation.
Does MintID implement SCITT?
No. MintID adopts the parts that already stand on RFCs — RFC 8785 for canonicalization, RFC 9052 for COSE signed statements, RFC 9995 for the hash-envelope shape — and cites the SCITT payload-binding draft as an alignment target. That draft is an individual IETF submission (version 05, September 2026), not a standard, and MintID makes no claim about its future.
What is a typed digest reference?
A digest cited together with what kind of object it points at and how the digest was computed — the canonicalization algorithm and the hash. A bare 32-byte value can be mistaken for the digest of the wrong object or the wrong bytes; a typed reference cannot.
Does this touch the zero-knowledge proof?
No. The proof is a binary object and is not canonicalized as JSON; the rule covers what the proof is bound to — the challenge, the policy, the chain state — not the proof itself. No on-chain logic changes, and no new field carries anything about a holder: typed references point at public objects only.

Point at an RFC, not at our code

If you are implementing a MintID verifier client in another language, the bytes are now specified. Tell us where the profile is unclear.