use sha2::{Digest, Sha256 as Sha256Hasher};
use thiserror::Error;
use crate::canonical::{
canonicalize_method_shape, canonicalize_span_commitment, canonicalize_span_shape,
canonicalize_trace, canonicalize_trace_roots, CanonicalError,
};
use crate::types::{MethodShapeManifest, Sha256, SpanCommitment, SpanShape, Trace, TraceRoots};
use crate::validation::validate_sha256_format;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum HashError {
#[error("canonicalization error: {0}")]
Canonicalization(#[from] CanonicalError),
#[error("invalid hash format: {0}")]
InvalidFormat(String),
}
pub fn compute_sha256(canonical: &str) -> Sha256 {
let mut hasher = Sha256Hasher::new();
hasher.update(canonical.as_bytes());
let hash = hasher.finalize();
format!("sha256:{:x}", hash)
}
pub fn compute_span_shape_hash(shape: &SpanShape) -> Result<Sha256, HashError> {
let canonical = canonicalize_span_shape(shape)?;
Ok(compute_sha256(&canonical))
}
pub fn compute_method_shape_root(span_shapes: &[Sha256]) -> Result<Sha256, HashError> {
for (idx, hash) in span_shapes.iter().enumerate() {
validate_sha256_format(hash)
.map_err(|e| HashError::InvalidFormat(format!("span_shapes[{}]: {}", idx, e)))?;
}
let mut sorted = span_shapes.to_vec();
sorted.sort();
let joined = sorted.join("|");
Ok(compute_sha256(&joined))
}
pub fn compute_span_commitment_hash(commitment: &SpanCommitment) -> Result<Sha256, HashError> {
let canonical = canonicalize_span_commitment(commitment)?;
Ok(compute_sha256(&canonical))
}
pub fn compute_trace_set_root(span_hashes: &[Sha256]) -> Result<Sha256, HashError> {
for (idx, hash) in span_hashes.iter().enumerate() {
validate_sha256_format(hash)
.map_err(|e| HashError::InvalidFormat(format!("span_hashes[{}]: {}", idx, e)))?;
}
let mut sorted = span_hashes.to_vec();
sorted.sort();
let joined = sorted.join("|");
Ok(compute_sha256(&joined))
}
pub fn compute_identity_root(
trace_id: &str,
identity_route: &str,
span_hashes: &[Sha256],
) -> Result<Sha256, HashError> {
for (idx, hash) in span_hashes.iter().enumerate() {
validate_sha256_format(hash)
.map_err(|e| HashError::InvalidFormat(format!("span_hashes[{}]: {}", idx, e)))?;
}
let mut sorted_hashes = span_hashes.to_vec();
sorted_hashes.sort();
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert("trace_id", serde_json::Value::String(trace_id.to_string()));
map.insert(
"identity_route",
serde_json::Value::String(identity_route.to_string()),
);
map.insert(
"span_commitments",
serde_json::Value::Array(
sorted_hashes
.iter()
.map(|s| serde_json::Value::String(s.clone()))
.collect(),
),
);
let canonical = serde_json::to_string(&serde_json::Value::Object(
map.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
))
.map_err(|e| HashError::Canonicalization(CanonicalError::Serialization(e.to_string())))?;
Ok(compute_sha256(&canonical))
}
pub fn compute_trace_seq_root(span_hashes: &[Sha256]) -> Result<Sha256, HashError> {
for (idx, hash) in span_hashes.iter().enumerate() {
validate_sha256_format(hash)
.map_err(|e| HashError::InvalidFormat(format!("span_hashes[{}]: {}", idx, e)))?;
}
let joined = span_hashes.join("|");
Ok(compute_sha256(&joined))
}
pub fn compute_method_shape_hash(manifest: &MethodShapeManifest) -> Result<Sha256, HashError> {
let canonical = canonicalize_method_shape(manifest)?;
Ok(compute_sha256(&canonical))
}
pub fn compute_trace_hash(trace: &Trace) -> Result<Sha256, HashError> {
let canonical = canonicalize_trace(trace)?;
Ok(compute_sha256(&canonical))
}
pub fn compute_trace_roots_hash(roots: &TraceRoots) -> Result<Sha256, HashError> {
let canonical = canonicalize_trace_roots(roots)?;
Ok(compute_sha256(&canonical))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_sha256_format() {
let hash = compute_sha256("test");
assert!(hash.starts_with("sha256:"));
assert_eq!(hash.len(), 71); assert!(hash
.chars()
.skip(7)
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));
}
#[test]
fn test_compute_sha256_determinism() {
let hash1 = compute_sha256("test");
let hash2 = compute_sha256("test");
assert_eq!(hash1, hash2);
}
#[test]
fn test_compute_method_shape_root_sorted() {
let a =
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
let b =
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();
let c =
"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".to_string();
let r1 = compute_method_shape_root(&[b.clone(), a.clone()]).unwrap();
let r2 = compute_method_shape_root(&[a.clone(), b.clone()]).unwrap();
assert_eq!(r1, r2, "Order must not matter");
let r3 = compute_method_shape_root(&[a, b, c.clone()]).unwrap();
assert_ne!(r2, r3, "Superset must produce different root");
}
#[test]
fn test_compute_trace_set_root_sorted() {
let a =
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
let b =
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();
let r1 = compute_trace_set_root(&[b.clone(), a.clone()]).unwrap();
let r2 = compute_trace_set_root(&[a.clone(), b.clone()]).unwrap();
assert_eq!(r1, r2, "Order must not matter");
}
#[test]
fn test_compute_identity_root() {
let trace_id = "tr_01H1234567890";
let identity_route = "did:key:zTest";
let spans = vec![
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
];
let r1 = compute_identity_root(trace_id, identity_route, &spans).unwrap();
let r2 = compute_identity_root(trace_id, identity_route, &spans).unwrap();
assert_eq!(r1, r2, "Must be deterministic");
let r3 = compute_identity_root(trace_id, "did:key:zOther", &spans).unwrap();
assert_ne!(r1, r3, "Different identity must produce different root");
}
#[test]
fn test_compute_trace_seq_root_preserves_order() {
let a =
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
let b =
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();
let r1 = compute_trace_seq_root(&[a.clone(), b.clone()]).unwrap();
let r2 = compute_trace_seq_root(&[b.clone(), a.clone()]).unwrap();
assert_ne!(r1, r2, "Order must matter for sequence root");
}
}