77 lines
2.5 KiB
C#
77 lines
2.5 KiB
C#
// Licensed to StellaOps under the BUSL-1.1 license.
|
|
|
|
|
|
using Blake3;
|
|
using StellaOps.ReachGraph.Schema;
|
|
using StellaOps.ReachGraph.Serialization;
|
|
|
|
namespace StellaOps.ReachGraph.Hashing;
|
|
|
|
/// <summary>
|
|
/// Computes BLAKE3-256 digests for reachability graphs using canonical serialization.
|
|
/// </summary>
|
|
public sealed partial class ReachGraphDigestComputer
|
|
{
|
|
private readonly CanonicalReachGraphSerializer _serializer;
|
|
|
|
public ReachGraphDigestComputer()
|
|
: this(new CanonicalReachGraphSerializer())
|
|
{
|
|
}
|
|
|
|
public ReachGraphDigestComputer(CanonicalReachGraphSerializer serializer)
|
|
{
|
|
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compute BLAKE3-256 digest of canonical JSON (excluding signatures).
|
|
/// </summary>
|
|
/// <param name="graph">The reachability graph to hash.</param>
|
|
/// <returns>Digest in format "blake3:{hex}".</returns>
|
|
public string ComputeDigest(ReachGraphMinimal graph)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(graph);
|
|
|
|
// Remove signatures before hashing (avoid circular dependency)
|
|
var unsigned = graph with { Signatures = null };
|
|
var canonical = _serializer.SerializeMinimal(unsigned);
|
|
|
|
using var hasher = Hasher.New();
|
|
hasher.Update(canonical);
|
|
var hash = hasher.Finalize();
|
|
|
|
return $"blake3:{Convert.ToHexString(hash.AsSpan()).ToLowerInvariant()}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compute BLAKE3-256 digest from raw canonical JSON bytes.
|
|
/// </summary>
|
|
/// <param name="canonicalJson">The canonical JSON bytes to hash.</param>
|
|
/// <returns>Digest in format "blake3:{hex}".</returns>
|
|
public static string ComputeDigest(ReadOnlySpan<byte> canonicalJson)
|
|
{
|
|
using var hasher = Hasher.New();
|
|
hasher.Update(canonicalJson);
|
|
var hash = hasher.Finalize();
|
|
|
|
return $"blake3:{Convert.ToHexString(hash.AsSpan()).ToLowerInvariant()}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify digest matches graph content.
|
|
/// </summary>
|
|
/// <param name="graph">The reachability graph to verify.</param>
|
|
/// <param name="expectedDigest">The expected digest.</param>
|
|
/// <returns>True if digest matches, false otherwise.</returns>
|
|
public bool VerifyDigest(ReachGraphMinimal graph, string expectedDigest)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(graph);
|
|
ArgumentException.ThrowIfNullOrEmpty(expectedDigest);
|
|
|
|
var computed = ComputeDigest(graph);
|
|
return string.Equals(computed, expectedDigest, StringComparison.Ordinal);
|
|
}
|
|
|
|
}
|