ethdigest/
lib.rs

1//! Implementation of Ethereum digest and hashing for Rust.
2//!
3//! This crate provides a [`Digest`] type for representing an Ethereum 32-byte
4//! digest as well as various Keccak-256 hashing utilities for computing them.
5//!
6//! # Macros
7//!
8//! There are a couple of exported macros for creating compile-time digest
9//! constants:
10//!
11//! - [`digest!`]\: hexadecimal constant
12//! - [`keccak!`]\: compute constant from a pre-image
13//!
14//! Under the hood, they are implemented with `const fn` and do not use
15//! procedural macros.
16//!
17//! # Features
18//!
19//! - **_default_ `std`**: Additional integration with Rust standard library
20//!   types. Notably, this includes [`std::error::Error`] implementation on the
21//!   [`ParseDigestError`] and conversions from [`Vec<u8>`].
22//! - **`serde`**: Serialization traits for the [`serde`] crate. Note that the
23//!   implementation is very much geared towards JSON serialization with
24//!   [`serde_json`].
25//! - **`sha3`**: Use the Rust Crypto Keccak-256 implementation (provided by the
26//!   [`sha3`] crate) instead of the built-in one. Note that the [`keccak!`]
27//!   macro will always use the built-in Keccak-256 implementation for computing
28//!   digests, as [`sha3`] does not expose a `const fn` API.
29//!
30//! [`serde`]: https://crates.io/crates/serde
31//! [`serde_json`]: https://crates.io/crates/serde_json
32//! [`sha3`]: https://crates.io/crates/sha3
33
34#![cfg_attr(not(any(feature = "std", test)), no_std)]
35
36mod hasher;
37mod hex;
38pub mod keccak;
39#[cfg(feature = "serde")]
40mod serde;
41
42pub use crate::hasher::Hasher;
43use crate::hex::{Alphabet, FormattingBuffer, ParseHexError};
44use core::{
45    array::{IntoIter, TryFromSliceError},
46    fmt::{self, Debug, Display, Formatter, LowerHex, UpperHex},
47    ops::{Deref, DerefMut},
48    slice::Iter,
49    str::FromStr,
50};
51
52/// Macro to create Ethereum digest values from string literals that get parsed
53/// at compile time. A compiler error will be generated if an invalid digest is
54/// specified.
55///
56/// # Examples
57///
58/// Basic usage:
59///
60/// ```
61/// # use ethdigest::{digest, Digest};
62/// for digest in [
63///     digest!("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"),
64///     digest!("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"),
65/// ] {
66///     assert_eq!(digest, Digest([0xee; 32]));
67/// }
68/// ```
69///
70/// The macro generate compile errors on invalid input:
71///
72/// ```compile_fail
73/// # use ethdigest::digest;
74/// let _ = digest!("not a valid hex digest literal!");
75/// ```
76///
77/// Note that this can be used in `const` contexts, but unfortunately not in
78/// pattern matching contexts:
79///
80/// ```
81/// # use ethdigest::{digest, Digest};
82/// const DIGEST: Digest = digest!("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
83/// ```
84///
85/// ```compile_fail
86/// # use ethdigest::{digest, Digest};
87/// match Digest::of("thing") {
88///     digest!("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20") => println!("matches"),
89///     _ => println!("doesn't match"),
90/// }
91/// ```
92#[macro_export]
93macro_rules! digest {
94    ($digest:expr $(,)?) => {{
95        const VALUE: $crate::Digest = $crate::Digest::const_from_str($digest);
96        VALUE
97    }};
98}
99
100/// Macro to create Ethereum digest values for compile-time hashed input.
101///
102/// # Examples
103///
104/// Basic usage:
105///
106/// ```
107/// # use ethdigest::{keccak, Digest};
108/// assert_eq!(
109///     Digest::of("Hello Ethereum!"),
110///     keccak!(b"Hello Ethereum!",),
111/// );
112/// ```
113///
114/// The input can be split into parts:
115///
116/// ```
117/// # use ethdigest::{keccak, Digest};
118/// assert_eq!(
119///     Digest::of("Hello Ethereum!"),
120///     keccak!(b"Hello", b" ", b"Ethereum!"),
121/// );
122/// ```
123///
124/// Note that this can be used in `const` contexts, but unfortunately not in
125/// pattern matching contexts:
126///
127/// ```
128/// # use ethdigest::{keccak, Digest};
129/// const DIGEST: Digest = keccak!(b"I will never change...");
130/// ```
131///
132/// ```compile_fail
133/// # use ethdigest::{keccak, Digest};
134/// match Digest::of("thing") {
135///     keccak!("thing") => println!("matches"),
136///     _ => println!("doesn't match"),
137/// }
138/// ```
139///
140/// Additionally, this can be composed with other macros and constant
141/// expressions:
142///
143/// ```
144/// # use ethdigest::{keccak, Digest};
145/// const FILEHASH: Digest = keccak!(include_bytes!("../README.md"));
146/// const PIHASH: Digest = keccak!(concat!("const PI = ", 3.14).as_bytes());
147/// ```
148#[macro_export]
149macro_rules! keccak {
150    ($data:expr $(,)?) => {{
151        const VALUE: $crate::Digest = $crate::Digest::const_of($data);
152        VALUE
153    }};
154    ($($part:expr),* $(,)?) => {{
155        const VALUE: $crate::Digest = $crate::Digest::const_of_parts(&[$($part),*]);
156        VALUE
157    }};
158}
159
160/// A 32-byte digest.
161#[repr(transparent)]
162#[derive(Copy, Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub struct Digest(pub [u8; 32]);
164
165impl Digest {
166    /// Creates a digest from a slice.
167    ///
168    /// # Panics
169    ///
170    /// This method panics if the length of the slice is not 32 bytes.
171    ///
172    /// # Examples
173    ///
174    /// Basic usage:
175    ///
176    /// ```
177    /// # use ethdigest::{digest, Digest};
178    /// let buffer = (0..255).collect::<Vec<_>>();
179    /// assert_eq!(
180    ///     Digest::from_slice(&buffer[0..32]),
181    ///     digest!("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
182    /// );
183    /// ```
184    pub fn from_slice(slice: &[u8]) -> Self {
185        slice.try_into().unwrap()
186    }
187
188    /// Creates a reference to a digest from a reference to a 32-byte array.
189    ///
190    /// # Examples
191    ///
192    /// Basic usage:
193    ///
194    /// ```
195    /// # use ethdigest::Digest;
196    /// let arrays = [[0; 32], [1; 32]];
197    /// for digest in arrays.iter().map(Digest::from_ref) {
198    ///     println!("{digest}");
199    /// }
200    /// ```
201    pub fn from_ref(array: &[u8; 32]) -> &'_ Self {
202        // SAFETY: `Digest` and `[u8; 32]` have the same memory layout.
203        unsafe { &*(array as *const [u8; 32]).cast::<Self>() }
204    }
205
206    /// Creates a mutable reference to a digest from a mutable reference to a
207    /// 32-byte array.
208    pub fn from_mut(array: &mut [u8; 32]) -> &'_ mut Self {
209        // SAFETY: `Digest` and `[u8; 32]` have the same memory layout.
210        unsafe { &mut *(array as *mut [u8; 32]).cast::<Self>() }
211    }
212
213    /// Creates a digest by hashing some input.
214    ///
215    /// # Examples
216    ///
217    /// Basic usage:
218    ///
219    /// ```
220    /// # use ethdigest::{digest, Digest};
221    /// assert_eq!(
222    ///     Digest::of("Hello Ethereum!"),
223    ///     digest!("0x67e083fb08738b8d7984e349687fec5bf03224c2dad4906020dfab9a0e4ceeac"),
224    /// );
225    /// ```
226    pub fn of(data: impl AsRef<[u8]>) -> Self {
227        let mut hasher = Hasher::new();
228        hasher.update(data);
229        hasher.finalize()
230    }
231
232    /// Same as [`Self::of()`] but as a `const fn`. This method is not intended
233    /// to be used directly but rather through the [`keccak!`] macro.
234    #[doc(hidden)]
235    pub const fn const_of(data: &[u8]) -> Self {
236        Self(keccak::v256(data))
237    }
238
239    /// Compute digest by hashing some input split into parts. This method is
240    /// not intended to be used directly but rather through the [`keccak!`]
241    /// macro.
242    #[doc(hidden)]
243    pub const fn const_of_parts(parts: &[&[u8]]) -> Self {
244        let mut hasher = keccak::V256::new();
245        let mut i = 0;
246        while i < parts.len() {
247            hasher = hasher.absorb(parts[i]);
248            i += 1;
249        }
250        Self(hasher.squeeze())
251    }
252
253    /// Same as [`FromStr::from_str()`] but as a `const fn`. This method is not
254    /// intended to be used directly but rather through the [`digest!`] macro.
255    #[doc(hidden)]
256    pub const fn const_from_str(src: &str) -> Self {
257        Self(hex::const_decode(src))
258    }
259
260    /// Returns a stack-allocated formatted string with the specified alphabet.
261    fn fmt_buffer(&self, alphabet: Alphabet) -> FormattingBuffer<66> {
262        hex::encode(self, alphabet)
263    }
264}
265
266impl Debug for Digest {
267    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
268        f.debug_tuple("Digest")
269            .field(&format_args!("{self}"))
270            .finish()
271    }
272}
273
274impl Display for Digest {
275    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
276        f.pad(self.fmt_buffer(Alphabet::default()).as_str())
277    }
278}
279
280impl LowerHex for Digest {
281    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
282        let buffer = self.fmt_buffer(Alphabet::default());
283        f.pad(if f.alternate() {
284            buffer.as_str()
285        } else {
286            buffer.as_bytes_str()
287        })
288    }
289}
290
291impl UpperHex for Digest {
292    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
293        let buffer = hex::encode::<32, 66>(self, Alphabet::Upper);
294        f.pad(if f.alternate() {
295            buffer.as_str()
296        } else {
297            buffer.as_bytes_str()
298        })
299    }
300}
301
302impl AsRef<[u8; 32]> for Digest {
303    fn as_ref(&self) -> &[u8; 32] {
304        &self.0
305    }
306}
307
308impl AsRef<[u8]> for Digest {
309    fn as_ref(&self) -> &[u8] {
310        &self.0
311    }
312}
313
314impl AsMut<[u8; 32]> for Digest {
315    fn as_mut(&mut self) -> &mut [u8; 32] {
316        &mut self.0
317    }
318}
319
320impl AsMut<[u8]> for Digest {
321    fn as_mut(&mut self) -> &mut [u8] {
322        &mut self.0
323    }
324}
325
326impl Deref for Digest {
327    type Target = [u8; 32];
328
329    fn deref(&self) -> &Self::Target {
330        &self.0
331    }
332}
333
334impl DerefMut for Digest {
335    fn deref_mut(&mut self) -> &mut Self::Target {
336        &mut self.0
337    }
338}
339
340impl FromStr for Digest {
341    type Err = ParseDigestError;
342
343    fn from_str(s: &str) -> Result<Self, Self::Err> {
344        Ok(Self(hex::decode(s)?))
345    }
346}
347
348impl IntoIterator for Digest {
349    type Item = u8;
350    type IntoIter = IntoIter<u8, 32>;
351
352    fn into_iter(self) -> Self::IntoIter {
353        self.0.into_iter()
354    }
355}
356
357impl<'a> IntoIterator for &'a Digest {
358    type Item = &'a u8;
359    type IntoIter = Iter<'a, u8>;
360
361    fn into_iter(self) -> Self::IntoIter {
362        self.0.iter()
363    }
364}
365
366impl PartialEq<[u8; 32]> for Digest {
367    fn eq(&self, other: &'_ [u8; 32]) -> bool {
368        **self == *other
369    }
370}
371
372impl PartialEq<[u8]> for Digest {
373    fn eq(&self, other: &'_ [u8]) -> bool {
374        **self == *other
375    }
376}
377
378impl PartialEq<&'_ [u8]> for Digest {
379    fn eq(&self, other: &&'_ [u8]) -> bool {
380        **self == **other
381    }
382}
383
384impl PartialEq<&'_ mut [u8]> for Digest {
385    fn eq(&self, other: &&'_ mut [u8]) -> bool {
386        **self == **other
387    }
388}
389
390#[cfg(feature = "std")]
391impl PartialEq<Vec<u8>> for Digest {
392    fn eq(&self, other: &Vec<u8>) -> bool {
393        **self == **other
394    }
395}
396
397impl TryFrom<&'_ [u8]> for Digest {
398    type Error = TryFromSliceError;
399
400    fn try_from(value: &'_ [u8]) -> Result<Self, Self::Error> {
401        Ok(Self(value.try_into()?))
402    }
403}
404
405impl TryFrom<&'_ mut [u8]> for Digest {
406    type Error = TryFromSliceError;
407
408    fn try_from(value: &'_ mut [u8]) -> Result<Self, Self::Error> {
409        Ok(Self(value.try_into()?))
410    }
411}
412
413impl<'a> TryFrom<&'a [u8]> for &'a Digest {
414    type Error = TryFromSliceError;
415
416    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
417        Ok(Digest::from_ref(value.try_into()?))
418    }
419}
420
421impl<'a> TryFrom<&'a mut [u8]> for &'a mut Digest {
422    type Error = TryFromSliceError;
423
424    fn try_from(value: &'a mut [u8]) -> Result<Self, Self::Error> {
425        Ok(Digest::from_mut(value.try_into()?))
426    }
427}
428
429#[cfg(feature = "std")]
430impl TryFrom<Vec<u8>> for Digest {
431    type Error = Vec<u8>;
432
433    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
434        Ok(Self(value.try_into()?))
435    }
436}
437
438/// Represents an error parsing a digest from a string.
439#[derive(Clone, Debug, Eq, PartialEq)]
440pub enum ParseDigestError {
441    /// The string does not have the correct length.
442    InvalidLength,
443    /// An invalid character was found.
444    InvalidHexCharacter { c: char, index: usize },
445}
446
447impl Display for ParseDigestError {
448    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
449        match self {
450            Self::InvalidLength => write!(f, "{}", ParseHexError::InvalidLength),
451            Self::InvalidHexCharacter { c, index } => {
452                let (c, index) = (*c, *index);
453                write!(f, "{}", ParseHexError::InvalidHexCharacter { c, index })
454            }
455        }
456    }
457}
458
459impl From<ParseHexError> for ParseDigestError {
460    fn from(err: ParseHexError) -> Self {
461        match err {
462            ParseHexError::InvalidLength => Self::InvalidLength,
463            ParseHexError::InvalidHexCharacter { c, index } => {
464                Self::InvalidHexCharacter { c, index }
465            }
466        }
467    }
468}
469
470#[cfg(feature = "std")]
471impl std::error::Error for ParseDigestError {}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    #[test]
478    fn hex_formatting() {
479        let digest = Digest([0xee; 32]);
480        assert_eq!(
481            format!("{digest:?}"),
482            "Digest(0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)"
483        );
484        assert_eq!(
485            format!("{digest}"),
486            "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
487        );
488        assert_eq!(
489            format!("{digest:x}"),
490            "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
491        );
492        assert_eq!(
493            format!("{digest:#x}"),
494            "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
495        );
496        assert_eq!(
497            format!("{digest:X}"),
498            "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
499        );
500        assert_eq!(
501            format!("{digest:#X}"),
502            "0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
503        );
504    }
505}