calimero_primitives/
hash.rs1#[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")]
24const 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 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 let hash_bytes: [u8; BYTES_LEN] = hasher.finalize().into();
58
59 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 let hash_bytes: [u8; BYTES_LEN] = hasher.finalize().into();
71
72 Ok(hash_bytes.into())
74 }
75
76 #[must_use]
77 pub fn as_str(&self) -> &str {
78 let s = &self.bs58_cache[..self.bs58_len as usize];
80
81 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 .expect("Base58 encoding failed");
118
119 Self {
120 bytes,
121 bs58_cache,
122 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 const DEFAULT_BYTES: [u8; BYTES_LEN] = [0; BYTES_LEN];
154
155 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 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}