Skip to content

Commit 2c691af

Browse files
committed
Use hardware acceleration for SHA256 (#2426)
## Proposed Changes Modify the SHA256 implementation in `eth2_hashing` so that it switches between `ring` and `sha2` to take advantage of [x86_64 SHA extensions](https://en.wikipedia.org/wiki/Intel_SHA_extensions). The extensions are available on modern Intel and AMD CPUs, and seem to provide a considerable speed-up: on my Ryzen 5950X it dropped state tree hashing times by about 30% from 35ms to 25ms (on Prater). ## Additional Info The extensions became available in the `sha2` crate [last year](https://www.reddit.com/r/rust/comments/hf2vcx/ann_rustcryptos_sha1_and_sha2_now_support/), and are not available in Ring, which uses a [pure Rust implementation of sha2](https://github.com/briansmith/ring/blob/main/src/digest/sha2.rs). Ring is faster on CPUs that lack the extensions so I've implemented a runtime switch to use `sha2` only when the extensions are available. The runtime switching seems to impose a miniscule penalty (see the benchmarks linked below).
1 parent a7b7134 commit 2c691af

File tree

10 files changed

+220
-84
lines changed

10 files changed

+220
-84
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

consensus/swap_or_not_shuffle/src/compute_shuffled_index.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::Hash256;
2-
use eth2_hashing::{Context, SHA256};
2+
use eth2_hashing::{Context, Sha256Context};
33
use std::cmp::max;
44

55
/// Return `p(index)` in a pseudorandom permutation `p` of `0...list_size-1` with ``seed`` as entropy.
@@ -54,7 +54,7 @@ fn do_round(seed: &[u8], index: usize, pivot: usize, round: u8, list_size: usize
5454
}
5555

5656
fn hash_with_round_and_position(seed: &[u8], round: u8, position: usize) -> Hash256 {
57-
let mut context = Context::new(&SHA256);
57+
let mut context = Context::new();
5858

5959
context.update(seed);
6060
context.update(&[round]);
@@ -64,17 +64,17 @@ fn hash_with_round_and_position(seed: &[u8], round: u8, position: usize) -> Hash
6464
*/
6565
context.update(&(position / 256).to_le_bytes()[0..4]);
6666

67-
let digest = context.finish();
67+
let digest = context.finalize();
6868
Hash256::from_slice(digest.as_ref())
6969
}
7070

7171
fn hash_with_round(seed: &[u8], round: u8) -> Hash256 {
72-
let mut context = Context::new(&SHA256);
72+
let mut context = Context::new();
7373

7474
context.update(seed);
7575
context.update(&[round]);
7676

77-
let digest = context.finish();
77+
let digest = context.finalize();
7878
Hash256::from_slice(digest.as_ref())
7979
}
8080

consensus/swap_or_not_shuffle/src/shuffle_list.rs

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::Hash256;
2-
use eth2_hashing::{Context, SHA256};
2+
use eth2_hashing::hash_fixed;
33
use std::mem;
44

55
const SEED_SIZE: usize = 32;
@@ -31,12 +31,10 @@ impl Buf {
3131
/// Returns the new pivot. It is "raw" because it has not modulo the list size (this must be
3232
/// done by the caller).
3333
fn raw_pivot(&self) -> u64 {
34-
let mut context = Context::new(&SHA256);
35-
context.update(&self.0[0..PIVOT_VIEW_SIZE]);
36-
let digest = context.finish();
34+
let digest = hash_fixed(&self.0[0..PIVOT_VIEW_SIZE]);
3735

3836
let mut bytes = [0; mem::size_of::<u64>()];
39-
bytes[..].copy_from_slice(&digest.as_ref()[0..mem::size_of::<u64>()]);
37+
bytes[..].copy_from_slice(&digest[0..mem::size_of::<u64>()]);
4038
u64::from_le_bytes(bytes)
4139
}
4240

@@ -47,10 +45,7 @@ impl Buf {
4745

4846
/// Hash the entire buffer.
4947
fn hash(&self) -> Hash256 {
50-
let mut context = Context::new(&SHA256);
51-
context.update(&self.0[..]);
52-
let digest = context.finish();
53-
Hash256::from_slice(digest.as_ref())
48+
Hash256::from_slice(&hash_fixed(&self.0))
5449
}
5550
}
5651

consensus/tree_hash/src/lib.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ pub use merkle_hasher::{Error, MerkleHasher};
77
pub use merkleize_padded::merkleize_padded;
88
pub use merkleize_standard::merkleize_standard;
99

10-
use eth2_hashing::{Context, SHA256};
11-
use eth2_hashing::{ZERO_HASHES, ZERO_HASHES_MAX_INDEX};
10+
use eth2_hashing::{hash_fixed, ZERO_HASHES, ZERO_HASHES_MAX_INDEX};
1211

1312
pub const BYTES_PER_CHUNK: usize = 32;
1413
pub const HASHSIZE: usize = 32;
@@ -39,11 +38,7 @@ pub fn merkle_root(bytes: &[u8], minimum_leaf_count: usize) -> Hash256 {
3938
let mut leaves = [0; HASHSIZE * 2];
4039
leaves[0..bytes.len()].copy_from_slice(bytes);
4140

42-
let mut context = Context::new(&SHA256);
43-
context.update(&leaves);
44-
let digest = context.finish();
45-
46-
Hash256::from_slice(digest.as_ref())
41+
Hash256::from_slice(&hash_fixed(&leaves))
4742
} else {
4843
// If there are 3 or more leaves, use `MerkleHasher`.
4944
let mut hasher = MerkleHasher::with_leaves(leaves);

consensus/tree_hash/src/merkle_hasher.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{get_zero_hash, Hash256, HASHSIZE};
2-
use eth2_hashing::{Context, Digest, SHA256};
2+
use eth2_hashing::{Context, Sha256Context, HASH_LEN};
33
use smallvec::{smallvec, SmallVec};
44
use std::mem;
55

@@ -15,7 +15,7 @@ pub enum Error {
1515
///
1616
/// Should be used as a left or right value for some node.
1717
enum Preimage<'a> {
18-
Digest(Digest),
18+
Digest([u8; HASH_LEN]),
1919
Slice(&'a [u8]),
2020
}
2121

@@ -41,17 +41,17 @@ struct HalfNode {
4141
impl HalfNode {
4242
/// Create a new half-node from the given `left` value.
4343
fn new(id: usize, left: Preimage) -> Self {
44-
let mut context = Context::new(&SHA256);
44+
let mut context = Context::new();
4545
context.update(left.as_bytes());
4646

4747
Self { context, id }
4848
}
4949

5050
/// Complete the half-node by providing a `right` value. Returns a digest of the left and right
5151
/// nodes.
52-
fn finish(mut self, right: Preimage) -> Digest {
52+
fn finish(mut self, right: Preimage) -> [u8; HASH_LEN] {
5353
self.context.update(right.as_bytes());
54-
self.context.finish()
54+
self.context.finalize()
5555
}
5656
}
5757

@@ -124,7 +124,7 @@ pub struct MerkleHasher {
124124
/// Stores the nodes that are half-complete and awaiting a right node.
125125
///
126126
/// A smallvec of size 8 means we can hash a tree with 256 leaves without allocating on the
127-
/// heap. Each half-node is 224 bytes, so this smallvec may store 1,792 bytes on the stack.
127+
/// heap. Each half-node is 232 bytes, so this smallvec may store 1856 bytes on the stack.
128128
half_nodes: SmallVec8<HalfNode>,
129129
/// The depth of the tree that will be produced.
130130
///
@@ -368,7 +368,7 @@ mod test {
368368
fn context_size() {
369369
assert_eq!(
370370
mem::size_of::<HalfNode>(),
371-
216 + 8,
371+
232,
372372
"Halfnode size should be as expected"
373373
);
374374
}

consensus/tree_hash/src/merkleize_padded.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{get_zero_hash, Hash256, BYTES_PER_CHUNK};
2-
use eth2_hashing::{hash, hash32_concat};
2+
use eth2_hashing::{hash32_concat, hash_fixed};
33

44
/// Merkleize `bytes` and return the root, optionally padding the tree out to `min_leaves` number of
55
/// leaves.
@@ -79,15 +79,15 @@ pub fn merkleize_padded(bytes: &[u8], min_leaves: usize) -> Hash256 {
7979
// Hash two chunks, creating a parent chunk.
8080
let hash = match bytes.get(start..start + BYTES_PER_CHUNK * 2) {
8181
// All bytes are available, hash as usual.
82-
Some(slice) => hash(slice),
82+
Some(slice) => hash_fixed(slice),
8383
// Unable to get all the bytes, get a small slice and pad it out.
8484
None => {
8585
let mut preimage = bytes
8686
.get(start..)
8787
.expect("`i` can only be larger than zero if there are bytes to read")
8888
.to_vec();
8989
preimage.resize(BYTES_PER_CHUNK * 2, 0);
90-
hash(&preimage)
90+
hash_fixed(&preimage)
9191
}
9292
};
9393

crypto/eth2_hashing/Cargo.toml

+2-5
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ description = "Hashing primitives used in Ethereum 2.0"
88

99
[dependencies]
1010
lazy_static = { version = "1.4.0", optional = true }
11-
12-
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
1311
ring = "0.16.19"
14-
15-
[target.'cfg(target_arch = "wasm32")'.dependencies]
16-
sha2 = "0.9.1"
12+
sha2 = "0.9.5"
13+
cpufeatures = "0.1.5"
1714

1815
[dev-dependencies]
1916
rustc-hex = "2.1.0"

0 commit comments

Comments
 (0)