Skip to main content

async_hash/
lib.rs

1//! Provides traits `Hash`, `HashStream`, and `HashTryStream` for SHA-2 hashing data
2//! which must be accessed asynchronously, e.g. a [`Stream`] or database table.
3//!
4//! `Hash` is implemented for standard Rust types:
5//!
6//!  - **Primitive types**:
7//!    - bool
8//!    - i8, i16, i32, i64, i128, isize
9//!    - u8, u16, u32, u64, u128, usize
10//!    - f32, f64
11//!    - &str
12//!    - String
13//!  - **Common standard library types**:
14//!    - Option\<T\>
15//!    - PhantomData\<T\>
16//!  - **Compound types**:
17//!    - \[T; 0\] through \[T; 32\]
18//!    - tuples up to size 16
19//!  - **Collection types**:
20//!    - BTreeMap\<K, V\>
21//!    - BTreeSet\<T\>
22//!    - BinaryHeap\<T\>
23//!    - LinkedList\<T\>
24//!    - VecDeque\<T\>
25//!    - Vec\<T\>
26//!  - **Other types**:
27//!    - SmallVec\<V\> (requires the `smallvec` feature flag)
28//!
29//! **IMPORTANT**: hashing is order-dependent. Do not implement the traits in this crate for
30//! any data structure which does not have a consistent order. Consider using the [`collate`] crate
31//! if you need to use a type which does not implement [`Ord`].
32//!
33//! [`collate`]: http://docs.rs/collate
34
35use std::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque};
36
37use futures::future::{FutureExt, TryFutureExt};
38use futures::stream::{Stream, StreamExt, TryStream, TryStreamExt};
39
40pub use sha2::digest::generic_array;
41pub use sha2::digest::{Digest, Output};
42pub use sha2::Sha256;
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn hash_equivalence_string_and_bytes() {
50        let s = "hello";
51        let from_str = Hash::<Sha256>::hash(s);
52        let from_string = Hash::<Sha256>::hash(s.to_string());
53
54        assert_eq!(from_str, from_string);
55    }
56
57    #[test]
58    fn hash_empty_matches_default() {
59        let empty_vec: Vec<u8> = Vec::new();
60        let empty_str = "";
61        let empty_opt: Option<u8> = None;
62
63        assert_eq!(Hash::<Sha256>::hash(empty_vec), default_hash::<Sha256>());
64        assert_ne!(Hash::<Sha256>::hash(empty_str), default_hash::<Sha256>());
65        assert_eq!(Hash::<Sha256>::hash(empty_opt), default_hash::<Sha256>());
66    }
67
68    #[test]
69    fn hash_sequence_is_order_sensitive() {
70        let a = vec![1u8, 2, 3];
71        let b = vec![3u8, 2, 1];
72
73        let hash_a = Hash::<Sha256>::hash(a);
74        let hash_b = Hash::<Sha256>::hash(b);
75
76        assert_ne!(hash_a, hash_b);
77    }
78}
79
80/// Trait to compute a SHA-2 hash using the digest type `D`
81pub trait Hash<D: Digest>: Sized {
82    /// Compute the SHA-2 hash of this value
83    fn hash(self) -> Output<D>;
84}
85
86impl<D: Digest> Hash<D> for () {
87    fn hash(self) -> Output<D> {
88        default_hash::<D>()
89    }
90}
91
92impl<D: Digest> Hash<D> for bool {
93    fn hash(self) -> Output<D> {
94        D::digest([self as u8])
95    }
96}
97
98macro_rules! hash_number {
99    ($n:literal, $ty:ty) => {
100        impl<D: Digest> Hash<D> for $ty {
101            fn hash(self) -> Output<D> {
102                D::digest(self.to_be_bytes())
103            }
104        }
105    };
106}
107
108hash_number!(4, f32);
109hash_number!(8, f64);
110hash_number!(1, i8);
111hash_number!(2, i16);
112hash_number!(4, i32);
113hash_number!(8, i64);
114hash_number!(16, i128);
115hash_number!(1, u8);
116hash_number!(2, u16);
117hash_number!(4, u32);
118hash_number!(8, u64);
119hash_number!(16, u128);
120
121impl<D: Digest> Hash<D> for isize {
122    fn hash(self) -> Output<D> {
123        Hash::<D>::hash(self as i64)
124    }
125}
126
127impl<D: Digest> Hash<D> for usize {
128    fn hash(self) -> Output<D> {
129        Hash::<D>::hash(self as u64)
130    }
131}
132
133impl<D: Digest> Hash<D> for &str {
134    fn hash(self) -> Output<D> {
135        D::digest(self.as_bytes())
136    }
137}
138
139impl<D: Digest> Hash<D> for String {
140    fn hash(self) -> Output<D> {
141        Hash::<D>::hash(self.as_str())
142    }
143}
144
145impl<D: Digest> Hash<D> for &String {
146    fn hash(self) -> Output<D> {
147        Hash::<D>::hash(self.as_str())
148    }
149}
150
151impl<D: Digest, T: Hash<D>> Hash<D> for Option<T> {
152    fn hash(self) -> Output<D> {
153        if let Some(value) = self {
154            value.hash()
155        } else {
156            default_hash::<D>()
157        }
158    }
159}
160
161macro_rules! hash_tuple {
162    ($($len:expr => ($($n:tt $name:ident)+))+) => {
163        $(
164            impl<D: Digest, $($name),+> Hash<D> for ($($name,)+)
165            where
166                $($name: Hash<D>,)+
167            {
168                fn hash(self) -> Output<D> {
169                    let mut hasher = D::new();
170                    $(
171                        let hash = self.$n.hash();
172                        hasher.update(hash);
173                    )+
174                    hasher.finalize()
175                }
176            }
177
178            impl<'a, D: Digest, $($name),+> Hash<D> for &'a ($($name,)+)
179            where
180                $(&'a $name: Hash<D>,)+
181            {
182                fn hash(self) -> Output<D> {
183                    let mut hasher = D::new();
184                    $(
185                        let hash = self.$n.hash();
186                        hasher.update(hash);
187                    )+
188                    hasher.finalize()
189                }
190            }
191        )+
192    }
193}
194
195hash_tuple! {
196    1 => (0 T0)
197    2 => (0 T0 1 T1)
198    3 => (0 T0 1 T1 2 T2)
199    4 => (0 T0 1 T1 2 T2 3 T3)
200    5 => (0 T0 1 T1 2 T2 3 T3 4 T4)
201    6 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5)
202    7 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6)
203    8 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7)
204    9 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8)
205    10 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9)
206    11 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10)
207    12 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11)
208    13 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12)
209    14 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13)
210    15 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14)
211    16 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15)
212}
213
214impl<D: Digest, T: Hash<D>> Hash<D> for [T; 0] {
215    fn hash(self) -> Output<D> {
216        default_hash::<D>()
217    }
218}
219
220macro_rules! hash_array {
221    ($($len:tt)+) => {
222        $(
223            impl<D: Digest, T: Hash<D>> Hash<D> for [T; $len] {
224                fn hash(self) -> Output<D> {
225                    if self.is_empty() {
226                        return default_hash::<D>();
227                    }
228
229                    let mut hasher = D::new();
230
231                    for item in self {
232                        hasher.update(item.hash());
233                    }
234
235                    hasher.finalize()
236                }
237            }
238        )+
239    }
240}
241
242hash_array! {
243    1 2 3 4 5 6 7 8 9 10
244    11 12 13 14 15 16 17 18 19 20
245    21 22 23 24 25 26 27 28 29 30
246    31 32
247}
248
249macro_rules! hash_seq {
250    ($ty:ty) => {
251        impl<D: Digest, T: Hash<D>> Hash<D> for $ty {
252            fn hash(self) -> Output<D> {
253                if self.is_empty() {
254                    return default_hash::<D>();
255                }
256
257                let mut hasher = D::new();
258
259                for item in self.into_iter() {
260                    hasher.update(item.hash());
261                }
262
263                hasher.finalize()
264            }
265        }
266
267        impl<'a, D, T> Hash<D> for &'a $ty
268        where
269            D: Digest,
270            &'a T: Hash<D>,
271        {
272            fn hash(self) -> Output<D> {
273                if self.is_empty() {
274                    return default_hash::<D>();
275                }
276
277                let mut hasher = D::new();
278                for item in self.into_iter() {
279                    hasher.update(item.hash());
280                }
281                hasher.finalize()
282            }
283        }
284    };
285}
286
287hash_seq!(BTreeSet<T>);
288hash_seq!(BinaryHeap<T>);
289hash_seq!(LinkedList<T>);
290hash_seq!(Vec<T>);
291hash_seq!(VecDeque<T>);
292
293#[cfg(feature = "smallvec")]
294impl<const N: usize, D: Digest, T> Hash<D> for smallvec::SmallVec<[T; N]>
295where
296    [T; N]: smallvec::Array,
297    <smallvec::SmallVec<[T; N]> as IntoIterator>::Item: Hash<D>,
298{
299    fn hash(self) -> Output<D> {
300        if self.is_empty() {
301            return default_hash::<D>();
302        }
303
304        let mut hasher = D::new();
305
306        for item in self.into_iter() {
307            hasher.update(item.hash());
308        }
309
310        hasher.finalize()
311    }
312}
313
314#[cfg(feature = "smallvec")]
315impl<'a, const N: usize, D: Digest, T> Hash<D> for &'a smallvec::SmallVec<[T; N]>
316where
317    [T; N]: smallvec::Array,
318    <&'a smallvec::SmallVec<[T; N]> as IntoIterator>::Item: Hash<D>,
319{
320    fn hash(self) -> Output<D> {
321        if self.is_empty() {
322            return default_hash::<D>();
323        }
324
325        let mut hasher = D::new();
326
327        for item in self.into_iter() {
328            hasher.update(item.hash());
329        }
330
331        hasher.finalize()
332    }
333}
334
335impl<D: Digest, K: Hash<D>, V: Hash<D>> Hash<D> for BTreeMap<K, V> {
336    fn hash(self) -> Output<D> {
337        if self.is_empty() {
338            return default_hash::<D>();
339        }
340
341        let mut hasher = D::new();
342
343        for item in self {
344            hasher.update(item.hash());
345        }
346
347        hasher.finalize()
348    }
349}
350
351impl<'a, D, K, V> Hash<D> for &'a BTreeMap<K, V>
352where
353    D: Digest,
354    &'a K: Hash<D>,
355    &'a V: Hash<D>,
356{
357    fn hash(self) -> Output<D> {
358        if self.is_empty() {
359            return default_hash::<D>();
360        }
361
362        let mut hasher = D::new();
363
364        for item in self {
365            hasher.update(item.hash());
366        }
367
368        hasher.finalize()
369    }
370}
371
372/// Hash a [`Stream`]
373pub async fn hash_stream<D, T, S>(stream: S) -> Output<D>
374where
375    D: Digest,
376    T: Hash<D>,
377    S: Stream<Item = T>,
378{
379    stream
380        .map(|item| Hash::<D>::hash(item))
381        .fold(D::new(), |mut hasher, hash| {
382            hasher.update(hash);
383            futures::future::ready(hasher)
384        })
385        .map(|hasher| hasher.finalize())
386        .await
387}
388
389/// Hash a [`TryStream`]
390pub async fn hash_try_stream<D, T, E, S>(stream: S) -> Result<Output<D>, E>
391where
392    D: Digest,
393    T: Hash<D>,
394    E: std::error::Error,
395    S: TryStream<Ok = T, Error = E>,
396{
397    stream
398        .map_ok(|item| Hash::<D>::hash(item))
399        .try_fold(D::new(), |mut hasher, hash| {
400            hasher.update(hash);
401            futures::future::ready(Ok(hasher))
402        })
403        .map_ok(|hasher| hasher.finalize())
404        .await
405}
406
407/// Construct an empty hash
408pub fn default_hash<D: Digest>() -> Output<D> {
409    generic_array::GenericArray::default()
410}