How it works

What is JCS canonical JSON and why does it matter?

JCS is the JSON Canonicalization Scheme defined in RFC 8785. It produces a deterministic byte representation of any JSON value, which is what makes cryptographic signatures over JSON reproducible.

Why standard JSON is bad for signatures

JSON allows arbitrary key ordering, whitespace, number formatting. Two parsers can serialise the same logical content to different bytes. A signature over one serialisation won't verify over the other. Result: signatures don't reliably verify across implementations. The fix is canonicalisation — agree on one serialisation, sign that.

What JCS does

JCS specifies: (a) sort keys in object lexicographically. (b) no whitespace except inside string values. (c) numbers in shortest form with no leading zeros. (d) strings escape the minimal set of control characters. Result: any two compliant JCS implementations produce the same bytes for the same logical content.

How JCS interacts with Ed25519

The signing flow: serialise the receipt content with JCS → produce canonical bytes → sign the bytes with Ed25519 → include signature in receipt envelope. The verification flow: take the receipt content, re-canonicalise with JCS, check the signature against the canonical bytes. If the content was tampered, the canonical bytes change, the signature fails to verify.

JCS implementations

JCS is straightforward to implement: ~200 lines of code in any language. We use the canonicalize npm package in TypeScript and the jcs PyPI package in Python. The work-receipt-spec repo includes reference implementations + test vectors so any implementer can verify their canonicaliser matches.

Alternatives we considered

CBOR (binary) — smaller but less debuggable. ASN.1 — old, well-supported but verbose. Protocol Buffers — schema-locked which doesn't fit our extensions story. JCS won on: human-readable, structurally JSON-shaped, standards-based, simple to implement. RFC 8785 is the right level of abstraction.

When canonicalisation goes wrong

Common bugs in custom canonicalisers: (a) sort keys using language-default ordering (which differs by locale) instead of strict byte-wise. (b) miss number normalisation (1.0 vs 1, 1e2 vs 100). (c) Unicode normalisation issues in string values (NFC vs NFD). Use the reference implementation; don't roll your own.

Related

Get the trust layer for your AI work

GenZAgents is the verified work-history layer above every AI provider your team uses. Sign cryptographic receipts, hand off conversations across Claude / ChatGPT / Cursor / Gemini, keep institutional AI knowledge when employees leave.

Last reviewed · 2 min read· Open spec· Changelog