dup_crypto/hashs/
hash64.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 wrapper for cryptographic hash of 64 bytes size
17//!
18//! # Summary
19//!
20//! * [Compute Sha512 hash](#compute-sha512-hash)
21//!
22//! ## Compute sha512 hash
23//!
24//! ```
25//! use dup_crypto::hashs::Hash64;
26//!
27//! let hash: Hash64 = Hash64::sha512(b"datas");
28//!
29//! assert_eq!(
30//!     "7F01265FD4F8CB88170139B81379C526429217317FB2D5CD209FFDFC8CA9FA1997222E640F50993A404CAE239D0D7480371E938B937FDF558C2C4194B77A0111",
31//!     &hash.to_hex(),
32//! );
33//! ```
34//!
35
36use crate::bases::*;
37use crate::rand::UnspecifiedRandError;
38#[cfg(target_arch = "wasm32")]
39use cryptoxide::{digest::Digest, sha2::Sha512};
40#[cfg(not(target_arch = "wasm32"))]
41use ring::digest;
42use std::{
43    fmt::{Debug, Display, Error, Formatter},
44    str::FromStr,
45};
46
47/// A hash wrapper.
48///
49/// A hash is often provided as string composed of 64 hexadecimal character (0 to 9 then A to F).
50#[derive(
51    Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd, zerocopy::AsBytes, zerocopy::FromBytes,
52)]
53#[repr(transparent)]
54pub struct Hash64(pub [u8; 64]);
55
56impl AsRef<[u8]> for Hash64 {
57    fn as_ref(&self) -> &[u8] {
58        &self.0[..]
59    }
60}
61
62impl Display for Hash64 {
63    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
64        write!(f, "{}", self.to_hex())
65    }
66}
67
68impl Debug for Hash64 {
69    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
70        write!(f, "Hash64({})", self)
71    }
72}
73
74impl Default for Hash64 {
75    fn default() -> Hash64 {
76        Hash64([0; 64])
77    }
78}
79
80impl FromStr for Hash64 {
81    type Err = BaseConversionError;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        Hash64::from_hex(s)
85    }
86}
87
88impl Hash64 {
89    /// Hash64 size (in bytes).
90    pub const SIZE_IN_BYTES: usize = 64;
91
92    /// Generate a random Hash64
93    #[inline]
94    pub fn random() -> Result<Self, UnspecifiedRandError> {
95        let mut random_bytes = [0u8; 64];
96        crate::rand::gen_random_bytes(&mut random_bytes).map_err(|_| UnspecifiedRandError)?;
97        Ok(Hash64(random_bytes))
98    }
99
100    #[cfg(target_arch = "wasm32")]
101    #[cfg(not(tarpaulin_include))]
102    /// Compute SHA512 hash of any binary data
103    pub fn sha512(data: &[u8]) -> Hash64 {
104        let mut hasher = Sha512::new();
105        hasher.input(data);
106        let mut hash_buffer = [0u8; 64];
107        hasher.result(&mut hash_buffer);
108        Hash64(hash_buffer)
109    }
110    #[cfg(not(target_arch = "wasm32"))]
111    /// Compute SHA512  hash of any binary data
112    pub fn sha512(datas: &[u8]) -> Hash64 {
113        let mut hash_buffer = [0u8; 64];
114        hash_buffer.copy_from_slice(digest::digest(&digest::SHA512, datas).as_ref());
115        Hash64(hash_buffer)
116    }
117
118    #[cfg(target_arch = "wasm32")]
119    #[cfg(not(tarpaulin_include))]
120    /// Compute SHA512 hash of any binary data on several parts
121    pub fn sha512_multipart(data_parts: &[&[u8]]) -> Hash64 {
122        let mut hasher = Sha512::new();
123        for data in data_parts {
124            hasher.input(data);
125        }
126        let mut hash_buffer = [0u8; 64];
127        hasher.result(&mut hash_buffer);
128        Hash64(hash_buffer)
129    }
130    #[cfg(not(target_arch = "wasm32"))]
131    /// Compute SHA512 hash of any binary data on several parts
132    pub fn sha512_multipart(data_parts: &[&[u8]]) -> Hash64 {
133        let mut ctx = digest::Context::new(&digest::SHA512);
134        for data in data_parts {
135            ctx.update(data);
136        }
137        let mut hash_buffer = [0u8; 64];
138        hash_buffer.copy_from_slice(ctx.finish().as_ref());
139        Hash64(hash_buffer)
140    }
141
142    /// Convert Hash64 into bytes vector
143    pub fn to_bytes_vector(&self) -> Vec<u8> {
144        self.0.to_vec()
145    }
146
147    /// Convert a `Hash` to an hex string.
148    pub fn to_hex(&self) -> String {
149        let strings: Vec<String> = self.0.iter().map(|b| format!("{:02X}", b)).collect();
150
151        strings.join("")
152    }
153
154    /// Convert a hex string in a `Hash`.
155    ///
156    /// The hex string must only contains hex characters
157    /// and produce a 64 bytes value.
158    #[inline]
159    pub fn from_hex(text: &str) -> Result<Hash64, BaseConversionError> {
160        Ok(Hash64(b16::str_hex_to_64bytes(text)?))
161    }
162
163    /// Return tha maximum hash value
164    ///
165    /// Hexadecimal representation is `FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`
166    pub const fn max() -> Hash64 {
167        Hash64([255; 64])
168    }
169}
170
171#[cfg(test)]
172mod tests {
173
174    use super::*;
175    use unwrap::unwrap;
176
177    #[test]
178    fn test_hash_random() {
179        let hash1 = Hash64::random();
180        let hash2 = Hash64::random();
181        assert_ne!(hash1, hash2);
182    }
183
184    #[test]
185    fn test_hash_debug() {
186        assert_eq!(
187            "Hash64(00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)".to_owned(),
188            format!("{:?}", Hash64::default()),
189        );
190    }
191
192    #[test]
193    fn test_hash_to_bytes() {
194        assert_eq!(
195            vec![
196                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,
197                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,
198                0, 0, 0, 0, 0, 0, 0, 0
199            ],
200            Hash64::default().to_bytes_vector(),
201        );
202    }
203
204    #[test]
205    fn test_hash_computation() {
206        assert_eq!(
207            unwrap!(Hash64::from_hex(
208                "9B71D224BD62F3785D96D46AD3EA3D73319BFBC2890CAADAE2DFF72519673CA72323C3D99BA5C11D7C7ACC6E14B8C5DA0C4663475C2E5C3ADEF46F73BCDEC043"
209            )),
210            Hash64::sha512(b"hello"),
211        );
212    }
213
214    #[test]
215    fn test_hash_from_hex() {
216        assert_eq!(
217            Ok(Hash64::default()),
218            Hash64::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
219        );
220        assert_eq!(
221            Err(BaseConversionError::InvalidLength {
222                expected: 128,
223                found: 130,
224            }),
225            Hash64::from_hex("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
226        );
227        assert_eq!(
228            Err(BaseConversionError::InvalidCharacter {
229                character: '_',
230                offset: 0,
231            }),
232            Hash64::from_hex("_0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
233        );
234        assert_eq!(
235            Err(BaseConversionError::InvalidCharacter {
236                character: '_',
237                offset: 1,
238            }),
239            Hash64::from_hex("0_000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
240        );
241    }
242}