# Canonicalization and hashing ## Ledger entries: RFC 8785 (JCS) An entry's `hash` is SHA-256, lowercase hex, over the UTF-8 bytes of the JCS serialization of the object ``` { at, type, correlation_id, facts, previous_hash, signer } ``` and nothing else: `hash` and `signature` sit outside it. `signer` (the writer's SPIFFE ID) sits **inside**, so an entry's claimed author cannot be rewritten without breaking the chain. `previous_hash` is the previous entry's `hash`, `null` on the first entry. The `signature` is ES256 by the instance key over the UTF-8 bytes of the hex `hash` string, base64url, 64 bytes R‖S. JCS, not "sorted keys, compact JSON" by habit. The points where they differ, and where a second implementation has to agree: - members sorted by **UTF-16 code unit** (§3.2.3), not by code point: U+1F600 (`😀`, surrogates D83D DE00) sorts before U+FB01 (`fi`). Python's `json.dumps(sort_keys=True)` gets this wrong; sort on the key's UTF-16 encoding; - numbers as ECMAScript `Number::toString` (§3.2.2.3): `1e+30`, `1e-27`, `4.5`, `0.002`; no exponent for integers below 1e21; - strings escaped as ECMAScript `JSON.stringify` (§3.2.2.2): ``, `\n`, `\"`, `\\`; non-ASCII literal; `/` not escaped; - a member whose value is `undefined` is **omitted**; `undefined` in an array is `null`; non-finite numbers are refused, never serialized. Vector: `jcs-vector.json` (a float, a nested array, non-ASCII text, and the code-unit ordering case). Reproducing its `sha256` from `input` is the acceptance test for a second implementation; the seller's Node implementation and an independent Python one agree on it (2026-09-07). The RFC's own §3.2.3 example is also asserted in the seller's tests. ## Signed artifacts: the bytes that were signed `quote_hash` is SHA-256, lowercase hex, over the **raw decoded payload octets** of the quote JWS, the exact bytes the seller signed. No canonicalization is involved, same principle as the DNS-AID card digest (`cap-sha256` over the raw fetched bytes). The seller computes it from the JWS it issued and writes it to its ledger; the buyer recomputes before asking the authority; the authority recomputes from the JWS the buyer forwards, after verifying the seller's signature; the seller recomputes at checkout from the stored JWS and refuses a stated hash that differs. Nobody accepts another party's value. The same rule applies to any other digest of a signed artifact (`body_sha256` in the request envelope is over the raw request body bytes). ## Not adopted, deferred RFC 9421 HTTP message signatures and `Content-Digest`: the signed envelope carries the body hash instead. Salted join fields: the join fields are random identifiers or digests already.