dup_crypto/
hashs.rs

1//  Copyright (C) 2020 Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Provide wrappers for cryptographic hashs
17//!
18//! # Summary
19//!
20//! * [Compute Sha256 hash](#compute-sha256-hash)
21//!
22//! ## Compute sha256 hash
23//!
24//! ```
25//! use dup_crypto::hashs::Hash;
26//!
27//! let hash: Hash = Hash::compute(b"datas");
28//!
29//! assert_eq!(
30//!     "958D41C80EF75834EFFC9CBE2E8AEE11AEDE28ADA596E876B8261EDF53266B40",
31//!     &hash.to_hex(),
32//! );
33//! ```
34//!
35
36mod hash64;
37
38pub use hash64::Hash64;
39
40use crate::bases::*;
41use crate::rand::UnspecifiedRandError;
42#[cfg(target_arch = "wasm32")]
43use cryptoxide::{digest::Digest, sha2::Sha256};
44#[cfg(not(target_arch = "wasm32"))]
45use ring::digest;
46use serde::{Deserialize, Serialize};
47use std::{
48    fmt::{Debug, Display, Error, Formatter},
49    str::FromStr,
50};
51
52const BLAKE3_CUTOFF: usize = 1 << 17;
53
54/// A hash wrapper.
55///
56/// A hash is often provided as string composed of 64 hexadecimal character (0 to 9 then A to F).
57#[derive(
58    Copy,
59    Clone,
60    Deserialize,
61    Eq,
62    Hash,
63    Ord,
64    PartialEq,
65    PartialOrd,
66    Serialize,
67    zerocopy::AsBytes,
68    zerocopy::FromBytes,
69)]
70#[repr(transparent)]
71pub struct Hash(pub [u8; 32]);
72
73impl AsRef<[u8]> for Hash {
74    fn as_ref(&self) -> &[u8] {
75        &self.0[..]
76    }
77}
78
79impl Display for Hash {
80    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
81        write!(f, "{}", self.to_hex())
82    }
83}
84
85impl Debug for Hash {
86    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
87        write!(f, "Hash({})", self)
88    }
89}
90
91impl Default for Hash {
92    fn default() -> Hash {
93        Hash([0; 32])
94    }
95}
96
97impl FromStr for Hash {
98    type Err = BaseConversionError;
99
100    fn from_str(s: &str) -> Result<Self, Self::Err> {
101        Hash::from_hex(s)
102    }
103}
104
105impl Hash {
106    /// Hash size (in bytes).
107    pub const SIZE_IN_BYTES: usize = 32;
108
109    /// Generate a random Hash
110    #[inline]
111    pub fn random() -> Result<Self, UnspecifiedRandError> {
112        let random_bytes = crate::rand::gen_32_bytes().map_err(|_| UnspecifiedRandError)?;
113        Ok(Hash(random_bytes))
114    }
115
116    #[cfg(target_arch = "wasm32")]
117    #[cfg(not(tarpaulin_include))]
118    /// Compute SHA256 hash of any binary datas
119    pub fn compute(datas: &[u8]) -> Hash {
120        let mut hasher = Sha256::new();
121        hasher.input(datas);
122        let mut hash_buffer = [0u8; 32];
123        hasher.result(&mut hash_buffer);
124        Hash(hash_buffer)
125    }
126    #[cfg(not(target_arch = "wasm32"))]
127    /// Compute SHA256 hash of any binary datas
128    pub fn compute(datas: &[u8]) -> Hash {
129        let mut hash_buffer = [0u8; 32];
130        hash_buffer.copy_from_slice(digest::digest(&digest::SHA256, datas).as_ref());
131        Hash(hash_buffer)
132    }
133
134    #[cfg(target_arch = "wasm32")]
135    #[cfg(not(tarpaulin_include))]
136    /// Compute SHA256 hash of any binary data on several parts
137    pub fn compute_multipart(data_parts: &[&[u8]]) -> Hash {
138        let mut hasher = Sha256::new();
139        for data in data_parts {
140            hasher.input(data);
141        }
142        let mut hash_buffer = [0u8; 32];
143        hasher.result(&mut hash_buffer);
144        Hash(hash_buffer)
145    }
146    #[cfg(not(target_arch = "wasm32"))]
147    /// Compute SHA256 hash of any binary data on several parts
148    pub fn compute_multipart(data_parts: &[&[u8]]) -> Hash {
149        let mut ctx = digest::Context::new(&digest::SHA256);
150        for data in data_parts {
151            ctx.update(data);
152        }
153        let mut hash_buffer = [0u8; 32];
154        hash_buffer.copy_from_slice(ctx.finish().as_ref());
155        Hash(hash_buffer)
156    }
157
158    /// Compute BLAKE3 hash of any binary datas
159    pub fn compute_blake3(datas: &[u8]) -> Hash {
160        if datas.len() > BLAKE3_CUTOFF {
161            let mut hasher = blake3::Hasher::new();
162            hasher.update_with_join::<blake3::join::RayonJoin>(datas);
163            let hash = hasher.finalize();
164            Hash(hash.into())
165        } else {
166            Hash(blake3::hash(datas).into())
167        }
168    }
169
170    /// Convert Hash into bytes vector
171    pub fn to_bytes_vector(&self) -> Vec<u8> {
172        self.0.to_vec()
173    }
174
175    /// Convert a `Hash` to an hex string.
176    pub fn to_hex(&self) -> String {
177        let strings: Vec<String> = self.0.iter().map(|b| format!("{:02X}", b)).collect();
178
179        strings.join("")
180    }
181
182    /// Convert a hex string in a `Hash`.
183    ///
184    /// The hex string must only contains hex characters
185    /// and produce a 32 bytes value.
186    #[inline]
187    pub fn from_hex(text: &str) -> Result<Hash, BaseConversionError> {
188        Ok(Hash(b16::str_hex_to_32bytes(text)?))
189    }
190
191    /// Return tha maximum hash value
192    ///
193    /// Hexadecimal representation is `FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`
194    pub const fn max() -> Hash {
195        Hash([255; 32])
196    }
197}
198
199#[cfg(test)]
200mod tests {
201
202    use super::*;
203    use unwrap::unwrap;
204
205    #[test]
206    fn test_hash_random() {
207        let hash1 = Hash::random();
208        let hash2 = Hash::random();
209        assert_ne!(hash1, hash2);
210    }
211
212    #[test]
213    fn test_hash_debug() {
214        assert_eq!(
215            "Hash(0000000000000000000000000000000000000000000000000000000000000000)".to_owned(),
216            format!("{:?}", Hash::default()),
217        );
218    }
219
220    #[test]
221    fn test_hash_to_bytes() {
222        assert_eq!(
223            vec![
224                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
225                0, 0, 0, 0
226            ],
227            Hash::default().to_bytes_vector(),
228        );
229    }
230
231    #[test]
232    fn test_hash_computation() {
233        assert_eq!(
234            unwrap!(Hash::from_hex(
235                "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
236            )),
237            Hash::compute(b"hello"),
238        );
239
240        assert_eq!(
241            unwrap!(Hash::from_hex(
242                "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
243            )),
244            Hash::compute(b"hello"),
245        );
246    }
247
248    #[test]
249    fn test_hash_from_hex() {
250        assert_eq!(
251            Ok(Hash::default()),
252            Hash::from_hex("0000000000000000000000000000000000000000000000000000000000000000")
253        );
254        assert_eq!(
255            Err(BaseConversionError::InvalidLength {
256                expected: 64,
257                found: 65,
258            }),
259            Hash::from_hex("00000000000000000000000000000000000000000000000000000000000000000")
260        );
261        assert_eq!(
262            Err(BaseConversionError::InvalidCharacter {
263                character: '_',
264                offset: 0,
265            }),
266            Hash::from_hex("_000000000000000000000000000000000000000000000000000000000000000")
267        );
268        assert_eq!(
269            Err(BaseConversionError::InvalidCharacter {
270                character: '_',
271                offset: 1,
272            }),
273            Hash::from_hex("0_00000000000000000000000000000000000000000000000000000000000000")
274        );
275    }
276}