7.9 KiB
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
public static bool IsVersioned(ReadOnlySpan<byte> 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:
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
public static string? ExtractVersion(ReadOnlySpan<byte> 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:
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
public static byte[] Canonicalize<T>(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:
var obj = new { z = 3, a = 1 };
byte[] canonical = CanonJson.Canonicalize(obj);
// Result: {"a":1,"z":3}
CanonicalizeVersioned
public static byte[] CanonicalizeVersioned<T>(T obj, string version = CanonVersion.Current)
Canonicalizes an object with a version marker for content-addressed hashing.
Parameters:
obj: The object to canonicalizeversion: Canonicalization version (default:CanonVersion.Current)
Returns: UTF-8 encoded canonical JSON bytes with version marker
Exceptions:
ArgumentNullException: Whenversionis nullArgumentException: Whenversionis empty
Example:
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
public static string Hash<T>(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:
var obj = new { foo = "bar" };
string hash = CanonJson.Hash(obj);
// Result: "7a38bf81f383f69433ad6e900d35b3e2385593f76a7b7ab5d4355b8ba41ee24b"
HashVersioned
public static string HashVersioned<T>(T obj, string version = CanonVersion.Current)
Computes SHA-256 hash of versioned canonical JSON.
Parameters:
obj: The object to hashversion: Canonicalization version (default:CanonVersion.Current)
Returns: Lowercase hex-encoded SHA-256 hash (64 characters)
Example:
var obj = new { foo = "bar" };
string hash = CanonJson.HashVersioned(obj);
// Different from legacy hash due to version marker
HashPrefixed
public static string HashPrefixed<T>(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:
var obj = new { foo = "bar" };
string hash = CanonJson.HashPrefixed(obj);
// Result: "sha256:7a38bf81f383f69433ad6e900d35b3e2385593f76a7b7ab5d4355b8ba41ee24b"
HashVersionedPrefixed
public static string HashVersionedPrefixed<T>(T obj, string version = CanonVersion.Current)
Computes SHA-256 hash with sha256: prefix (versioned format).
Parameters:
obj: The object to hashversion: Canonicalization version (default:CanonVersion.Current)
Returns: Hash in format sha256:<64-hex-chars>
Example:
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
byte[] Canonicalize(ReadOnlySpan<byte> 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
byte[] CanonicalizeWithVersion(ReadOnlySpan<byte> json, string version)
Canonicalizes UTF-8 JSON bytes with version marker prepended.
Parameters:
json: Raw UTF-8 JSON bytes to canonicalizeversion: Version string to embed
Returns: Canonical UTF-8 JSON bytes with _canonVersion field
Usage Examples
Computing Content-Addressed IDs
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
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
// 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:
- Underscore (
_) sorts before all letters in ASCII - After injecting version, remaining keys are sorted normally
{"_canonVersion":"stella:canon:v1","aaa":1,"bbb":2,"zzz":3}