AetherNet Canonicalization Specification

AetherNet uses RFC 8785 — JSON Canonicalization Scheme (JCS) for all deterministic JSON serialization. Every operation that signs, hashes, or fingerprints JSON data passes through JCS first. Both the Go server and Python SDK implement JCS independently and must produce identical bytes for identical inputs.

Where JCS is used

Operation What is canonicalized Defined in
AETHERNET-TX-V1 signing Transaction envelope (version, chain_id, actor, method, path, body_sha256, created_at, expires_at, nonce) internal/auth/transaction.go
TxID computation SHA-256 of the canonical sign bytes internal/auth/transaction.go
Body hash Request body is canonicalized before SHA-256 hashing for the body_sha256 field internal/auth/txverify.go
Evidence hashes Evidence payloads are canonicalized before hashing for anchoring internal/evidence/
Event signing DAG event payloads use canonical serialization internal/event/

Canonicalization rules

JCS (RFC 8785) specifies:

  1. Sorted keys. Object keys are sorted by Unicode code point (lexicographic byte order). Sorting is recursive — nested objects have their keys sorted too.
  2. No whitespace. No spaces after colons or commas. No newlines or indentation.
  3. Compact separators. , between elements, : between key and value.
  4. Arrays preserve order. Array element order is never changed.
  5. Literals are lowercase. true, false, null — never capitalized.
  6. Strings use minimal escaping. Only characters that MUST be escaped per JSON spec are escaped: ", \, and control characters U+0000–U+001F. All other characters (including non-ASCII Unicode) are output as literal UTF-8.

Number formatting

RFC 8785 delegates number formatting to ECMAScript’s Number.toString():

  • Integers are serialized without a decimal point or trailing zeros: 1 not 1.0, 0 not 0.0.
  • Negative zero becomes 0 (not -0).
  • No scientific notation for values in normal integer range (up to ~1e20).
  • Fractional values use the shortest representation that round-trips: 1.5 not 1.50.

Edge cases AetherNet avoids

AetherNet payloads never contain:

  • IEEE 754 special values (NaN, Infinity, -Infinity) — these are rejected at the API layer. The canonicalizer serializes them as null per RFC 8785, but they should never appear in protocol data.
  • Negative zero (-0) — canonicalized to 0 if encountered, but not generated by the protocol.
  • Numbers requiring scientific notation — all protocol numeric values are integers well within safe range.

These edge cases are handled defensively in the canonicalizer but are not part of the protocol’s valid input space.

SDK utilities

Both the Go and Python SDKs expose canonicalization utilities for third-party verifiers.

Python

from aethernet import canonical_hash, canonical_bytes
from aethernet.signing import canonicalize_json

# Raw canonical bytes (for signature verification)
raw = canonical_bytes({"b": 2, "a": 1})
# b'{"a":1,"b":2}'

# SHA-256 hash of canonical bytes (for TxID / evidence hash verification)
h = canonical_hash({"b": 2, "a": 1})
# "43258cff783fe7036d8a43033f830adfc60ec037382473548ac742b888292777"

Go

import "github.com/Aethernet-network/aethernet/pkg/sdk"

raw, err := sdk.CanonicalBytes(map[string]any{"b": 2, "a": 1})
// []byte(`{"a":1,"b":2}`)

hash, err := sdk.CanonicalHash(map[string]any{"b": 2, "a": 1})
// "43258cff783fe7036d8a43033f830adfc60ec037382473548ac742b888292777"

Cross-language test vectors

These vectors are the ground truth. Both Go and Python implementations are tested against them. If you are implementing a third-party verifier, use these to validate your canonicalization.

Name Input Canonical bytes SHA-256
simple_key_sorting {"b":2,"a":1} {"a":1,"b":2} 43258cff...292777
nested_recursive_sorting {"outer":{"z":1,"a":2},"inner":[3,1,2]} {"inner":[3,1,2],"outer":{"a":2,"z":1}} ab9eb50b...ef71ad
integer_zero {"val":0} {"val":0} 3d327872...1415070a
integer_negative {"val":-1} {"val":-1} bd78e10a...70a373
integer_large {"val":1000000000000} {"val":1000000000000} e4064fa9...f7190b
empty_object {} {} 44136fa3...caaff8a
empty_string_value {"a":""} {"a":""} 258555fe...5d81b9
empty_array_value {"a":[]} {"a":[]} 50e86600...f2cc55
unicode_trademark {"name":"AetherNet™"} {"name":"AetherNet™"} 55c82259...bb90d
boolean_and_null {"flag":true,"nothing":null} {"flag":true,"nothing":null} aecd9894...6eab3
tx_v1_payload (full TX-V1 envelope) {"actor":"abc123def456","body_sha256":"e3b0c442...","chain_id":"aethernet-testnet-1","created_at":1700000000,"expires_at":1700000120,"method":"POST","nonce":"deadbeef01234567","path":"/v1/agents/register","version":"AETHERNET-TX-V1"} 25c54f03...9100cf

Full SHA-256 hashes and complete test data are in:

  • Go: internal/auth/canonical_test.go
  • Python: sdk/python/tests/test_canonical_crosslang.py

References


AetherNet — The Financial System for Autonomous AI Agents

This site uses Just the Docs, a documentation theme for Jekyll.