Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions tests/cross_impl/canonical-bytes-jcs-vectors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"version": "v1",
"spec": "RFC 8785 JSON Canonicalization Scheme (JCS): canonical bytes and SHA-256 of the canonical form",
"spec_ref": "RFC 8785; agent-passport-system canonicalizeJCS (src/core/canonical-jcs.ts)",
"generated_at": "2026-07-10",
"provenance": "Generated from the agent-passport-system canonicalizeJCS reference (npm 3.3.1) and verified by the suite runner's vendored RFC 8785 canonicalizer, proving byte-parity between the reference and the standalone runner.",
"vectors": [
{
"name": "float-tenth",
"description": "0.1 has no exact binary representation; ECMAScript Number::toString (RFC 8785) emits the shortest round-trip decimal.",
"input": {
"value": 0.1
},
"canonical": "{\"value\":0.1}",
"canonical_bytes_hex": "7b2276616c7565223a302e317d",
"canonical_sha256": "097a678e9d1339735cc744964f1c2b1271b2e650d7c59c72d2ff1c93064ceb6e"
},
{
"name": "float-1e21-boundary",
"description": "1e21 is the ECMAScript threshold at which Number::toString switches to exponential notation (\"1e+21\").",
"input": {
"value": 1e+21
},
"canonical": "{\"value\":1e+21}",
"canonical_bytes_hex": "7b2276616c7565223a31652b32317d",
"canonical_sha256": "776bf0d292f3767fdfca3fdc9683d0ba51886b8e6e79cd228a41bafcfa9a1a6f"
},
{
"name": "negative-zero",
"description": "JSON cannot carry a distinct -0 (it parses to 0); JCS renders both as \"0\". This pins that both map to the same bytes.",
"input": {
"value": 0
},
"canonical": "{\"value\":0}",
"canonical_bytes_hex": "7b2276616c7565223a307d",
"canonical_sha256": "23d7b286bd429460b92a2a1c21b6afc34110446c5034c17363fda363aa0a7c5d"
},
{
"name": "integer-above-2pow53",
"description": "9007199254740994 (2^53 + 2) is exactly representable as an IEEE 754 double and serializes as a plain integer, not exponential.",
"input": {
"value": 9007199254740994
},
"canonical": "{\"value\":9007199254740994}",
"canonical_bytes_hex": "7b2276616c7565223a393030373139393235343734303939347d",
"canonical_sha256": "ab21889a153140e1e3f9d33a98584898dad088dd462faa45c36b5686cf76aee3"
},
{
"name": "small-exponent-vs-decimal",
"description": "Per ECMAScript, 1e-7 stays exponential (\"1e-7\") while 1e-6 becomes decimal (\"0.000001\"). Object keys are also sorted (dec before exp).",
"input": {
"exp": 1e-7,
"dec": 0.000001
},
"canonical": "{\"dec\":0.000001,\"exp\":1e-7}",
"canonical_bytes_hex": "7b22646563223a302e3030303030312c22657870223a31652d377d",
"canonical_sha256": "4ecf258561293513519a28337ef76c2ebb21c28cbcb13cf2def91616edab7de1"
},
{
"name": "astral-key-ordering",
"description": "Keys sort by UTF-16 code unit, not code point: the astral-plane key U+1D306 (surrogate lead unit 0xD834) sorts BEFORE the BMP key U+FF61 (0xFF61), because 0xD834 < 0xFF61.",
"input": {
"𝌆": 2,
"。": 1
},
"canonical": "{\"𝌆\":2,\"。\":1}",
"canonical_bytes_hex": "7b22f09d8c86223a322c22efbda1223a317d",
"canonical_sha256": "0ed25e94bd02f7ca63a6019a9671e12af3ce4b0ed0233c786ea424c9dd322047"
},
{
"name": "nfd-key-used-as-given",
"description": "JCS sorts and emits object keys exactly as given and does NOT Unicode-normalize keys. This key is NFD (e + U+0301 combining acute) and is preserved as NFD, distinct from its NFC form.",
"input": {
"café": 1,
"plain": 2
},
"canonical": "{\"café\":1,\"plain\":2}",
"canonical_bytes_hex": "7b2263616665cc81223a312c22706c61696e223a327d",
"canonical_sha256": "05ba6cbd8fd6ef6e0b64b4cb3d907bb9521369d0f50cb31d55e002460eef77df"
},
{
"name": "nested-object-and-array",
"description": "Nested object keys sort recursively; array element order is always preserved (arrays are never reordered).",
"input": {
"outer": {
"b": 2,
"a": 1
},
"arr": [
3,
1,
2
]
},
"canonical": "{\"arr\":[3,1,2],\"outer\":{\"a\":1,\"b\":2}}",
"canonical_bytes_hex": "7b22617272223a5b332c312c325d2c226f75746572223a7b2261223a312c2262223a327d7d",
"canonical_sha256": "be6fc370ca7efc25ae4b5ee7736507cbbac8fbc8278cb3371d1b8020a2bd0ca3"
}
]
}
29 changes: 29 additions & 0 deletions tests/test_canonical_bytes_vectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (c) 2026 Tymofii Pidlisnyi
# SPDX-License-Identifier: Apache-2.0
"""Loader parity test for the shared canonical-bytes JCS vectors.

The vectors are generated from the TS SDK canonicalizeJCS reference. This test
proves the Python canonicalize_jcs produces byte-identical canonical output and
SHA-256 for each, i.e. cross-language JCS parity on the byte-contract cases.
"""

import hashlib
import json
from pathlib import Path

from agent_passport.canonical import canonicalize_jcs

VECTORS = json.loads(
(Path(__file__).parent / "cross_impl" / "canonical-bytes-jcs-vectors.json").read_text(
encoding="utf-8"
)
)["vectors"]


def test_canonical_bytes_jcs_parity():
assert len(VECTORS) == 8
for v in VECTORS:
canon = canonicalize_jcs(v["input"])
assert canon == v["canonical"], f"{v['name']}: {canon!r} != {v['canonical']!r}"
assert canon.encode("utf-8").hex() == v["canonical_bytes_hex"], v["name"]
assert hashlib.sha256(canon.encode("utf-8")).hexdigest() == v["canonical_sha256"], v["name"]
Loading