fog_crypto/
hash.rs

1//! Cryptographic hashing.
2//!
3//! This module lets you create a cryptographic hash from a byte stream. Cryptographic hashes can
4//! be used to uniquely identify a data sequence. They can be passed to an
5//! [`IdentityKey`](crate::identity::IdentityKey) to be signed.
6//!
7//! # Example
8//!
9//! ```
10//! # use fog_crypto::hash::*;
11//! // Create a new hash from raw bytes
12//! let hash = Hash::new(b"I am the entire data sequence");
13//! println!("Hash(Base58): {}", hash);
14//!
15//! // Create a hash by feeding in bytes repeatedly
16//! let mut hash_state = HashState::new();
17//! hash_state.update(b"I am the first part of a data sequence");
18//! hash_state.update(b"And I am their sibling, the second part of a data sequence");
19//! let hash = hash_state.finalize();
20//! println!("Hash(Base58): {}", hash);
21//! ```
22
23use crate::error::CryptoError;
24
25use std::{convert::{TryFrom, TryInto}, fmt};
26
27use blake2::{
28    Digest,
29    Blake2b,
30    digest::consts::U32,
31};
32
33use subtle::{Choice, ConstantTimeEq};
34
35/// Default Hash algorithm version.
36pub const DEFAULT_HASH_VERSION: u8 = 1;
37
38/// Minimum accepted Hash algorithm version.
39pub const MIN_HASH_VERSION: u8 = 1;
40
41/// Maximum accepted Hash algorithm version.
42pub const MAX_HASH_VERSION: u8 = 1;
43
44const V1_DIGEST_SIZE: usize = 32;
45type V1Blake = Blake2b<U32>;
46
47/// Maximum size that a hash could be. This may change when versions increment.
48pub const MAX_HASH_LEN: usize = 1 + V1_DIGEST_SIZE;
49
50/// Crytographically secure hash of data.
51///
52/// Offers constant time equality check (non-constant time ordinal checks). A version byte is used
53/// to indicate what hash algorithm should be used.  Uses base58 encoding when displayed, unless
54/// overridden with hex formatting or debug formatting.
55///
56/// # Supported Versions
57/// - 1: Blake2B hash with 32 bytes of digest
58///
59/// # Example
60/// ```
61/// # use fog_crypto::hash::*;
62/// // Create a new hash from raw bytes
63/// let hash = Hash::new(b"I am the entire data sequence");
64/// println!("Hash(Base58): {}", hash);
65///
66/// ```
67#[derive(Clone)]
68pub struct Hash {
69    data: [u8; MAX_HASH_LEN],
70}
71
72impl Hash {
73    /// Create a new hash from raw data, using the recommended algorithm.
74    pub fn new(data: impl AsRef<[u8]>) -> Self {
75        Self::with_version(data, DEFAULT_HASH_VERSION).unwrap()
76    }
77
78    /// Create a hash with a specific algorithm version. You should avoid this except when working
79    /// through a upgrade process, where you may briefly need to support more than one version.
80    /// Fails if the version isn't supported.
81    pub fn with_version(data: impl AsRef<[u8]>, version: u8) -> Result<Self, CryptoError> {
82        let mut state = HashState::with_version(version)?;
83        state.update(data);
84        Ok(state.finalize())
85    }
86
87    /// Algorithm version associated with this hash.
88    pub fn version(&self) -> u8 {
89        self.data[0]
90    }
91
92    /// The raw digest from the hash, without the version byte.
93    pub fn digest(&self) -> &[u8] {
94        &self.data[1..]
95    }
96
97    /// Attempt to parse a Base58-encoded hash type. Fails if the string isn't valid Base58 or the
98    /// hash itself isn't valid.
99    pub fn from_base58(s: &str) -> Result<Self, CryptoError> {
100        let raw = bs58::decode(s)
101            .into_vec()
102            .or(Err(CryptoError::BadFormat("Not valid Base58")))?;
103        Self::try_from(&raw[..])
104    }
105
106    /// Encode the hash as a Base58 string.
107    pub fn to_base58(&self) -> String {
108        bs58::encode(&self.data).into_string()
109    }
110}
111
112impl TryFrom<&[u8]> for Hash {
113    type Error = CryptoError;
114
115    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
116        let &version = value.first().ok_or(CryptoError::BadLength {
117            step: "get hash version",
118            actual: 0,
119            expected: 1,
120        })?;
121
122        // Version check
123        if version < MIN_HASH_VERSION || version > MAX_HASH_VERSION {
124            return Err(CryptoError::UnsupportedVersion(version));
125        }
126
127        // Length check
128        let data: [u8; MAX_HASH_LEN] = value.try_into().map_err(|_| {
129            CryptoError::BadLength {
130                step: "get hash digest (with version)",
131                actual: value.len(),
132                expected: 1 + V1_DIGEST_SIZE,
133            }
134        })?;
135
136        Ok(Self { data })
137    }
138}
139
140impl std::convert::AsRef<[u8]> for Hash {
141    fn as_ref(&self) -> &[u8] {
142        &self.data[..]
143    }
144}
145
146impl ConstantTimeEq for Hash {
147    fn ct_eq(&self, other: &Self) -> Choice {
148        self.data[..].ct_eq(&other.data[..])
149    }
150}
151
152impl PartialEq for Hash {
153    fn eq(&self, other: &Self) -> bool {
154        self.ct_eq(other).into()
155    }
156}
157
158impl Eq for Hash {}
159
160// Not constant time, as no cryptographic operation requires Ord. This is solely for ordering in a
161// BTree
162use std::cmp::Ordering;
163impl std::cmp::Ord for Hash {
164    fn cmp(&self, other: &Hash) -> Ordering {
165        self.data.cmp(&other.data)
166    }
167}
168
169impl std::cmp::PartialOrd for Hash {
170    fn partial_cmp(&self, other: &Hash) -> Option<Ordering> {
171        Some(self.cmp(other))
172    }
173}
174
175impl fmt::Debug for Hash {
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        let (version, digest) = self.data.split_first().unwrap();
178        f.debug_struct("Hash")
179            .field("version", version)
180            .field("digest", &digest)
181            .finish()
182    }
183}
184
185impl fmt::Display for Hash {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        write!(f, "{}", self.to_base58())
188    }
189}
190
191impl fmt::LowerHex for Hash {
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        for byte in self.data.iter() {
194            write!(f, "{:x}", byte)?;
195        }
196        Ok(())
197    }
198}
199
200impl fmt::UpperHex for Hash {
201    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202        for byte in self.data.iter() {
203            write!(f, "{:X}", byte)?;
204        }
205        Ok(())
206    }
207}
208
209impl std::hash::Hash for Hash {
210    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
211        self.data.hash(state);
212    }
213}
214
215/// A hasher that can incrementally take in data and produce a hash at any time.
216///
217/// # Example
218///
219/// ```
220/// // Create a hash by feeding in bytes repeatedly
221/// # use fog_crypto::hash::*;
222/// let mut hash_state = HashState::new();
223/// hash_state.update(b"I am the first part of a data sequence");
224/// let hash_first = hash_state.hash(); // Produce a hash of just the first part
225/// hash_state.update(b"And I am their sibling, the second part of a data sequence");
226/// let hash_full = hash_state.finalize(); // Consume the HashState
227/// println!("hash_first(Base58): {}", hash_first);
228/// println!("hash_full(Base58): {}", hash_full);
229/// ```
230#[derive(Clone)]
231pub struct HashState {
232    state: V1Blake,
233}
234
235impl HashState {
236    /// Initialize a new hasher.
237    pub fn new() -> HashState {
238        Self::with_version(DEFAULT_HASH_VERSION).unwrap()
239    }
240
241    /// Initialize a new hasher with a specific algorithm version. You should avoid this except
242    /// when working through an upgrade process, where you may briefly need to support more than
243    /// one version. Fails if the version isn't supported.
244    pub fn with_version(version: u8) -> Result<HashState, CryptoError> {
245        if version > MAX_HASH_VERSION || version < MIN_HASH_VERSION {
246            return Err(CryptoError::UnsupportedVersion(version));
247        }
248        let state = V1Blake::new();
249        Ok(HashState { state })
250    }
251
252    /// Get the version of hash that this hasher will produce on completion.
253    pub fn version(&self) -> u8 {
254        1u8
255    }
256
257    /// Update the hasher with new input data.
258    pub fn update(&mut self, data: impl AsRef<[u8]>) {
259        self.state.update(data);
260    }
261
262    /// Get the hash of the data fed into the algorithm so far.
263    pub fn hash(&self) -> Hash {
264        self.clone().finalize()
265    }
266
267    /// Finalize the hasher and produce a hash. Functions like `hash()` but consumes the state.
268    pub fn finalize(self) -> Hash {
269        let mut data = [0u8; MAX_HASH_LEN];
270        data[0] = 1u8;
271        let hash = self.state.finalize();
272        data[1..].copy_from_slice(&hash);
273        Hash { data }
274    }
275}
276
277impl Default for HashState {
278    fn default() -> Self {
279        Self::new()
280    }
281}
282
283impl fmt::Debug for HashState {
284    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
285        formatter
286            .debug_struct("HashState")
287            .field("version", &self.version())
288            .finish()
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use hex;
296    use serde_json::{self, Value};
297    use std::fs;
298
299    #[test]
300    fn hash_vectors() {
301        let file_ref = fs::File::open("test-resources/blake2b-test-vectors.json").unwrap();
302        let json_ref: Value = serde_json::from_reader(file_ref).unwrap();
303
304        for vector in json_ref.as_array().unwrap().iter() {
305            let ref_hash = hex::decode(vector["out"].as_str().unwrap()).unwrap();
306            let ref_input = hex::decode(vector["input"].as_str().unwrap()).unwrap();
307            let h = Hash::new(&ref_input[..]);
308            let mut state: HashState = HashState::new();
309            state.update(&ref_input[..]);
310            let h2 = state.hash();
311            let h3 = state.finalize();
312            assert_eq!(h.version(), 1u8);
313            assert_eq!(h.digest(), &ref_hash[..]);
314            assert_eq!(h2.version(), 1u8);
315            assert_eq!(h2.digest(), &ref_hash[..]);
316            assert_eq!(h3.version(), 1u8);
317            assert_eq!(h3.digest(), &ref_hash[..]);
318            let v = Vec::from(h.as_ref());
319            let hd = Hash::try_from(&v[..]).unwrap();
320            assert_eq!(h, hd);
321        }
322    }
323
324    #[test]
325    fn bad_version() {
326        let hash = Hash::new(b"I am a message, being hashed.");
327        let mut enc = Vec::from(hash.as_ref());
328        enc[0] = 99u8;
329        let result = Hash::try_from(&enc[..]);
330        assert!(result.is_err());
331        enc[0] = 0u8;
332        let result = Hash::try_from(&enc[..]);
333        assert!(result.is_err());
334    }
335
336    #[test]
337    fn edge_cases() {
338        match Hash::with_version([1, 2], 0).unwrap_err() {
339            CryptoError::UnsupportedVersion(v) => {
340                assert_eq!(v, 0, "UnsupportedVersion should have been 0");
341            }
342            _ => panic!("New hash should always fail on version 0"),
343        };
344        match HashState::with_version(0).unwrap_err() {
345            CryptoError::UnsupportedVersion(v) => {
346                assert_eq!(v, 0, "UnsupportedVersion should have been 0");
347            }
348            _ => panic!("HashState should always fail on version 0"),
349        };
350        let digest =
351            hex::decode("8b57a796a5d07cb04cc1614dfc2acb3f73edc712d7f433619ca3bbe66bb15f49")
352                .unwrap();
353        let h = Hash::new(hex::decode("00010203040506070809").unwrap());
354        assert_eq!(h.version(), 1);
355        assert_eq!(h.digest(), &digest[..]);
356    }
357
358    #[test]
359    fn base58() {
360        use rand::prelude::*;
361        let mut rng = rand::thread_rng();
362
363        // Golden test case
364        let h = Hash::new(b"I am data, about to be hashed.");
365        let b58 = h.to_base58();
366        let expected = "PnQZwqcH74g1gGpsRbPpzpPqTaHU5PELxrwAosE9MWxM";
367        let eq = b58 == expected;
368        if !eq {
369            println!("Base58 actual:   {}", b58);
370            println!("Base58 expected: {}", expected);
371        }
372        assert!(eq);
373        let h2 = Hash::from_base58(&b58).unwrap();
374        let eq = h == h2;
375        if !eq {
376            println!("in:  {}", h);
377            println!("out: {}", h2);
378        }
379        assert!(eq);
380
381        // Random test cases
382        for _ in 0..1000 {
383            let mut v: Vec<u8> = Vec::with_capacity(32);
384            for _ in 0..32 {
385                v.push(rng.gen());
386            }
387            let h = Hash::new(&v[..]);
388            let b58 = h.to_base58();
389            let h2 = Hash::from_base58(&b58).unwrap();
390            let eq = h == h2;
391            if !eq {
392                println!("in:  {}", h);
393                println!("out: {}", h2);
394            }
395            assert!(eq);
396        }
397    }
398}