Skip to main content

calimero_primitives/
hash.rs

1#[cfg(test)]
2#[path = "tests/hash.rs"]
3mod tests;
4
5use core::cmp::Ordering;
6use core::fmt::{self, Debug, Display, Formatter};
7use core::hash::{Hash as StdHash, Hasher};
8use core::ops::Deref;
9use core::str::{from_utf8_unchecked, FromStr};
10#[cfg(feature = "borsh")]
11use std::io;
12
13#[cfg(feature = "borsh")]
14use borsh::{BorshDeserialize, BorshSerialize};
15use bs58::decode::Error as Bs58Error;
16use serde::de::{Error as SerdeError, Visitor};
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18use serde_json::{to_writer as to_json_writer, Result as JsonResult};
19use sha2::{Digest, Sha256};
20use thiserror::Error as ThisError;
21
22const BYTES_LEN: usize = 32;
23#[expect(clippy::integer_division, reason = "Not harmful here")]
24// https://github.com/bitcoin/libbase58/blob/master/base58.c#L155
25const MAX_STR_LEN: usize = (BYTES_LEN * 138 / 100) + 1;
26
27#[derive(Clone, Copy)]
28pub struct Hash {
29    bytes: [u8; BYTES_LEN],
30    bs58_cache: [u8; MAX_STR_LEN],
31    bs58_len: u8,
32}
33
34impl Hash {
35    #[must_use]
36    pub const fn as_bytes(&self) -> &[u8; BYTES_LEN] {
37        &self.bytes
38    }
39
40    #[must_use]
41    pub fn new(data: &[u8]) -> Self {
42        let hash_bytes: [u8; BYTES_LEN] = Sha256::digest(data).into();
43        // Will call `From<[u8; 32]>` which computes the cache
44        hash_bytes.into()
45    }
46
47    pub fn is_zero(&self) -> bool {
48        self.bytes.iter().all(|&byte| byte == 0)
49    }
50
51    pub fn hash_json<T: Serialize>(data: &T) -> JsonResult<Self> {
52        let mut hasher = Sha256::default();
53
54        to_json_writer(&mut hasher, data)?;
55
56        // Get the raw [u8; 32] bytes from the hasher
57        let hash_bytes: [u8; BYTES_LEN] = hasher.finalize().into();
58
59        // Let `From<[u8; 32]>` handle construction and caching
60        Ok(hash_bytes.into())
61    }
62
63    #[cfg(feature = "borsh")]
64    pub fn hash_borsh<T: BorshSerialize>(data: &T) -> io::Result<Self> {
65        let mut hasher = Sha256::default();
66
67        data.serialize(&mut hasher)?;
68
69        // Get the raw [u8; 32] bytes from the hasher
70        let hash_bytes: [u8; BYTES_LEN] = hasher.finalize().into();
71
72        // Let `From<[u8; 32]>` handle construction and caching
73        Ok(hash_bytes.into())
74    }
75
76    #[must_use]
77    pub fn as_str(&self) -> &str {
78        // Safe: Read from the pre-computed cache.
79        let s = &self.bs58_cache[..self.bs58_len as usize];
80
81        // We can trust this is valid UTF-8 because we are the ones
82        // who put it there during initialization.
83        // Using from_utf8().unwrap() is also perfectly fine.
84        unsafe { from_utf8_unchecked(s) }
85    }
86
87    fn from_str(s: &str) -> Result<Self, Option<Bs58Error>> {
88        let s_len = s.len();
89        if s_len > MAX_STR_LEN {
90            return Err(Some(Bs58Error::BufferTooSmall));
91        }
92
93        let mut bytes = [0; BYTES_LEN];
94        match bs58::decode(s).onto(&mut bytes) {
95            Ok(len) if len == bytes.len() => {
96                let mut bs58_cache = [0; MAX_STR_LEN];
97                bs58_cache[..s_len].copy_from_slice(s.as_bytes());
98
99                Ok(Self {
100                    bytes,
101                    bs58_cache,
102                    bs58_len: s_len.try_into().expect("infallible conversion: checked before string length is less than MAX_STR_LEN"),
103                })
104            }
105            Ok(_) => Err(None),
106            Err(err) => Err(Some(err)),
107        }
108    }
109}
110
111impl From<[u8; BYTES_LEN]> for Hash {
112    fn from(bytes: [u8; BYTES_LEN]) -> Self {
113        let mut bs58_cache = [0; MAX_STR_LEN];
114        let len = bs58::encode(&bytes)
115            .onto(&mut bs58_cache[..])
116            // Panics if MAX_STR_LEN is wrong, which is good.
117            .expect("Base58 encoding failed");
118
119        Self {
120            bytes,
121            bs58_cache,
122            // Safe: max len is 45
123            bs58_len: len
124                .try_into()
125                .expect("infaliible conversion: bs58_len conversion failed, but shouldn't have"),
126        }
127    }
128}
129
130impl From<Hash> for [u8; BYTES_LEN] {
131    fn from(hash: Hash) -> Self {
132        hash.bytes
133    }
134}
135
136impl AsRef<[u8; BYTES_LEN]> for Hash {
137    fn as_ref(&self) -> &[u8; BYTES_LEN] {
138        &self.bytes
139    }
140}
141
142impl Deref for Hash {
143    type Target = [u8; BYTES_LEN];
144
145    fn deref(&self) -> &Self::Target {
146        &self.bytes
147    }
148}
149
150impl Default for Hash {
151    fn default() -> Self {
152        // Create the default byte array
153        const DEFAULT_BYTES: [u8; BYTES_LEN] = [0; BYTES_LEN];
154
155        // Let `From<[u8; 32]>` handle construction and caching
156        DEFAULT_BYTES.into()
157    }
158}
159
160impl StdHash for Hash {
161    fn hash<H: Hasher>(&self, state: &mut H) {
162        self.bytes.hash(state);
163    }
164}
165
166impl PartialEq for Hash {
167    fn eq(&self, other: &Self) -> bool {
168        self.bytes.eq(&other.bytes)
169    }
170}
171
172impl Eq for Hash {}
173
174impl PartialOrd for Hash {
175    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
176        Some(self.cmp(other))
177    }
178}
179
180impl Ord for Hash {
181    fn cmp(&self, other: &Self) -> Ordering {
182        self.bytes.cmp(&other.bytes)
183    }
184}
185
186impl Display for Hash {
187    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
188        f.pad(self.as_str())
189    }
190}
191
192impl Debug for Hash {
193    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
194        f.debug_tuple("Hash").field(&self.as_str()).finish()
195    }
196}
197
198#[derive(Clone, Copy, Debug, ThisError)]
199#[non_exhaustive]
200pub enum HashError {
201    #[error("invalid hash length")]
202    InvalidLength,
203
204    #[error("invalid base58")]
205    DecodeError(#[from] Bs58Error),
206}
207
208impl FromStr for Hash {
209    type Err = HashError;
210
211    fn from_str(s: &str) -> Result<Self, Self::Err> {
212        match Self::from_str(s) {
213            Ok(hash) => Ok(hash),
214            Err(None) => Err(HashError::InvalidLength),
215            Err(Some(err)) => Err(HashError::DecodeError(err)),
216        }
217    }
218}
219
220#[cfg(feature = "borsh")]
221impl BorshSerialize for Hash {
222    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
223        writer.write_all(&self.bytes)
224    }
225}
226
227#[cfg(feature = "borsh")]
228impl BorshDeserialize for Hash {
229    fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
230        let mut bytes = [0; BYTES_LEN];
231        reader.read_exact(&mut bytes)?;
232
233        // Let `From<[u8; 32]>` handle construction and caching
234        Ok(bytes.into())
235    }
236}
237
238impl Serialize for Hash {
239    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
240        serializer.serialize_str(self.as_str())
241    }
242}
243
244impl<'de> Deserialize<'de> for Hash {
245    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
246        struct HashVisitor;
247
248        impl Visitor<'_> for HashVisitor {
249            type Value = Hash;
250
251            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
252                formatter.write_str("a base58 encoded hash")
253            }
254
255            fn visit_str<E: SerdeError>(self, v: &str) -> Result<Self::Value, E> {
256                match Hash::from_str(v) {
257                    Ok(hash) => Ok(hash),
258                    Err(None) => Err(E::invalid_length(v.len(), &self)),
259                    Err(Some(err)) => Err(E::custom(err)),
260                }
261            }
262        }
263
264        deserializer.deserialize_str(HashVisitor)
265    }
266}