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}