Skip to main content

molframe_core/contract/
digest.rs

1//! A cryptographic digest of the bytes an analysis read.
2//!
3//! [`Fingerprint`](super::Fingerprint) is a fast 64-bit hash, enough to tell one
4//! policy or cache entry from another and nothing more: it is not collision
5//! resistant, so it cannot back the claim that these are exactly the bytes an
6//! earlier result was computed from. That claim is what re-execution makes, so it
7//! rests on this, SHA-256, and the two types are kept apart so that one is never
8//! mistaken for the other.
9
10use sha2::{Digest, Sha256};
11use std::fmt;
12
13/// The SHA-256 digest of a block of bytes.
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub struct ContentDigest([u8; 32]);
16
17impl ContentDigest {
18    /// Digests a block of bytes.
19    #[must_use]
20    pub fn of(bytes: &[u8]) -> Self {
21        Self(Sha256::digest(bytes).into())
22    }
23
24    /// The 32 digest bytes.
25    #[must_use]
26    pub const fn as_bytes(&self) -> &[u8; 32] {
27        &self.0
28    }
29
30    /// Reads a digest from its 64 lowercase hexadecimal digits.
31    #[must_use]
32    pub fn from_hex(text: &str) -> Option<Self> {
33        let text = match text.strip_prefix("sha256:") {
34            Some(digits) => digits,
35            None => text,
36        };
37        if text.len() != 64 || !text.bytes().all(|byte| byte.is_ascii_hexdigit()) {
38            return None;
39        }
40        let mut bytes = [0_u8; 32];
41        for (slot, pair) in bytes.iter_mut().zip(text.as_bytes().chunks(2)) {
42            let digits = std::str::from_utf8(pair).ok()?;
43            *slot = u8::from_str_radix(digits, 16).ok()?;
44        }
45        Some(Self(bytes))
46    }
47}
48
49impl fmt::Display for ContentDigest {
50    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51        formatter.write_str("sha256:")?;
52        for byte in self.0 {
53            write!(formatter, "{byte:02x}")?;
54        }
55        Ok(())
56    }
57}
58
59impl fmt::Debug for ContentDigest {
60    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(formatter, "{self}")
62    }
63}
64
65#[cfg(test)]
66#[path = "digest_tests.rs"]
67mod tests;