cml_crypto/chain_crypto/
digest.rs

1//! module to provide some handy interfaces atop the hashes so we have
2//! the common interfaces for the project to work with.
3
4use std::convert::TryFrom;
5use std::hash::{Hash, Hasher};
6use std::str::FromStr;
7use std::{error, fmt, result};
8
9use cryptoxide::blake2b::Blake2b;
10use cryptoxide::digest::Digest as _;
11use cryptoxide::sha3;
12use hex::FromHexError;
13
14use crate::typed_bytes::ByteSlice;
15
16use crate::chain_crypto::bech32::{self, Bech32};
17use crate::chain_crypto::hash::{Blake2b256, Sha3_256};
18
19#[derive(Debug, PartialEq, Clone)]
20pub enum Error {
21    InvalidDigestSize { got: usize, expected: usize },
22    InvalidHexEncoding(FromHexError),
23}
24impl fmt::Display for Error {
25    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26        match self {
27            Error::InvalidDigestSize { got: sz, expected } => write!(
28                f,
29                "invalid digest size, expected {expected} but received {sz} bytes."
30            ),
31            Error::InvalidHexEncoding(_) => write!(f, "invalid hex encoding for digest value"),
32        }
33    }
34}
35
36impl error::Error for Error {
37    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
38        match self {
39            Error::InvalidDigestSize {
40                got: _,
41                expected: _,
42            } => None,
43            Error::InvalidHexEncoding(err) => Some(err),
44        }
45    }
46}
47
48impl From<FromHexError> for Error {
49    fn from(err: FromHexError) -> Self {
50        Error::InvalidHexEncoding(err)
51    }
52}
53
54#[derive(Debug, Copy, Clone)]
55pub struct TryFromSliceError(());
56
57impl fmt::Display for TryFromSliceError {
58    #[inline]
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        fmt::Display::fmt("could not convert slice to digest", f)
61    }
62}
63
64pub trait DigestAlg {
65    const HASH_SIZE: usize;
66    type DigestData: Clone + PartialEq + Hash + Send + AsRef<[u8]>;
67    type DigestContext: Clone;
68
69    fn try_from_slice(slice: &[u8]) -> Result<Self::DigestData, Error>;
70    fn new() -> Self::DigestContext;
71    fn append_data(ctx: &mut Self::DigestContext, data: &[u8]);
72    fn finalize(ctx: Self::DigestContext) -> Self::DigestData;
73}
74
75impl DigestAlg for Blake2b256 {
76    const HASH_SIZE: usize = 32;
77    type DigestData = [u8; Self::HASH_SIZE];
78    type DigestContext = Blake2b;
79
80    fn try_from_slice(slice: &[u8]) -> Result<Self::DigestData, Error> {
81        if slice.len() == Self::HASH_SIZE {
82            let mut out = [0u8; Self::HASH_SIZE];
83            out.copy_from_slice(slice);
84            Ok(out)
85        } else {
86            Err(Error::InvalidDigestSize {
87                expected: Self::HASH_SIZE,
88                got: slice.len(),
89            })
90        }
91    }
92
93    fn new() -> Self::DigestContext {
94        Blake2b::new(Self::HASH_SIZE)
95    }
96
97    fn append_data(ctx: &mut Self::DigestContext, data: &[u8]) {
98        ctx.input(data)
99    }
100
101    fn finalize(mut ctx: Self::DigestContext) -> Self::DigestData {
102        let mut out: Self::DigestData = [0; Self::HASH_SIZE];
103        ctx.result(&mut out);
104        out
105    }
106}
107
108impl DigestAlg for Sha3_256 {
109    const HASH_SIZE: usize = 32;
110    type DigestData = [u8; Self::HASH_SIZE];
111    type DigestContext = sha3::Sha3_256;
112
113    fn try_from_slice(slice: &[u8]) -> Result<Self::DigestData, Error> {
114        if slice.len() == Self::HASH_SIZE {
115            let mut out = [0u8; Self::HASH_SIZE];
116            out.copy_from_slice(slice);
117            Ok(out)
118        } else {
119            Err(Error::InvalidDigestSize {
120                expected: Self::HASH_SIZE,
121                got: slice.len(),
122            })
123        }
124    }
125
126    fn new() -> Self::DigestContext {
127        sha3::Sha3_256::new()
128    }
129
130    fn append_data(ctx: &mut Self::DigestContext, data: &[u8]) {
131        ctx.input(data)
132    }
133
134    fn finalize(mut ctx: Self::DigestContext) -> Self::DigestData {
135        let mut out: Self::DigestData = [0; Self::HASH_SIZE];
136        ctx.result(&mut out);
137        out
138    }
139}
140
141/// A Digest Context for the H digest algorithm
142pub struct Context<H: DigestAlg>(H::DigestContext);
143
144impl<H: DigestAlg> Clone for Context<H> {
145    fn clone(&self) -> Self {
146        Self(self.0.clone())
147    }
148}
149
150impl<H: DigestAlg> Context<H> {
151    /// Create a new digest context
152    pub fn new() -> Self {
153        Self(H::new())
154    }
155
156    /// Append data in the context
157    pub fn append_data(&mut self, data: &[u8]) {
158        H::append_data(&mut self.0, data)
159    }
160
161    /// Finalize a context and create a digest
162    pub fn finalize(self) -> Digest<H> {
163        Digest(H::finalize(self.0))
164    }
165}
166
167impl<H: DigestAlg> Default for Context<H> {
168    fn default() -> Self {
169        Self::new()
170    }
171}
172
173pub struct Digest<H: DigestAlg>(H::DigestData);
174
175impl<H: DigestAlg> Clone for Digest<H> {
176    fn clone(&self) -> Self {
177        Self(self.0.clone())
178    }
179}
180
181macro_rules! define_from_instances {
182    ($hash_ty:ty, $hash_size:expr, $bech32_hrp:expr) => {
183        impl From<Digest<$hash_ty>> for [u8; $hash_size] {
184            fn from(digest: Digest<$hash_ty>) -> Self {
185                digest.0
186            }
187        }
188
189        impl<'a> From<&'a Digest<$hash_ty>> for &'a [u8; $hash_size] {
190            fn from(digest: &'a Digest<$hash_ty>) -> Self {
191                &digest.0
192            }
193        }
194
195        impl From<[u8; $hash_size]> for Digest<$hash_ty> {
196            fn from(bytes: [u8; $hash_size]) -> Self {
197                Digest(bytes)
198            }
199        }
200        impl From<$hash_ty> for Digest<$hash_ty> {
201            fn from(bytes: $hash_ty) -> Self {
202                let out: [u8; $hash_size] = bytes.into();
203                out.into()
204            }
205        }
206        impl Bech32 for Digest<$hash_ty> {
207            const BECH32_HRP: &'static str = $bech32_hrp;
208
209            fn try_from_bech32_str(bech32_str: &str) -> bech32::Result<Self> {
210                let bytes = bech32::try_from_bech32_to_bytes::<Self>(bech32_str)?;
211                Digest::try_from(&bytes[..]).map_err(bech32::Error::data_invalid)
212            }
213
214            fn to_bech32_str(&self) -> String {
215                bech32::to_bech32_from_bytes::<Self>(self.as_ref())
216            }
217        }
218    };
219}
220
221define_from_instances!(Sha3_256, 32, "sha3");
222define_from_instances!(Blake2b256, 32, "blake2b");
223
224unsafe impl<H: DigestAlg> Send for Digest<H> {}
225
226impl<H: DigestAlg> PartialEq for Digest<H> {
227    fn eq(&self, other: &Self) -> bool {
228        self.0 == other.0
229    }
230}
231
232impl<H: DigestAlg> Eq for Digest<H> {}
233
234impl<H: DigestAlg> PartialOrd for Digest<H> {
235    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
236        Some(self.cmp(other))
237    }
238}
239
240impl<H: DigestAlg> Ord for Digest<H> {
241    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
242        self.as_ref().cmp(other.as_ref())
243    }
244}
245
246impl<H: DigestAlg> AsRef<[u8]> for Digest<H> {
247    fn as_ref(&self) -> &[u8] {
248        self.0.as_ref()
249    }
250}
251
252impl<H: DigestAlg> Hash for Digest<H> {
253    fn hash<HA: Hasher>(&self, state: &mut HA) {
254        self.0.hash(state)
255    }
256}
257
258impl<H: DigestAlg> TryFrom<&[u8]> for Digest<H> {
259    type Error = Error;
260    fn try_from(slice: &[u8]) -> Result<Digest<H>, Self::Error> {
261        <H as DigestAlg>::try_from_slice(slice).map(Digest)
262    }
263}
264
265impl<H: DigestAlg> FromStr for Digest<H> {
266    type Err = Error;
267    fn from_str(s: &str) -> result::Result<Digest<H>, Self::Err> {
268        let bytes = hex::decode(s)?;
269        Digest::try_from(&bytes[..])
270    }
271}
272
273impl<H: DigestAlg> fmt::Display for Digest<H> {
274    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275        write!(f, "{}", hex::encode(self.as_ref()))
276    }
277}
278impl<H: DigestAlg> fmt::Debug for Digest<H> {
279    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
280        f.write_str(concat!(stringify!($hash_ty), "(0x"))?;
281        write!(f, "{}", hex::encode(self.as_ref()))?;
282        f.write_str(")")
283    }
284}
285
286impl<H: DigestAlg> Digest<H> {
287    /// Get the digest of a slice of data
288    #[allow(clippy::self_named_constructors)]
289    pub fn digest(slice: &[u8]) -> Self {
290        let mut ctx = Context::new();
291        ctx.append_data(slice);
292        ctx.finalize()
293    }
294}
295
296use std::marker::PhantomData;
297
298/// A typed version of Digest
299pub struct DigestOf<H: DigestAlg, T> {
300    inner: Digest<H>,
301    marker: PhantomData<T>,
302}
303
304unsafe impl<H: DigestAlg, T> Send for DigestOf<H, T> {}
305
306impl<H: DigestAlg, T> Clone for DigestOf<H, T> {
307    fn clone(&self) -> Self {
308        DigestOf {
309            inner: self.inner.clone(),
310            marker: self.marker,
311        }
312    }
313}
314
315impl<H: DigestAlg, T> From<DigestOf<H, T>> for Digest<H> {
316    fn from(d: DigestOf<H, T>) -> Self {
317        d.inner
318    }
319}
320
321impl<H: DigestAlg, T> From<Digest<H>> for DigestOf<H, T> {
322    fn from(d: Digest<H>) -> Self {
323        DigestOf {
324            inner: d,
325            marker: PhantomData,
326        }
327    }
328}
329
330impl<H: DigestAlg, T> PartialEq for DigestOf<H, T> {
331    fn eq(&self, other: &Self) -> bool {
332        self.inner == other.inner
333    }
334}
335
336impl<H: DigestAlg, T> Eq for DigestOf<H, T> {}
337
338impl<H: DigestAlg, T> PartialOrd for DigestOf<H, T> {
339    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
340        Some(self.cmp(other))
341    }
342}
343
344impl<H: DigestAlg, T> Ord for DigestOf<H, T> {
345    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
346        self.inner.cmp(&other.inner)
347    }
348}
349
350impl<H: DigestAlg, T> AsRef<[u8]> for DigestOf<H, T> {
351    fn as_ref(&self) -> &[u8] {
352        self.inner.as_ref()
353    }
354}
355
356impl<H: DigestAlg, T> Hash for DigestOf<H, T> {
357    fn hash<HA: Hasher>(&self, state: &mut HA) {
358        self.inner.hash(state)
359    }
360}
361
362impl<H: DigestAlg, T> TryFrom<&[u8]> for DigestOf<H, T> {
363    type Error = Error;
364    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
365        Digest::<H>::try_from(slice).map(|d| d.into())
366    }
367}
368
369impl<H: DigestAlg, T> FromStr for DigestOf<H, T> {
370    type Err = Error;
371    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
372        Digest::<H>::from_str(s).map(|d| d.into())
373    }
374}
375
376impl<H: DigestAlg, T> fmt::Display for DigestOf<H, T> {
377    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
378        self.inner.fmt(f)
379    }
380}
381impl<H: DigestAlg, T> fmt::Debug for DigestOf<H, T> {
382    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
383        self.inner.fmt(f)
384    }
385}
386
387impl<H: DigestAlg, T> DigestOf<H, T> {
388    /// Coerce a digest of T, to a digest of U
389    pub fn coerce<U>(&self) -> DigestOf<H, U> {
390        DigestOf {
391            inner: self.inner.clone(),
392            marker: PhantomData,
393        }
394    }
395
396    pub fn digest_byteslice(byteslice: &ByteSlice<T>) -> Self {
397        let mut ctx = Context::new();
398        ctx.append_data(byteslice.as_slice());
399        DigestOf {
400            inner: ctx.finalize(),
401            marker: PhantomData,
402        }
403    }
404
405    /// Get the digest of object T, given its AsRef<[u8]> implementation
406    pub fn digest(obj: &T) -> Self
407    where
408        T: AsRef<[u8]>,
409    {
410        let mut ctx = Context::new();
411        ctx.append_data(obj.as_ref());
412        DigestOf {
413            inner: ctx.finalize(),
414            marker: PhantomData,
415        }
416    }
417
418    /// Get the digest of object T, given its serialization function in closure
419    pub fn digest_with<F>(obj: &T, f: F) -> Self
420    where
421        F: FnOnce(&T) -> &[u8],
422    {
423        let mut ctx = Context::new();
424        ctx.append_data(f(obj));
425        DigestOf {
426            inner: ctx.finalize(),
427            marker: PhantomData,
428        }
429    }
430}
431
432macro_rules! typed_define_from_instances {
433    ($hash_ty:ty, $hash_size:expr, $bech32_hrp:expr) => {
434        impl<T> From<DigestOf<$hash_ty, T>> for [u8; $hash_size] {
435            fn from(digest: DigestOf<$hash_ty, T>) -> Self {
436                digest.inner.into()
437            }
438        }
439        impl<'a, T> From<&'a DigestOf<$hash_ty, T>> for &'a [u8; $hash_size] {
440            fn from(digest: &'a DigestOf<$hash_ty, T>) -> Self {
441                (&digest.inner).into()
442            }
443        }
444
445        impl<T> From<[u8; $hash_size]> for DigestOf<$hash_ty, T> {
446            fn from(bytes: [u8; $hash_size]) -> Self {
447                Digest::from(bytes).into()
448            }
449        }
450
451        impl<T> From<$hash_ty> for DigestOf<$hash_ty, T> {
452            fn from(bytes: $hash_ty) -> Self {
453                let out: [u8; $hash_size] = bytes.into();
454                out.into()
455            }
456        }
457        impl<T> Bech32 for DigestOf<$hash_ty, T> {
458            const BECH32_HRP: &'static str = $bech32_hrp;
459
460            fn try_from_bech32_str(bech32_str: &str) -> bech32::Result<Self> {
461                let bytes = bech32::try_from_bech32_to_bytes::<Self>(bech32_str)?;
462                Digest::try_from(&bytes[..])
463                    .map_err(bech32::Error::data_invalid)
464                    .map(|d| d.into())
465            }
466
467            fn to_bech32_str(&self) -> String {
468                bech32::to_bech32_from_bytes::<Self>(self.inner.as_ref())
469            }
470        }
471    };
472}
473
474typed_define_from_instances!(Sha3_256, 32, "sha3");
475typed_define_from_instances!(Blake2b256, 32, "blake2b");