massa_hash/hash.rs
1// Copyright (c) 2022 MASSA LABS <info@massa.net>
2
3use crate::error::MassaHashError;
4use crate::settings::HASH_SIZE_BYTES;
5use massa_serialization::{Deserializer, SerializeError, Serializer};
6use nom::{
7 error::{context, ContextError, ParseError},
8 IResult,
9};
10use std::{cmp::Ordering, convert::TryInto, str::FromStr};
11
12/// Hash wrapper, the underlying hash type is `Blake3`
13///
14/// The motivations for selecting Blake3 were-
15/// Speed: Blake3 is significantly faster than other popular hashing algorithms, such as SHA-256 and SHA-3.
16/// This is largely due to its ability to leverage modern CPU architectures and instruction sets, as well as its optimized implementation.
17///
18/// Security: Blake3 is designed to be highly secure and resistant to a wide range of attacks, including collision attacks,
19/// length-extension attacks, and timing attacks. It also offers better resistance to side-channel attacks than many other hashing algorithms.
20///
21/// Flexibility: Blake3 is highly flexible and can be used in a variety of applications, including as a general-purpose hash function,
22/// as a key derivation function, and as a message authentication code. It also supports a wide range of input sizes and can produce output
23/// of any desired length.
24///
25/// Scalability: Blake3 can efficiently take advantage of multiple cores and SIMD (single instruction, multiple data) instructions,
26/// allowing it to scale well on modern CPUs.
27///
28/// Improved Compression Function: The compression function used in Blake3 is an improved version of the one used in its predecessor, Blake2.
29/// This improved compression function offers better diffusion and mixing properties, which contributes to its increased security.
30///
31/// Keyed Hashing: Blake3 supports keyed hashing, which allows users to use a secret key to generate a unique hash value.
32/// This feature can be useful in applications that require message authentication or integrity verification.
33///
34/// Tree Hashing: Blake3 supports tree hashing, which allows users to hash large files or data structures in a parallel and efficient manner.
35/// This feature can be useful in applications that involve large-scale data processing, such as cloud storage or distributed file systems.
36///
37/// Open Source: Blake3 is an open-source algorithm, which means that its code is publicly available for review and auditing by anyone.
38/// This can help improve its security and reliability, as well as increase transparency and trust among users.
39///
40#[derive(Eq, PartialEq, Copy, Clone, Hash)]
41pub struct Hash(blake3::Hash);
42
43impl PartialOrd for Hash {
44 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
45 Some(self.cmp(other))
46 }
47}
48
49/// In massa, this function is generally useful for data structures that performs ordering and where hashes are used
50/// as keys. For e.g., it is used for the BTreeMap where the order of the addresses is to be maintained.
51/// This function helps to have a single coherent BTreeMap which is then used to perform the draw
52/// See Pos-Worker for more details.
53impl Ord for Hash {
54 fn cmp(&self, other: &Self) -> Ordering {
55 self.0.as_bytes().cmp(other.0.as_bytes())
56 }
57}
58
59impl std::fmt::Display for Hash {
60 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
61 write!(f, "{}", self.to_bs58_check())
62 }
63}
64
65impl std::fmt::Debug for Hash {
66 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
67 std::fmt::Display::fmt(self, f)
68 }
69}
70
71impl Hash {
72 /// Creates a hash full of zeros bytes.
73 pub fn zero() -> Self {
74 Hash(blake3::Hash::from([0; HASH_SIZE_BYTES]))
75 }
76
77 /// Compute a hash from data.
78 ///
79 /// # Example
80 /// ```
81 /// # use massa_hash::Hash;
82 /// let hash = Hash::compute_from(&"hello world".as_bytes());
83 /// ```
84 pub fn compute_from(data: &[u8]) -> Self {
85 Hash(blake3::hash(data))
86 }
87
88 /// Compute a hash from tuple of byte arrays.
89 ///
90 /// # Example
91 /// ```
92 /// # use massa_hash::Hash;
93 /// let hash = Hash::compute_from_tuple(&[&"hello".as_bytes(), &"world".as_bytes()]);
94 /// ```
95 pub fn compute_from_tuple(data: &[&[u8]]) -> Self {
96 let mut hasher = blake3::Hasher::new();
97 for d in data {
98 hasher.update(&(d.len() as u64).to_be_bytes());
99 hasher.update(d);
100 }
101 Hash(hasher.finalize())
102 }
103
104 /// Serialize a Hash using `bs58` encoding with checksum.
105 ///
106 /// # Example
107 /// ```
108 /// # use massa_hash::Hash;
109 /// let hash = Hash::compute_from(&"hello world".as_bytes());
110 /// let serialized: String = hash.to_bs58_check();
111 /// ```
112 /// Motivations for using base58 encoding:
113 ///
114 /// base58_check is like base64 but-
115 /// * fully standardized (no = vs /)
116 /// * no weird characters (eg. +) only alphanumeric
117 /// * ambiguous letters combined (eg. O vs 0, or l vs 1)
118 /// * contains a checksum at the end to detect typing errors
119 ///
120 pub fn to_bs58_check(&self) -> String {
121 bs58::encode(self.to_bytes()).with_check().into_string()
122 }
123
124 /// Serialize a Hash as bytes.
125 ///
126 /// # Example
127 /// ```
128 /// # use massa_hash::Hash;
129 /// let hash = Hash::compute_from(&"hello world".as_bytes());
130 /// let serialized = hash.to_bytes();
131 /// ```
132 pub fn to_bytes(&self) -> &[u8; HASH_SIZE_BYTES] {
133 self.0.as_bytes()
134 }
135
136 /// Convert into bytes.
137 ///
138 /// # Example
139 /// ```
140 /// # use massa_hash::Hash;
141 /// let hash = Hash::compute_from(&"hello world".as_bytes());
142 /// let serialized = hash.into_bytes();
143 /// ```
144 pub fn into_bytes(self) -> [u8; HASH_SIZE_BYTES] {
145 *self.0.as_bytes()
146 }
147
148 /// Deserialize using `bs58` encoding with checksum.
149 ///
150 /// # Example
151 /// ```
152 /// # use serde::{Deserialize, Serialize};
153 /// # use massa_hash::Hash;
154 /// let hash = Hash::compute_from(&"hello world".as_bytes());
155 /// let serialized: String = hash.to_bs58_check();
156 /// let deserialized: Hash = Hash::from_bs58_check(&serialized).unwrap();
157 /// ```
158 pub fn from_bs58_check(data: &str) -> Result<Hash, MassaHashError> {
159 let decoded_bs58_check = bs58::decode(data)
160 .with_check(None)
161 .into_vec()
162 .map_err(|err| MassaHashError::ParsingError(format!("{}", err)))?;
163 Ok(Hash::from_bytes(
164 &decoded_bs58_check
165 .as_slice()
166 .try_into()
167 .map_err(|err| MassaHashError::ParsingError(format!("{}", err)))?,
168 ))
169 }
170
171 /// Deserialize a Hash as bytes.
172 ///
173 /// # Example
174 /// ```
175 /// # use serde::{Deserialize, Serialize};
176 /// # use massa_hash::Hash;
177 /// let hash = Hash::compute_from(&"hello world".as_bytes());
178 /// let serialized = hash.into_bytes();
179 /// let deserialized: Hash = Hash::from_bytes(&serialized);
180 /// ```
181 pub fn from_bytes(data: &[u8; HASH_SIZE_BYTES]) -> Hash {
182 Hash(blake3::Hash::from(*data))
183 }
184}
185
186impl TryFrom<&[u8]> for Hash {
187 type Error = MassaHashError;
188
189 /// Try parsing from byte slice.
190 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
191 Ok(Hash::from_bytes(value.try_into().map_err(|err| {
192 MassaHashError::ParsingError(format!("{}", err))
193 })?))
194 }
195}
196
197/// Serializer for `Hash`
198#[derive(Default, Clone)]
199pub struct HashSerializer;
200
201impl HashSerializer {
202 /// Creates a serializer for `Hash`
203 pub const fn new() -> Self {
204 Self
205 }
206}
207
208impl Serializer<Hash> for HashSerializer {
209 fn serialize(&self, value: &Hash, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
210 buffer.extend(value.to_bytes());
211 Ok(())
212 }
213}
214
215/// Deserializer for `Hash`
216#[derive(Default, Clone)]
217pub struct HashDeserializer;
218
219impl HashDeserializer {
220 /// Creates a deserializer for `Hash`
221 pub const fn new() -> Self {
222 Self
223 }
224}
225
226impl Deserializer<Hash> for HashDeserializer {
227 /// ## Example
228 /// ```rust
229 /// use massa_hash::{Hash, HashDeserializer};
230 /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
231 ///
232 /// let hash_deserializer = HashDeserializer::new();
233 /// let hash = Hash::compute_from(&"hello world".as_bytes());
234 /// let (rest, deserialized) = hash_deserializer.deserialize::<DeserializeError>(hash.to_bytes()).unwrap();
235 /// assert_eq!(deserialized, hash);
236 /// assert_eq!(rest.len(), 0);
237 /// ```
238 fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
239 &self,
240 buffer: &'a [u8],
241 ) -> IResult<&'a [u8], Hash, E> {
242 context("Failed hash deserialization", |input: &'a [u8]| {
243 if buffer.len() < HASH_SIZE_BYTES {
244 return Err(nom::Err::Error(ParseError::from_error_kind(
245 input,
246 nom::error::ErrorKind::LengthValue,
247 )));
248 }
249 Ok((
250 &buffer[HASH_SIZE_BYTES..],
251 Hash::from_bytes(&buffer[..HASH_SIZE_BYTES].try_into().map_err(|_| {
252 nom::Err::Error(ParseError::from_error_kind(
253 input,
254 nom::error::ErrorKind::Fail,
255 ))
256 })?),
257 ))
258 })(buffer)
259 }
260}
261
262impl ::serde::Serialize for Hash {
263 /// `::serde::Serialize` trait for Hash
264 /// if the serializer is human readable,
265 /// serialization is done using `serialize_bs58_check`
266 /// else, it uses `serialize_binary`
267 ///
268 /// # Example
269 ///
270 /// Human readable serialization :
271 /// ```
272 /// # use serde::{Deserialize, Serialize};
273 /// # use massa_hash::Hash;
274 /// let hash = Hash::compute_from(&"hello world".as_bytes());
275 /// let serialized: String = serde_json::to_string(&hash).unwrap();
276 /// ```
277 ///
278 fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
279 if s.is_human_readable() {
280 s.collect_str(&self.to_bs58_check())
281 } else {
282 s.serialize_bytes(self.to_bytes())
283 }
284 }
285}
286
287impl<'de> ::serde::Deserialize<'de> for Hash {
288 /// `::serde::Deserialize` trait for Hash
289 /// if the deserializer is human readable,
290 /// deserialization is done using `deserialize_bs58_check`
291 /// else, it uses `deserialize_binary`
292 ///
293 /// # Example
294 ///
295 /// Human readable deserialization :
296 /// ```
297 /// # use massa_hash::Hash;
298 /// # use serde::{Deserialize, Serialize};
299 /// let hash = Hash::compute_from(&"hello world".as_bytes());
300 /// let serialized: String = serde_json::to_string(&hash).unwrap();
301 /// let deserialized: Hash = serde_json::from_str(&serialized).unwrap();
302 /// ```
303 ///
304 fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Hash, D::Error> {
305 if d.is_human_readable() {
306 struct Base58CheckVisitor;
307
308 impl<'de> ::serde::de::Visitor<'de> for Base58CheckVisitor {
309 type Value = Hash;
310
311 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
312 formatter.write_str("an ASCII base58check string")
313 }
314
315 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
316 where
317 E: ::serde::de::Error,
318 {
319 if let Ok(v_str) = std::str::from_utf8(v) {
320 Hash::from_bs58_check(v_str).map_err(E::custom)
321 } else {
322 Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self))
323 }
324 }
325
326 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
327 where
328 E: ::serde::de::Error,
329 {
330 Hash::from_bs58_check(v).map_err(E::custom)
331 }
332 }
333 d.deserialize_str(Base58CheckVisitor)
334 } else {
335 struct BytesVisitor;
336
337 impl<'de> ::serde::de::Visitor<'de> for BytesVisitor {
338 type Value = Hash;
339
340 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
341 formatter.write_str("a bytestring")
342 }
343
344 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
345 where
346 E: ::serde::de::Error,
347 {
348 Ok(Hash::from_bytes(v.try_into().map_err(E::custom)?))
349 }
350 }
351
352 d.deserialize_bytes(BytesVisitor)
353 }
354 }
355}
356
357impl FromStr for Hash {
358 type Err = MassaHashError;
359 fn from_str(s: &str) -> Result<Self, Self::Err> {
360 Hash::from_bs58_check(s)
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use serial_test::serial;
367
368 use super::*;
369
370 fn example() -> Hash {
371 Hash::compute_from("hello world".as_bytes())
372 }
373
374 #[test]
375 #[serial]
376 fn test_serde_json() {
377 let hash = example();
378 let serialized = serde_json::to_string(&hash).unwrap();
379 let deserialized = serde_json::from_str(&serialized).unwrap();
380 assert_eq!(hash, deserialized)
381 }
382
383 #[test]
384 #[serial]
385 fn test_hash() {
386 let data = "abc".as_bytes();
387 let hash = Hash::compute_from(data);
388 let hash_ref: [u8; HASH_SIZE_BYTES] = [
389 100, 55, 179, 172, 56, 70, 81, 51, 255, 182, 59, 117, 39, 58, 141, 181, 72, 197, 88,
390 70, 93, 121, 219, 3, 253, 53, 156, 108, 213, 189, 157, 133,
391 ];
392 assert_eq!(hash.into_bytes(), hash_ref);
393 }
394}