Cross-Language Requirements
Cross-language conformance means independent implementations produce the same protocol-visible output from the same input. A one-byte difference in canonical output changes the resulting hash, signature digest, or Merkle root hash, so matching data models is not enough.
How Conformance Is Checked
Every implementation runs the 15 required core suites, plus the suites of each claimed extension, and its outputs must match the expected vectors. pnpm run verify:cross-language also runs the TypeScript, Python, and Rust implementations on the same cases and compares every pair of results. Run Conformance Tests describes both commands and the interface for other implementations.
Common Divergences
Standard JSON libraries do not produce ALX canonical bytes by default. Each row shows a value where Python's json.dumps defaults differ from the canonical output. The last row differs because ALX sorts keys by UTF-16 code units, and most languages sort strings by code point. The two orders disagree when one key contains a character above U+FFFF and another contains a character between U+E000 and U+FFFF.
| Value | ALX canonical bytes | Python json.dumps default |
|---|---|---|
1e-7 | 1e-7 | 1e-07 |
1.0 | 1 | 1.0 |
{"name": "café"} | {"name":"café"} | {"name": "caf\u00e9"} |
{"a": 1, "b": [1, 2]} | {"a":1,"b":[1,2]} | {"a": 1, "b": [1, 2]} |
Keys fi (U+FB01) and 😀 (U+1F600) | 😀 sorts first | With sort_keys=True, fi sorts first |
When to Run These Checks
Run the vectors when porting ALX to another language, changing serialization code, upgrading cryptographic dependencies, or reviewing interoperability.
- JSON and Number Behavior
- Hash and Parent Behavior
- Merkle Behavior
- Signing Extension
Ports must reproduce UTF-16 key sorting, JavaScript number rendering, the 128-level depth limit, and rejection of values outside the ALX value model.
Independent implementations must match:
- UTF-8 output without Unicode normalization;
- JavaScript UTF-16 code-unit object-key ordering;
- the shortest round-trip decimal form of JavaScript
JSON.stringify(), with integer-valued numbers written as integers; - rejection of
undefined, including sparse array elements and object properties whose value isundefined; and - the 128-level depth limit and the other rejected types.
Ports must use Keccak-256 and reproduce lowercase hash and parent normalization exactly.
Independent implementations must match:
- Keccak-256 rather than standardized SHA3-256;
- the
ALX-CONTENT-V1\0andALX-BLOCK-V1\0domain tags, each ending in one zero byte, before the canonical bytes; - lowercase
0x-prefixed 32-byte hashes; - trim, lowercase, validation, deduplication, and sorting of parent hashes; and
- rejection of more than 256 distinct parent hashes after deduplication.
Ports that claim the Merkle extension must reproduce sorted-pair hashing and odd-leaf promotion.
Ports sort the normalized leaf hashes, order each pair by raw 32-byte value, hash the 64-byte concatenation with Keccak-256 and no prefix, length, or domain byte, and promote an unpaired value to the next level unchanged. Merkle Trees lists every step. Merkle root hash and proof outputs must match byte for byte.
Ports that claim the signing extension must reproduce the portable attestation digest.
The digest is keccak256(UTF8("ALX-ATTESTATION-V1\0") || canonicalize({ algorithm, attester, blockHash, timestamp })). Ed25519 signs the digest as pure Ed25519 defined by RFC 8032. P-256 signs the digest as the ECDSA prehash and uses low-S, 64-byte compact r || s signatures. EIP-712 is an optional TypeScript backend outside portable signing-extension conformance and has no cross-language vectors.