Skip to main content

zemse_ethereum_hashing/
lib.rs

1//! Optimized SHA256 for use in Ethereum.
2//!
3//! The initial purpose of this crate was to provide an abstraction over the hash function used in
4//! the beacon chain. The hash function changed during the specification process, so defining it
5//! once in this crate made it easy to replace.
6//!
7//! Now this crate serves primarily as a wrapper over two SHA256 crates: `sha2` and `ring` – which
8//! it switches between at runtime based on the availability of SHA intrinsics.
9
10#[cfg(feature = "zero_hash_cache")]
11use std::sync::LazyLock;
12
13use sha2_impl::Sha2CrateImpl;
14pub use self::DynamicContext as Context;
15
16mod sha2_impl;
17
18/// Length of a SHA256 hash in bytes.
19pub const HASH_LEN: usize = 32;
20
21
22/// Returns the digest of `input` using the best available implementation.
23pub fn hash(input: &[u8]) -> Vec<u8> {
24    DynamicImpl::best().hash(input)
25}
26
27/// Hash function returning a fixed-size array (to save on allocations).
28///
29/// Uses the best available implementation based on CPU features.
30pub fn hash_fixed(input: &[u8]) -> [u8; HASH_LEN] {
31    DynamicImpl::best().hash_fixed(input)
32}
33
34/// Compute the hash of two slices concatenated.
35pub fn hash32_concat(h1: &[u8], h2: &[u8]) -> [u8; 32] {
36    let mut ctxt = DynamicContext::new();
37    ctxt.update(h1);
38    ctxt.update(h2);
39    ctxt.finalize()
40}
41
42/// Context trait for abstracting over implementation contexts.
43pub trait Sha256Context {
44    fn new() -> Self;
45
46    fn update(&mut self, bytes: &[u8]);
47
48    fn finalize(self) -> [u8; HASH_LEN];
49}
50
51/// Top-level trait implemented by both `sha2` and `ring` implementations.
52pub trait Sha256 {
53    type Context: Sha256Context;
54
55    fn hash(&self, input: &[u8]) -> Vec<u8>;
56
57    fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN];
58}
59
60/// Default dynamic implementation that switches between available implementations.
61pub enum DynamicImpl {
62    Sha2,
63}
64
65#[inline(always)]
66pub fn have_sha_extensions() -> bool {
67    false
68}
69
70impl DynamicImpl {
71    /// Choose the best available implementation based on the currently executing CPU.
72    #[inline(always)]
73    pub fn best() -> Self {
74        Self::Sha2
75    }
76}
77
78impl Sha256 for DynamicImpl {
79    type Context = DynamicContext;
80
81    #[inline(always)]
82    fn hash(&self, input: &[u8]) -> Vec<u8> {
83        match self {
84            Self::Sha2 => Sha2CrateImpl.hash(input),
85        }
86    }
87
88    #[inline(always)]
89    fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN] {
90        match self {
91            Self::Sha2 => Sha2CrateImpl.hash_fixed(input),
92        }
93    }
94}
95
96/// Context encapsulating all implemenation contexts.
97///
98/// This enum ends up being 8 bytes larger than the largest inner context.
99pub enum DynamicContext {
100    Sha2(sha2::Sha256),
101}
102
103impl Sha256Context for DynamicContext {
104    fn new() -> Self {
105        match DynamicImpl::best() {
106            DynamicImpl::Sha2 => Self::Sha2(Sha256Context::new()),
107        }
108    }
109
110    fn update(&mut self, bytes: &[u8]) {
111        match self {
112            Self::Sha2(ctxt) => Sha256Context::update(ctxt, bytes),
113        }
114    }
115
116    fn finalize(self) -> [u8; HASH_LEN] {
117        match self {
118            Self::Sha2(ctxt) => Sha256Context::finalize(ctxt),
119        }
120    }
121}
122
123/// The max index that can be used with `ZERO_HASHES`.
124#[cfg(feature = "zero_hash_cache")]
125pub const ZERO_HASHES_MAX_INDEX: usize = 48;
126
127#[cfg(feature = "zero_hash_cache")]
128/// Cached zero hashes where `ZERO_HASHES[i]` is the hash of a Merkle tree with 2^i zero leaves.
129pub static ZERO_HASHES: LazyLock<Vec<[u8; HASH_LEN]>> = LazyLock::new(|| {
130    let mut hashes = vec![[0; HASH_LEN]; ZERO_HASHES_MAX_INDEX + 1];
131
132    for i in 0..ZERO_HASHES_MAX_INDEX {
133        hashes[i + 1] = hash32_concat(&hashes[i], &hashes[i]);
134    }
135
136    hashes
137});
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use rustc_hex::FromHex;
143
144    #[cfg(target_arch = "wasm32")]
145    use wasm_bindgen_test::*;
146
147    #[cfg_attr(not(target_arch = "wasm32"), test)]
148    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
149    fn test_hashing() {
150        let input: Vec<u8> = b"hello world".as_ref().into();
151
152        let output = hash(input.as_ref());
153        let expected_hex = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
154        let expected: Vec<u8> = expected_hex.from_hex().unwrap();
155        assert_eq!(expected, output);
156    }
157
158    #[cfg(feature = "zero_hash_cache")]
159    mod zero_hash {
160        use super::*;
161
162        #[test]
163        fn zero_hash_zero() {
164            assert_eq!(ZERO_HASHES[0], [0; 32]);
165        }
166    }
167}