# StellaOps.Canonical.Json Canonical JSON serialization with deterministic hashing for StellaOps proofs. ## Overview This library provides canonical JSON serialization that produces bit-identical output across different environments, enabling deterministic replay and cryptographic verification of score proofs. ## Key Features - **Deterministic Output**: Object keys are recursively sorted using Ordinal comparison - **No Whitespace**: Compact output with no formatting variations - **Consistent Hashing**: SHA-256 hashes are always lowercase hex - **Cross-Platform**: Same output across Windows, Linux, containers - **Stable Defaults**: Default serialization uses camelCase naming and UnsafeRelaxed JSON escaping (override with custom options) ## Usage ### Basic Canonicalization ```csharp using StellaOps.Canonical.Json; var obj = new { z = 3, a = 1, nested = new { b = 2, x = 1 } }; // Get canonical bytes byte[] canonical = CanonJson.Canonicalize(obj); // Result: {"a":1,"nested":{"b":2,"x":1},"z":3} // Compute hash string hash = CanonJson.Sha256Hex(canonical); // Result: lowercase 64-char hex string ``` ### One-Step Hash ```csharp // Hash object directly string hash = CanonJson.Hash(obj); // With sha256: prefix string prefixed = CanonJson.HashPrefixed(obj); // Result: "sha256:a1b2c3..." ``` ### Canonicalizing Existing JSON ```csharp // Re-sort keys in existing JSON byte[] rawJson = Encoding.UTF8.GetBytes(@"{""z"":1,""a"":2}"); byte[] canonical = CanonJson.CanonicalizeParsedJson(rawJson); // Result: {"a":2,"z":1} ``` If you need stricter escaping rules for raw JSON, pass a custom encoder: ```csharp byte[] canonical = CanonJson.CanonicalizeParsedJson(rawJson, JavaScriptEncoder.Default); ``` ### Custom Serialization Options ```csharp var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; byte[] canonical = CanonJson.Canonicalize(obj, options); ``` Notes: - Default naming policy is `JsonNamingPolicy.CamelCase` for the object-to-JSON step. - Default encoder is `JavaScriptEncoder.UnsafeRelaxedJsonEscaping` for canonical output. ## API Reference | Method | Description | |--------|-------------| | `Canonicalize(obj)` | Serialize and canonicalize an object | | `Canonicalize(obj, options)` | Serialize with custom options and canonicalize | | `CanonicalizeParsedJson(bytes)` | Canonicalize existing JSON bytes | | `CanonicalizeParsedJson(bytes, encoder)` | Canonicalize existing JSON with a custom encoder | | `Sha256Hex(bytes)` | Compute SHA-256, return lowercase hex | | `Sha256Prefixed(bytes)` | Compute SHA-256 with "sha256:" prefix | | `Hash(obj)` | Canonicalize and hash in one step | | `HashPrefixed(obj)` | Canonicalize and hash with prefix | ## Guarantees 1. **Key Ordering**: Object keys are always sorted alphabetically (Ordinal) 2. **No Environment Dependencies**: No timestamps, random values, or environment variables 3. **UTF-8 Without BOM**: Output is always UTF-8 encoded without byte order mark 4. **Array Order Preserved**: Arrays maintain element order (only object keys are sorted) ## Use Cases - **Scan Manifests**: Hash all inputs affecting scan results - **DSSE Payloads**: Sign canonical JSON for attestations - **Proof Replay**: Verify scores are deterministic - **Content Addressing**: Store proofs by their hash ## Related Components - `StellaOps.Scanner.Core.Models.ScanManifest` - Uses CanonJson for manifest hashing - `StellaOps.Attestor` - Signs canonical JSON payloads - `StellaOps.Evidence.Bundle` - Content-addressed proof storage