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}