Skip to main content

audit_trail/
hash.rs

1//! Hash digest and pluggable [`Hasher`] trait.
2//!
3//! `audit-trail` does not bundle a concrete hash function. Callers wire in an
4//! implementation of [`Hasher`] backed by SHA-256, BLAKE3, SHA3-256, or any
5//! other collision-resistant 32-byte hash. The trait is hot-path friendly:
6//! no allocations, no boxed trait objects, no `dyn`.
7
8use core::fmt;
9
10/// Size, in bytes, of a hash output produced by a [`Hasher`].
11///
12/// Fixed at 32 bytes to cover the common cryptographic primitives
13/// (SHA-256, BLAKE3, SHA3-256, KangarooTwelve-256).
14pub const HASH_LEN: usize = 32;
15
16/// Fixed-size hash output.
17///
18/// Used as the `prev_hash` and `hash` fields on every [`crate::Record`].
19///
20/// # Example
21///
22/// ```
23/// use audit_trail::Digest;
24///
25/// let zero = Digest::ZERO;
26/// assert_eq!(zero.as_bytes(), &[0u8; 32]);
27/// ```
28#[derive(Copy, Clone, PartialEq, Eq, Hash)]
29pub struct Digest([u8; HASH_LEN]);
30
31impl Digest {
32    /// All-zero digest. Used as the `prev_hash` of the genesis record.
33    pub const ZERO: Self = Self([0u8; HASH_LEN]);
34
35    /// Construct a digest from raw bytes.
36    ///
37    /// # Example
38    ///
39    /// ```
40    /// use audit_trail::Digest;
41    /// let d = Digest::from_bytes([1u8; 32]);
42    /// assert_eq!(d.as_bytes()[0], 1);
43    /// ```
44    #[inline]
45    pub const fn from_bytes(bytes: [u8; HASH_LEN]) -> Self {
46        Self(bytes)
47    }
48
49    /// Borrow the underlying bytes.
50    #[inline]
51    pub const fn as_bytes(&self) -> &[u8; HASH_LEN] {
52        &self.0
53    }
54
55    /// Consume the digest and return the underlying bytes.
56    #[inline]
57    pub const fn into_bytes(self) -> [u8; HASH_LEN] {
58        self.0
59    }
60}
61
62impl Default for Digest {
63    #[inline]
64    fn default() -> Self {
65        Self::ZERO
66    }
67}
68
69impl fmt::Debug for Digest {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.write_str("Digest(")?;
72        for byte in &self.0 {
73            write!(f, "{byte:02x}")?;
74        }
75        f.write_str(")")
76    }
77}
78
79impl fmt::LowerHex for Digest {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        for byte in &self.0 {
82            write!(f, "{byte:02x}")?;
83        }
84        Ok(())
85    }
86}
87
88impl AsRef<[u8]> for Digest {
89    #[inline]
90    fn as_ref(&self) -> &[u8] {
91        &self.0
92    }
93}
94
95/// Pluggable hash function used to chain audit records.
96///
97/// Implementations must be deterministic and collision-resistant. A typical
98/// adapter wraps a `Sha256`, `Blake3`, or `Sha3_256` from an external crate.
99///
100/// The trait is stateful: callers invoke [`reset`](Hasher::reset) at the
101/// start of each record, [`update`](Hasher::update) with field bytes, then
102/// [`finalize`](Hasher::finalize) to write the output digest. Implementations
103/// may reuse internal buffers across calls to avoid heap allocation.
104///
105/// # Example
106///
107/// ```
108/// use audit_trail::{Digest, Hasher, HASH_LEN};
109///
110/// /// A trivially-insecure XOR "hasher" for demonstration only.
111/// struct XorHasher([u8; HASH_LEN]);
112///
113/// impl Hasher for XorHasher {
114///     fn reset(&mut self) { self.0 = [0u8; HASH_LEN]; }
115///     fn update(&mut self, bytes: &[u8]) {
116///         for (i, b) in bytes.iter().enumerate() {
117///             self.0[i % HASH_LEN] ^= *b;
118///         }
119///     }
120///     fn finalize(&mut self, out: &mut Digest) {
121///         *out = Digest::from_bytes(self.0);
122///     }
123/// }
124/// ```
125pub trait Hasher {
126    /// Reset the hasher to its initial state.
127    fn reset(&mut self);
128
129    /// Absorb a byte slice into the hash state.
130    fn update(&mut self, bytes: &[u8]);
131
132    /// Finalize the hash and write the result into `out`.
133    ///
134    /// After finalization the hasher is left in an unspecified state and
135    /// must be [`reset`](Hasher::reset) before being used again.
136    fn finalize(&mut self, out: &mut Digest);
137}