{"algorithm":"Ed25519","headers":{"Ari-Canonical-Hash":"hex sha256 of the canonical payload bytes","Ari-Key-Id":"ari-<sha256(SPKI DER)[0..12]>","Ari-Receipt-Id":"ULID · short citable reference","Ari-Signature":"base64(ed25519(signing_input))","Ari-Signed-At":"RFC3339 server timestamp"},"name":"Ari Receipt Canonicalization v1","payloadCanonicalization":{"ariProfileNotes":["ARI signs JSON wire payloads only. The server never emits non-JSON-native input types (Date, Map, Set, typed array, RegExp, class instances) on `/api/*` responses; canonicalizing one of those throws.","Large-integer rule (uniform across the TypeScript signer/verifier and the Python verifier): integers whose absolute value is strictly greater than `Number.MAX_SAFE_INTEGER` (2^53 - 1 = 9007199254740991) MUST be encoded as JSON *strings* (the decimal digits, wrapped in JSON quotes). Within ±(2^53 - 1) they are encoded as JSON numbers. In TypeScript the `bigint` type is always encoded as a JSON string regardless of magnitude (which trivially satisfies the rule). In Python an `int` is checked at serialization time: in-range emits as a JSON number, out-of-range emits as a JSON string. Both implementations therefore produce byte-for-byte identical canonical bytes for the same logical integer value · see the cross-language test vectors in `tools/ari-verify/test/cross-lang-vectors.test.ts` and `tools/ari-verify-py/tests/test_cross_lang_vectors.py`.","Verifiers MUST sign over the wire bytes of an HTTP response, not over a re-parsed value, because re-parsing loses field order witnesses (which RFC 8785 makes irrelevant) AND number-format witnesses (which RFC 8785 fixes but other JSON encoders do not)."],"reference":"RFC 8785","rules":["Object keys sorted by UTF-16 code-unit order (RFC 8785 §3.2.3).","No insignificant whitespace.","JSON strings escaped per RFC 8259 §7 (no \\u escapes for non-control characters).","Numbers serialized via ECMA-262 ToString (matches JSON.stringify for finite values).","`-0` serialized as `0`. NaN/Infinity rejected.","`undefined` values dropped from objects (mirrors JSON.stringify)."],"scheme":"JCS"},"profile":"ari-receipts/v1","publicKey":{"currentFingerprint":"aedb:d75d:43c8:1ab2:d815:9736:6311:de97","currentKeyId":"ari-aedbd75d43c8","jsonUrl":"/.well-known/ari-pubkey.json","pemUrl":"/.well-known/ari-pubkey.pem","qrUrl":"/.well-known/ari-pubkey.svg"},"publishedAt":"2026-04-25","referenceImplementations":{"python":"https://pypi.org/project/ari-verify/","typescript":"https://www.npmjs.com/package/ari-verify"},"signingInput":{"description":"The bytes signed are: canonical JSON payload, then for each signed header (in fixed order, if present): \\n<Name>: <value>","headerLineSeparator":"\\n","headerNameValueSeparator":": ","headers":["License","Content-Type","Ari-Signed-At","Ari-Key-Id","Ari-Receipt-Id","Ari-Schedule-Proof"]},"summary":"Defines the byte-exact serialization signed by the Ari-Signature header. ARI canonicalization is the `ari-receipts/v1` profile of JCS (RFC 8785) · see `ariProfileNotes` for every place the profile extends or constrains strict RFC 8785. Independent verifiers can re-derive the signing input from a captured response.","verifierAlgorithm":["1. Read response body bytes verbatim. They are already canonical · do NOT re-parse and re-stringify with a non-JCS encoder.","2. signing_input = body","3. For each name in [License, Content-Type, Ari-Signed-At, Ari-Key-Id, Ari-Receipt-Id], if the header is present, append '\\n' + name + ': ' + value to signing_input.","4. ed25519_verify(base64_decode(Ari-Signature), signing_input, public_key_from(/.well-known/ari-pubkey.pem))","5. Optional: hex(sha256(body)) MUST equal Ari-Canonical-Hash."],"version":"1.0.0"}