Deterministic Canonicalization & Hashing Specification

Technical Standard: RFC 8785 (JSON Canonicalization Scheme)
License: Apache License 2.0

1. The Core Problem: Semantic Equivalence vs. Cryptographic Variance

ciaf-json-v1 adopts RFC 8785 JSON Canonicalization Scheme (JCS) without modification. Evidence payloads are validated against their applicable schema, canonicalized as RFC 8785 JCS UTF-8 bytes, hashed with SHA-256, and signed with Ed25519. The signed payload excludes the integrity envelope containing content_hash, signature, and related metadata. Optional-field semantics, line-ending treatment, and domain-specific normalization rules are defined by the relevant evidence schema before canonicalization; they are not performed by the canonicalizer itself.

In the AI Governance Evidence Infrastructure (AGEI) architecture, our core technical claim is "Proof, Not Logs." This requires that every receipt, policy version, gate evaluation, and validation record is cryptographically signed and chained.

However, data interchange formats—specifically JSON—are structurally flexible. The exact same semantic data can be represented as binary byte-streams that are radically different. A simple change in dictionary key order, insignificant whitespace, float notations, string escaping, or line-endings will yield entirely different SHA-256 content hashes.

Payload A (Raw telemetry):

{
  "receipt_id": "rcpt_123",
  "status": "APPROVED",
  "metrics": { "accuracy": 0.99, "latency_ms": 12.0 }
}
SHA-256: 7f83b1...

Payload B (Semantically identical, but formatted differently):

{"status":"APPROVED","receipt_id":"rcpt_123","metrics":{"latency_ms":12,"accuracy":0.99}}
SHA-256: 3c92fa...

Without a deterministic Canonicalization Protocol, a verifier or auditor checking a receipt cannot recompute the hash or validate the digital signature. This breaks independent verifiability and portability across organizational boundaries.

The Cognitive Insight Audit Framework (CIAF) enforces strict canonicalization rules using the field canonicalization_version. This document defines the engineering standards for canonicalizing, hashing, and signing evidence objects to ensure consistent multi-platform verification.


2. The CIAF Canonicalization Protocol (ciaf-json-v1)

The standard canonicalization format for JSON-based payloads in CIAF is ciaf-json-v1, which strictly adopts RFC 8785 (JSON Canonicalization Scheme - JCS). When serializing any receipt, metadata envelope, or evidence payload for hashing or signing, implementations must enforce the following rules:

2.1 Lexical Key Sorting

  • All keys within an object must be sorted in ascending order.
  • Sorting is based on the UTF-16 code units of the keys.
  • For nested objects, sorting must be applied recursively at every level of the hierarchy.

2.2 Whitespace Minimization

  • All insignificant whitespace must be stripped.
  • No spaces, tabs, or carriage returns are permitted between keys, values, colons, or commas.
  • The output must be a single, contiguous line of text.

2.3 String and Character Escaping

  • String values must be encoded in UTF-8.
  • Strings MUST be valid Unicode and MUST be preserved exactly as received by the application’s JSON data model. ciaf-json-v1 applies RFC 8785 JCS and does not apply Unicode normalization. Invalid Unicode, including lone surrogate code points, MUST cause canonicalization to fail.
  • Only essential control characters (such as \n, \r, \t, \", and \\) must be escaped using their standard backslash sequences.
  • Forward slashes (/) must not be escaped.

2.4 Numeric Representation

  • Numbers MUST be serialized using the ECMAScript-compatible IEEE 754 double-precision serialization required by RFC 8785. Implementations MUST reject NaN and Infinity. Implementations MUST NOT substitute a custom exponent format, decimal formatter, or locale-sensitive serializer.
  • For the procurement or treasury scenario, do not use JSON floats for money. Use integer values for minor units (e.g. "amount_minor": 15000000) or decimal strings (e.g. "amount_decimal": "150000.00") depending on the safe integer constraints of the implementation profile.

2.5 Boolean, Null, and Omitted Optional Fields

  • Booleans must be represented as lowercase literal true or false.
  • Nulls must be lowercase literal null.
  • Schema authors MUST specify whether optional fields are omitted or may be represented as null. Producers MUST construct the intended semantic object before canonicalization. Canonicalization MUST NOT add, remove, default, coerce, or otherwise alter fields.

3. Language-Specific Pitfalls & Code Variations

Implementations written in different languages handle JSON serialization in subtly distinct ways by default. This section highlights the "Danger Zones" where standard library serialization will cause verification failures.

3.1 Python (json standard library vs. JCS)

The default behavior of Python’s json.dumps() includes whitespace and preserves dict insertion order (or arbitrary hashing order in older versions).

❌ Incorrect Python Serialization:

import json
payload = {"status": "APPROVED", "receipt_id": "rcpt_123"}
# Yields: '{"status": "APPROVED", "receipt_id": "rcpt_123"}' (Key order preserved, includes spaces)
serialized = json.dumps(payload)

✅ Correct RFC 8785 Compliant Python Serialization:

Implementations MUST use an RFC 8785-conformant canonicalization library validated against RFC 8785 test vectors.

import json
import jcs # Recommended open-source library

payload = {"status": "APPROVED", "receipt_id": "rcpt_123"}
canonicalized = jcs.canonicalize(payload)

Standard-library JSON serialization (e.g. json.dumps()) may be used only in tests or tightly constrained environments and MUST NOT be represented as fully JCS-compliant without interoperability validation.

3.2 JavaScript / Node.js

Standard JSON.stringify() does not sort object keys and can vary depending on engine-specific property ordering optimization rules.

❌ Incorrect JavaScript Serialization:

const payload = { status: "APPROVED", receipt_id: "rcpt_123" };
const serialized = JSON.stringify(payload); // Yields raw string with original property order

✅ Correct JavaScript JCS Serialization:

Always utilize a verified canonicalization package like canonicalize or fast-json-stable-stringify:

const canonicalize = require('canonicalize'); // RFC 8785 compliant
const payload = { status: "APPROVED", receipt_id: "rcpt_123" };
const canonical_bytes = Buffer.from(canonicalize(payload), 'utf8');

3.3 Go (Golang)

While the standard json.Marshal() sorts map keys alphabetically, it does not automatically apply UTF-8 NFC normalization or enforce the JCS float-to-string conversion algorithm.

❌ Incorrect Go Serialization:

import "encoding/json"
// Marshalling raw structs preserves struct field definition order, not lexical order!
bytes, _ := json.Marshal(payloadStruct)

✅ Correct Go Serialization:

Utilize the open-source github.com/gowebpki/jcs library to marshal maps or structs deterministically:

import "github.com/gowebpki/jcs"

// Canonicalize directly into bytes
canonical_bytes, err := jcs.Transform(rawJSONBytes)

3.4 Database Engines (PostgreSQL jsonb Warning)

PostgreSQL jsonb stores data in a parsed binary format that strips insignificant whitespace and deduplicates keys. However, the internal database ordering of keys is based on a binary hash-key representation, which is not alphabetically sorted and differs from RFC 8785.

⚠️ Database Hash Danger:

Executing SELECT json_col::text or converting jsonb to a text string inside the database will result in a string formatted according to PostgreSQL's internal engine layout. Computing a hash over this output will fail validation in external Python, Go, or JS microservices.

✅ Database Guardrail:

Never compute or verify cryptographic hashes directly over text-cast database JSON columns. Always retrieve the raw JSON data, decode it into your application logic, and apply the language-specific canonicalizer library (e.g., jcs in Python) before hashing or signing.

3.5 Platform Line-Endings

If an evidence payload includes large text blobs (such as LLM prompts, output summaries, or dataset CSV headers):

  • Windows runtimes write line-endings as CRLF (\r\n).
  • Unix/Linux runtimes write line-endings as LF (\n).
  • For fields declared as normalized text content, producers MUST normalize CRLF and CR line endings to LF before constructing the signed payload. This transformation is an application-level semantic rule, not part of RFC 8785 canonicalization.

4. Architectural Hashing & Signing Standards

To enforce consistent cryptographic strength across the entire open-source ecosystem, cognitiveinsight.ai establishes the following cryptographical standards:

CriterionStandardEnforcement Posture
Content HashingSHA-256Mandatory for all content_hash, receipt_hash, and Merkle tree calculations.
Digital SignaturesEd25519 (RFC 8032)Mandatory for all externally verifiable receipts, vault objects, and audit pack exports.
Symmetric IntegrityHMAC-SHA256Optional; strictly reserved for internal-only session token integrity where third-party verification is not required.
Key Identitykey_idRequired on all signed records to bind the signature to a rotation-friendly key registry.
Format Trackingcanonicalization_versionRequired in the metadata envelope of all receipts, proving compliant serialization.

5. Schema Integration Pattern

Do not hash an object containing its own content_hash and signature. Define an unsigned payload and an integrity envelope.

{
  "receipt": {
    "receipt_id": "018f4d15-...",
    "receipt_type": "tool_execution",
    "organization_id": "018f4d16-...",
    "policy_version_id": "018f4d17-...",
    "gate_evaluation_id": "018f4d18-...",
    "created_at": "2026-08-13T15:49:47Z",
    "outcome": "APPROVED",
    "action": {
      "tool": "submit_purchase_request",
      "target_resource": "procurement-api-sandbox",
      "parameter_hash": "..."
    }
  },
  "integrity": {
    "canonicalization_version": "ciaf-json-v1",
    "hash_algorithm": "SHA-256",
    "content_hash": "...",
    "signature_algorithm": "Ed25519",
    "key_id": "agei-demo-ed25519-2026-01",
    "signature": "base64url-encoded-signature"
  }
}

Adding Domain Separation:

Use a stable domain separator when hashing or signing so a receipt cannot be confused with a policy document, proof bundle, or audit manifest that happens to have the same JSON payload. Hash and sign those domain-separated byte strings:

AGEI:receipt:ciaf-json-v1\0 || canonical_receipt_bytes
AGEI:policy-version:ciaf-json-v1\0 || canonical_policy_bytes
AGEI:proof-bundle:ciaf-json-v1\0 || canonical_proof_bytes
AGEI:audit-manifest:ciaf-json-v1\0 || canonical_manifest_bytes

6. How Verifiers Reconstruct the Truth

An independent examiner (or automated verification script) validates an audit pack by performing the following steps precisely:

  1. Parse the envelope.
  2. Validate integrity metadata.
  3. Validate the receipt schema.
  4. Canonicalize receipt using the declared canonicalization_version.
  5. Recompute SHA-256 and compare it with content_hash.
  6. Resolve the public key using key_id.
  7. Verify the Ed25519 signature over the same canonical receipt bytes.
  8. Reject the artifact if any step fails.