khive_types/hash.rs
1//! 256-bit content hash for checkpoint integrity verification.
2//!
3//! # Formal proof reference
4//!
5//! `proofs/Retrieval/HNSW.lean` — hash identity used in checkpoint
6//! compatibility checks (khive.Retrieval.HNSW.checkpoint_correctness).
7
8use core::fmt;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// 256-bit (32-byte) content hash.
14///
15/// Used as a content-addressed identifier for HNSW checkpoints and other
16/// snapshot artifacts. The underlying algorithm is caller-defined; the type
17/// carries the raw bytes without encoding assumptions.
18#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20#[cfg_attr(feature = "serde", serde(transparent))]
21pub struct Hash32([u8; 32]);
22
23impl Hash32 {
24 /// Zero hash (nil value).
25 pub const ZERO: Self = Self([0u8; 32]);
26
27 /// Construct from raw bytes.
28 #[inline]
29 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
30 Self(bytes)
31 }
32
33 /// Return the raw byte representation.
34 #[inline]
35 pub const fn as_bytes(&self) -> &[u8; 32] {
36 &self.0
37 }
38
39 /// Compute a BLAKE3 hash over the given byte slice.
40 ///
41 /// Requires the `blake3` feature.
42 #[cfg(feature = "blake3")]
43 #[inline]
44 pub fn from_blake3(data: &[u8]) -> Self {
45 let hash = blake3::hash(data);
46 Self(*hash.as_bytes())
47 }
48
49 /// Constant-time equality check.
50 ///
51 /// Accumulates XOR over all 32 bytes without early exit so the comparison
52 /// takes the same number of iterations regardless of where bytes differ.
53 /// Suitable for integrity comparisons where timing side-channels are a
54 /// concern. The `#[inline(never)]` attribute discourages the compiler from
55 /// inlining and optimising away the full-loop traversal.
56 #[inline(never)]
57 pub fn eq_ct(&self, other: &Self) -> bool {
58 let diff = self
59 .0
60 .iter()
61 .zip(other.0.iter())
62 .fold(0u8, |acc, (a, b)| acc | (a ^ b));
63 diff == 0
64 }
65}
66
67impl From<[u8; 32]> for Hash32 {
68 #[inline]
69 fn from(bytes: [u8; 32]) -> Self {
70 Self(bytes)
71 }
72}
73
74impl fmt::Debug for Hash32 {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "Hash32(")?;
77 for b in &self.0 {
78 write!(f, "{b:02x}")?;
79 }
80 write!(f, ")")
81 }
82}
83
84impl fmt::Display for Hash32 {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 for b in &self.0 {
87 write!(f, "{b:02x}")?;
88 }
89 Ok(())
90 }
91}