using System.Security.Cryptography; using System.Text; namespace StellaOps.Evidence.Bundle; /// Content-addressed hash set for all evidence artifacts. public sealed class EvidenceHashSet { public string Algorithm { get; init; } = "SHA-256"; public required IReadOnlyList Hashes { get; init; } public required string CombinedHash { get; init; } public IReadOnlyDictionary? LabeledHashes { get; init; } public static EvidenceHashSet Compute(IDictionary labeledHashes) { ArgumentNullException.ThrowIfNull(labeledHashes); var sorted = labeledHashes.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).ToList(); var combined = string.Join(":", sorted.Select(kvp => $"{kvp.Key}={kvp.Value}")); var hash = ComputeSha256(combined); return new EvidenceHashSet { Hashes = sorted.Select(kvp => kvp.Value).ToList(), CombinedHash = hash, LabeledHashes = sorted.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) }; } public static EvidenceHashSet Empty() => new() { Hashes = Array.Empty(), CombinedHash = ComputeSha256(string.Empty), LabeledHashes = new Dictionary() }; private static string ComputeSha256(string input) => Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input))).ToLowerInvariant(); }