ouisync/crypto/
hash.rs

1pub use blake3::traits::digest::Digest;
2
3use generic_array::{typenum::U32, GenericArray};
4use serde::{Deserialize, Serialize};
5use std::{
6    array::TryFromSliceError,
7    collections::{BTreeMap, BTreeSet},
8    fmt,
9    ops::Deref,
10    slice,
11};
12
13#[cfg(test)]
14use rand::{
15    distributions::{Distribution, Standard},
16    Rng,
17};
18
19/// Wrapper for a 256-bit hash digest. Also implements friendly formatting.
20#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
21#[repr(transparent)]
22#[serde(from = "[u8; Self::SIZE]", into = "[u8; Self::SIZE]")]
23pub struct Hash(blake3::Hash);
24
25impl Hash {
26    pub const SIZE: usize = blake3::OUT_LEN;
27}
28
29impl From<[u8; Self::SIZE]> for Hash {
30    fn from(array: [u8; Self::SIZE]) -> Self {
31        Hash(array.into())
32    }
33}
34
35impl From<GenericArray<u8, U32>> for Hash {
36    fn from(array: GenericArray<u8, U32>) -> Self {
37        let array: [u8; Self::SIZE] = array.into();
38        Hash(array.into())
39    }
40}
41
42impl TryFrom<&'_ [u8]> for Hash {
43    type Error = TryFromSliceError;
44
45    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
46        let array: [u8; Self::SIZE] = slice.try_into()?;
47        Ok(Self(array.into()))
48    }
49}
50
51impl From<Hash> for [u8; Hash::SIZE] {
52    fn from(hash: Hash) -> [u8; Hash::SIZE] {
53        hash.0.into()
54    }
55}
56
57impl AsRef<[u8]> for Hash {
58    fn as_ref(&self) -> &[u8] {
59        self.0.as_bytes().as_slice()
60    }
61}
62
63impl PartialOrd for Hash {
64    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
65        Some(self.cmp(other))
66    }
67}
68
69impl Ord for Hash {
70    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
71        self.0.as_bytes().cmp(other.0.as_bytes())
72    }
73}
74
75impl fmt::Display for Hash {
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        write!(f, "{self:x}")
78    }
79}
80
81impl fmt::Debug for Hash {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        write!(f, "{self:<8x}")
84    }
85}
86
87impl fmt::LowerHex for Hash {
88    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89        hex_fmt::HexFmt(self.as_ref()).fmt(f)
90    }
91}
92
93impl Hashable for Hash {
94    fn update_hash<S: Digest>(&self, state: &mut S) {
95        self.as_ref().update_hash(state)
96    }
97}
98
99derive_sqlx_traits_for_byte_array_wrapper!(Hash);
100
101#[cfg(test)]
102impl Distribution<Hash> for Standard {
103    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Hash {
104        let array: [u8; Hash::SIZE] = rng.r#gen();
105        Hash(array.into())
106    }
107}
108
109/// Similar to std::hash::Hash, but for cryptographic hashes.
110pub trait Hashable {
111    // Update the hash state.
112    fn update_hash<S: Digest>(&self, state: &mut S);
113
114    // This is needed due to lack of specialization in stable rust.
115    fn update_hash_slice<S>(slice: &[Self], state: &mut S)
116    where
117        S: Digest,
118        Self: Sized,
119    {
120        for item in slice {
121            item.update_hash(state)
122        }
123    }
124
125    // Hash self using the given hashing algorithm.
126    fn hash_with<H>(&self) -> Hash
127    where
128        H: Digest<OutputSize = U32>,
129    {
130        let mut h = H::new();
131        self.update_hash(&mut h);
132        h.finalize().into()
133    }
134
135    // Hash self using the default hashing algorithm (BLAKE3).
136    fn hash(&self) -> Hash {
137        self.hash_with::<blake3::Hasher>()
138    }
139}
140
141impl<T> Hashable for &'_ T
142where
143    T: Hashable + ?Sized,
144{
145    fn update_hash<S: Digest>(&self, state: &mut S) {
146        (**self).update_hash(state);
147    }
148}
149
150impl Hashable for u8 {
151    fn update_hash<S: Digest>(&self, state: &mut S) {
152        state.update(slice::from_ref(self))
153    }
154
155    fn update_hash_slice<S: Digest>(slice: &[Self], state: &mut S) {
156        state.update(slice)
157    }
158}
159
160impl Hashable for u32 {
161    fn update_hash<S: Digest>(&self, state: &mut S) {
162        state.update(self.to_le_bytes())
163    }
164}
165
166impl Hashable for u64 {
167    fn update_hash<S: Digest>(&self, state: &mut S) {
168        state.update(self.to_le_bytes())
169    }
170}
171
172impl<T> Hashable for [T]
173where
174    T: Hashable,
175{
176    fn update_hash<S: Digest>(&self, state: &mut S) {
177        (self.len() as u64).update_hash(state);
178        Hashable::update_hash_slice(self, state);
179    }
180}
181
182impl<T, const N: usize> Hashable for [T; N]
183where
184    T: Hashable,
185{
186    fn update_hash<S: Digest>(&self, state: &mut S) {
187        self.as_slice().update_hash(state)
188    }
189}
190
191impl<T> Hashable for Vec<T>
192where
193    T: Hashable,
194{
195    fn update_hash<S: Digest>(&self, state: &mut S) {
196        self.as_slice().update_hash(state);
197    }
198}
199
200impl<K, V> Hashable for BTreeMap<K, V>
201where
202    K: Hashable,
203    V: Hashable,
204{
205    fn update_hash<S: Digest>(&self, state: &mut S) {
206        (self.len() as u64).update_hash(state);
207        for (key, value) in self {
208            key.update_hash(state);
209            value.update_hash(state);
210        }
211    }
212}
213
214impl<T> Hashable for BTreeSet<T>
215where
216    T: Hashable,
217{
218    fn update_hash<S: Digest>(&self, state: &mut S) {
219        (self.len() as u64).update_hash(state);
220        for item in self {
221            item.update_hash(state);
222        }
223    }
224}
225
226// NOTE: `Hashable` is purposefully not implemented for `HashMap` / `HashSet` because the resulting
227// hash would be dependent on the iteration order which in case of `HashMap` / `HashSet` is often
228// random. Thus two maps/set that compare as equal would produce different hashes.
229
230macro_rules! impl_hashable_for_tuple {
231    ($($name:ident)+) => {
232        impl<$($name: Hashable),+> Hashable for ($($name,)+) {
233            #[allow(non_snake_case)]
234            fn update_hash<S: Digest>(&self, state: &mut S) {
235                let ($($name,)+) = self;
236                $($name.update_hash(state);)+
237            }
238        }
239    }
240}
241
242impl_hashable_for_tuple!(T0 T1);
243impl_hashable_for_tuple!(T0 T1 T2);
244impl_hashable_for_tuple!(T0 T1 T2 T3);
245
246/// Wrapper that caches the hash of the inner value.
247pub(crate) struct CacheHash<T> {
248    owner: T,
249    hash: Hash,
250}
251
252impl<T> CacheHash<T> {
253    pub fn into_inner(self) -> T {
254        self.owner
255    }
256}
257
258impl<T> Deref for CacheHash<T> {
259    type Target = T;
260
261    fn deref(&self) -> &Self::Target {
262        &self.owner
263    }
264}
265
266impl<T> From<T> for CacheHash<T>
267where
268    T: Hashable,
269{
270    fn from(owner: T) -> Self {
271        let hash = owner.hash();
272        Self { owner, hash }
273    }
274}
275
276impl<T> Clone for CacheHash<T>
277where
278    T: Clone,
279{
280    fn clone(&self) -> Self {
281        Self {
282            owner: self.owner.clone(),
283            hash: self.hash,
284        }
285    }
286}
287
288impl<T> Hashable for CacheHash<T>
289where
290    T: Hashable,
291{
292    fn update_hash<S: Digest>(&self, state: &mut S) {
293        self.owner.update_hash(state)
294    }
295
296    fn hash(&self) -> Hash {
297        self.hash
298    }
299}
300
301impl<T: fmt::Debug> fmt::Debug for CacheHash<T> {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        self.owner.fmt(f)
304    }
305}
306
307#[cfg(test)]
308mod test_utils {
309    use super::Hash;
310    use proptest::{
311        arbitrary::{any, Arbitrary},
312        array::UniformArrayStrategy,
313        num,
314        strategy::{Map, NoShrink, Strategy},
315    };
316
317    impl Arbitrary for Hash {
318        type Parameters = ();
319        type Strategy = Map<
320            NoShrink<UniformArrayStrategy<num::u8::Any, [u8; Hash::SIZE]>>,
321            fn([u8; Hash::SIZE]) -> Self,
322        >;
323
324        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
325            any::<[u8; Hash::SIZE]>().no_shrink().prop_map(Hash::from)
326        }
327    }
328}