# CanonJson API Reference **Namespace**: `StellaOps.Canonical.Json` **Assembly**: `StellaOps.Canonical.Json` **Version**: 1.0.0 --- ## Overview The `CanonJson` class provides RFC 8785-compliant JSON canonicalization and cryptographic hashing utilities for content-addressed identifiers. It ensures deterministic, reproducible JSON serialization across all environments. --- ## CanonVersion Class Static class containing canonicalization version constants and utilities. ### Constants | Constant | Type | Value | Description | |----------|------|-------|-------------| | `V1` | `string` | `"stella:canon:v1"` | Version 1: RFC 8785 JSON canonicalization | | `VersionFieldName` | `string` | `"_canonVersion"` | Field name for version marker (underscore ensures first position) | | `Current` | `string` | `V1` | Current default version for new hashes | ### Methods #### IsVersioned ```csharp public static bool IsVersioned(ReadOnlySpan canonicalJson) ``` Detects if canonical JSON includes a version marker. **Parameters:** - `canonicalJson`: UTF-8 encoded canonical JSON bytes **Returns:** `true` if the JSON starts with `{"_canonVersion":`, `false` otherwise **Example:** ```csharp var json = """{"_canonVersion":"stella:canon:v1","foo":"bar"}"""u8; bool versioned = CanonVersion.IsVersioned(json); // true var legacy = """{"foo":"bar"}"""u8; bool legacyVersioned = CanonVersion.IsVersioned(legacy); // false ``` --- #### ExtractVersion ```csharp public static string? ExtractVersion(ReadOnlySpan canonicalJson) ``` Extracts the version string from versioned canonical JSON. **Parameters:** - `canonicalJson`: UTF-8 encoded canonical JSON bytes **Returns:** The version string (e.g., `"stella:canon:v1"`) or `null` if not versioned **Example:** ```csharp var json = """{"_canonVersion":"stella:canon:v1","foo":"bar"}"""u8; string? version = CanonVersion.ExtractVersion(json); // "stella:canon:v1" ``` --- ## CanonJson Class Static class providing JSON canonicalization and hashing methods. ### Canonicalization Methods #### Canonicalize ```csharp public static byte[] Canonicalize(T obj) ``` Canonicalizes an object to RFC 8785 JSON without version marker (legacy format). **Parameters:** - `obj`: The object to canonicalize **Returns:** UTF-8 encoded canonical JSON bytes **Example:** ```csharp var obj = new { z = 3, a = 1 }; byte[] canonical = CanonJson.Canonicalize(obj); // Result: {"a":1,"z":3} ``` --- #### CanonicalizeVersioned ```csharp public static byte[] CanonicalizeVersioned(T obj, string version = CanonVersion.Current) ``` Canonicalizes an object with a version marker for content-addressed hashing. **Parameters:** - `obj`: The object to canonicalize - `version`: Canonicalization version (default: `CanonVersion.Current`) **Returns:** UTF-8 encoded canonical JSON bytes with version marker **Exceptions:** - `ArgumentNullException`: When `version` is null - `ArgumentException`: When `version` is empty **Example:** ```csharp var obj = new { z = 3, a = 1 }; byte[] canonical = CanonJson.CanonicalizeVersioned(obj); // Result: {"_canonVersion":"stella:canon:v1","a":1,"z":3} // With explicit version byte[] v2 = CanonJson.CanonicalizeVersioned(obj, "stella:canon:v2"); // Result: {"_canonVersion":"stella:canon:v2","a":1,"z":3} ``` --- ### Hashing Methods #### Hash ```csharp public static string Hash(T obj) ``` Computes SHA-256 hash of canonical JSON (legacy format, no version marker). **Parameters:** - `obj`: The object to hash **Returns:** Lowercase hex-encoded SHA-256 hash (64 characters) **Example:** ```csharp var obj = new { foo = "bar" }; string hash = CanonJson.Hash(obj); // Result: "7a38bf81f383f69433ad6e900d35b3e2385593f76a7b7ab5d4355b8ba41ee24b" ``` --- #### HashVersioned ```csharp public static string HashVersioned(T obj, string version = CanonVersion.Current) ``` Computes SHA-256 hash of versioned canonical JSON. **Parameters:** - `obj`: The object to hash - `version`: Canonicalization version (default: `CanonVersion.Current`) **Returns:** Lowercase hex-encoded SHA-256 hash (64 characters) **Example:** ```csharp var obj = new { foo = "bar" }; string hash = CanonJson.HashVersioned(obj); // Different from legacy hash due to version marker ``` --- #### HashPrefixed ```csharp public static string HashPrefixed(T obj) ``` Computes SHA-256 hash with `sha256:` prefix (legacy format). **Parameters:** - `obj`: The object to hash **Returns:** Hash in format `sha256:<64-hex-chars>` **Example:** ```csharp var obj = new { foo = "bar" }; string hash = CanonJson.HashPrefixed(obj); // Result: "sha256:7a38bf81f383f69433ad6e900d35b3e2385593f76a7b7ab5d4355b8ba41ee24b" ``` --- #### HashVersionedPrefixed ```csharp public static string HashVersionedPrefixed(T obj, string version = CanonVersion.Current) ``` Computes SHA-256 hash with `sha256:` prefix (versioned format). **Parameters:** - `obj`: The object to hash - `version`: Canonicalization version (default: `CanonVersion.Current`) **Returns:** Hash in format `sha256:<64-hex-chars>` **Example:** ```csharp var obj = new { foo = "bar" }; string hash = CanonJson.HashVersionedPrefixed(obj); // Result: "sha256:..." (different from HashPrefixed due to version marker) ``` --- ## IJsonCanonicalizer Interface Interface for JSON canonicalization implementations. ### Methods #### Canonicalize ```csharp byte[] Canonicalize(ReadOnlySpan json) ``` Canonicalizes UTF-8 JSON bytes per RFC 8785. **Parameters:** - `json`: Raw UTF-8 JSON bytes to canonicalize **Returns:** Canonical UTF-8 JSON bytes --- #### CanonicalizeWithVersion ```csharp byte[] CanonicalizeWithVersion(ReadOnlySpan json, string version) ``` Canonicalizes UTF-8 JSON bytes with version marker prepended. **Parameters:** - `json`: Raw UTF-8 JSON bytes to canonicalize - `version`: Version string to embed **Returns:** Canonical UTF-8 JSON bytes with `_canonVersion` field --- ## Usage Examples ### Computing Content-Addressed IDs ```csharp using StellaOps.Canonical.Json; // Evidence predicate hashing var evidence = new EvidencePredicate { Source = "scanner/trivy", SbomEntryId = "sha256:91f2ab3c:pkg:npm/lodash@4.17.21", VulnerabilityId = "CVE-2021-23337" }; // Compute versioned hash (recommended) string evidenceId = CanonJson.HashVersionedPrefixed(evidence); // Result: "sha256:..." ``` ### Verifying Attestations ```csharp public bool VerifyAttestation(byte[] payload, string expectedHash) { // Detect format and verify accordingly if (CanonVersion.IsVersioned(payload)) { var version = CanonVersion.ExtractVersion(payload); // Re-canonicalize with same version and compare var computed = CanonJson.HashVersioned(payload, version!); return computed == expectedHash; } // Legacy format var legacyHash = CanonJson.Hash(payload); return legacyHash == expectedHash; } ``` ### Migration from Legacy to Versioned ```csharp // Old code (legacy) var hash = CanonJson.Hash(predicate); // New code (versioned) - just add "Versioned" var hash = CanonJson.HashVersioned(predicate); ``` --- ## Algorithm Details ### RFC 8785 Compliance | Requirement | Implementation | |-------------|----------------| | Key ordering | Ordinal string comparison (case-sensitive, ASCII) | | Number format | IEEE 754, shortest representation | | String escaping | Minimal (only `"`, `\`, control characters) | | Whitespace | None (compact output) | | Encoding | UTF-8 without BOM | ### Version Marker Position The `_canonVersion` field is **always first** in the output due to: 1. Underscore (`_`) sorts before all letters in ASCII 2. After injecting version, remaining keys are sorted normally ```json {"_canonVersion":"stella:canon:v1","aaa":1,"bbb":2,"zzz":3} ``` --- ## Related Documentation - [Proof Chain Specification](../modules/attestor/proof-chain-specification.md) - [Canonicalization Migration Guide](../operations/canon-version-migration.md) - [RFC 8785 - JSON Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785)