Skip to main content

s2_common/
bash.rs

1use bytes::Bytes;
2
3/// BLAKE3 hash (32 bytes) of any number of fields.
4///
5/// Default SerDe implementation uses hex representation.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Bash(blake3::Hash);
8
9impl Bash {
10    pub const LEN: usize = 32;
11
12    /// Hashes components separated by a delimiter byte.
13    /// Callers must ensure components do not contain the delimiter.
14    pub fn delimited(components: &[&[u8]], delimiter: u8) -> Self {
15        let mut hasher = blake3::Hasher::new();
16        for component in components {
17            hasher.update(component);
18            hasher.update(&[delimiter]);
19        }
20        Self(hasher.finalize())
21    }
22
23    /// Hashes components with length prefixes to avoid separator ambiguity.
24    pub fn length_prefixed(components: &[&[u8]]) -> Self {
25        let mut hasher = blake3::Hasher::new();
26        for component in components {
27            hasher.update(&(component.len() as u64).to_le_bytes());
28            hasher.update(component);
29        }
30        Self(hasher.finalize())
31    }
32
33    pub fn as_bytes(&self) -> &[u8; Bash::LEN] {
34        self.0.as_bytes()
35    }
36}
37
38impl std::fmt::Display for Bash {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.write_str(self.0.to_hex().as_str())
41    }
42}
43
44impl AsRef<[u8]> for Bash {
45    fn as_ref(&self) -> &[u8] {
46        self.as_bytes()
47    }
48}
49
50impl From<Bash> for [u8; Bash::LEN] {
51    fn from(bash: Bash) -> Self {
52        bash.0.into()
53    }
54}
55
56impl From<[u8; Bash::LEN]> for Bash {
57    fn from(bytes: [u8; Bash::LEN]) -> Self {
58        Self(blake3::Hash::from_bytes(bytes))
59    }
60}
61
62impl From<Bash> for Bytes {
63    fn from(bash: Bash) -> Self {
64        Bytes::copy_from_slice(bash.as_bytes())
65    }
66}
67
68impl serde::Serialize for Bash {
69    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70    where
71        S: serde::Serializer,
72    {
73        serializer.serialize_str(&self.0.to_hex())
74    }
75}
76
77impl<'de> serde::Deserialize<'de> for Bash {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: serde::Deserializer<'de>,
81    {
82        let s = String::deserialize(deserializer)?;
83        let hash = blake3::Hash::from_hex(s.as_bytes()).map_err(serde::de::Error::custom)?;
84        Ok(Self(hash))
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::Bash;
91
92    #[test]
93    fn bash_len_prefixed_components_are_unambiguous() {
94        let bash1 = Bash::length_prefixed(&[b"a\0", b"b"]);
95        let bash2 = Bash::length_prefixed(&[b"a", b"\0b"]);
96
97        assert_ne!(bash1, bash2);
98    }
99}