Skip to content

Canonicalization

Protocol

Canonicalization

fiscal402.sorted-json/1 exists so independent systems can reproduce the exact signed payload.

Identifier

fiscal402.sorted-json/1. This is a Fiscal402 convention. It is not RFC 8785 JSON Canonicalization Scheme and is not an IANA standard.

Encoding

  • Character encoding: UTF-8.
  • Compact JSON: JSON.stringify after key-sort. No spaces after : or ,. No trailing newline.
  • Object keys sorted recursively with JavaScript string UTF-16 code unit order (Object.keys(obj).sort()).
  • Keys whose value is undefined are omitted. null is kept.
  • Array order is preserved. Elements are recursively canonicalized.
  • Numbers follow JavaScript JSON.stringify. Receipts do not emit NaN or Infinity. Monetary amounts are decimal strings. rate_percent is a JSON number.
  • Timestamps already on the record are not rewritten.

Signed payload

Hash every field except hashes and signature:

  • spec, spec_version, receipt_id, mode, issued_at, protocol_version, tax_ruleset_version
  • settlement, context, determination, fx, vies, artifacts, disclaimer

Algorithm

canonical

unsigned = receipt without hashes and signature
canonical = fiscal402.sorted-json/1(unsigned)
hashes.canonical_payload_sha256 = SHA-256(UTF-8 bytes of canonical)
signature.value = Ed25519(UTF-8 bytes of the hex digest string)

The Ed25519 input is the hex digest string, not the raw 32-byte hash.

Legacy receipts

Receipts without hashes.canonicalization === "fiscal402.sorted-json/1" must be reported as legacy-stored-hash. Tools must not claim those payloads were reproduced with this identifier.