From 1fdb1ecaf024ca162cf366e7aee81b03af58f089 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 11:48:21 +0200 Subject: [PATCH 1/7] feat: support hashtree hashing with feature --- ssz-rs/Cargo.toml | 7 ++++ ssz-rs/benches/proofs.rs | 1 + ssz-rs/src/merkleization/hasher.rs | 48 +++++++++++++++++++++++++ ssz-rs/src/merkleization/merkleize.rs | 46 ++++++++---------------- ssz-rs/src/merkleization/mod.rs | 1 + ssz-rs/src/merkleization/multiproofs.rs | 28 ++++++--------- ssz-rs/src/merkleization/proofs.rs | 25 ++++++------- ssz-rs/src/union.rs | 6 ++-- ssz-rs/src/vector.rs | 14 ++++---- 9 files changed, 104 insertions(+), 72 deletions(-) create mode 100644 ssz-rs/benches/proofs.rs create mode 100644 ssz-rs/src/merkleization/hasher.rs diff --git a/ssz-rs/Cargo.toml b/ssz-rs/Cargo.toml index ae5f3dbd..059cbc8b 100644 --- a/ssz-rs/Cargo.toml +++ b/ssz-rs/Cargo.toml @@ -15,12 +15,14 @@ exclude = ["tests/data"] default = ["serde", "std"] std = ["bitvec/default", "sha2/default", "alloy-primitives/default"] sha2-asm = ["sha2/asm"] +hashtree = ["dep:hashtree"] serde = ["dep:serde", "alloy-primitives/serde"] [dependencies] bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } ssz_rs_derive = { path = "../ssz-rs-derive", version = "0.9.0" } sha2 = { version = "0.9.8", default-features = false } +hashtree = { version = "0.2.0", optional = true, package = "hashtree-rs" } serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", @@ -32,6 +34,11 @@ snap = "1.0" project-root = "0.2.2" serde_json = "1.0.81" hex = "0.4.3" +criterion = { version = "0.5", features = ["html_reports"] } [build-dependencies] sha2 = "0.9.8" + +[[bench]] +name = "proofs" +harness = false diff --git a/ssz-rs/benches/proofs.rs b/ssz-rs/benches/proofs.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/ssz-rs/benches/proofs.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/ssz-rs/src/merkleization/hasher.rs b/ssz-rs/src/merkleization/hasher.rs new file mode 100644 index 00000000..a8911d3a --- /dev/null +++ b/ssz-rs/src/merkleization/hasher.rs @@ -0,0 +1,48 @@ +#[cfg(feature = "hashtree")] +use std::sync::Once; + +use super::BYTES_PER_CHUNK; + +#[cfg(not(feature = "hashtree"))] +use ::sha2::{Digest, Sha256}; + +#[cfg(feature = "hashtree")] +static INIT: Once = Once::new(); + +/// Function that hashes 2 [BYTES_PER_CHUNK] (32) len byte slices together. Depending on the feature flags, +/// this will either use: +/// - sha256 (default) +/// - sha256 with assembly support (with the "sha2-asm" feature flag) +/// - hashtree (with the "hashtree" feature flag) +pub fn hash_chunks(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> [u8; BYTES_PER_CHUNK] { + #[cfg(feature = "hashtree")] + { + // Initialize the hashtree library (once) + INIT.call_once(|| { + hashtree::init(); + }); + + debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); + debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); + + let mut out = [0u8; BYTES_PER_CHUNK]; + + let mut chunks = Vec::with_capacity(2 * BYTES_PER_CHUNK); + chunks.extend_from_slice(left.as_ref()); + chunks.extend_from_slice(right.as_ref()); + + // NOTE: hashtree "chunks" are 64 bytes long, not 32. That's why we + // specify "1" as the chunk count. + hashtree::hash(&mut out, chunks.as_slice(), 1); + + out + } + + #[cfg(not(feature = "hashtree"))] + { + let mut hasher = Sha256::new(); + hasher.update(left.as_ref()); + hasher.update(right.as_ref()); + hasher.finalize_reset().into() + } +} diff --git a/ssz-rs/src/merkleization/merkleize.rs b/ssz-rs/src/merkleization/merkleize.rs index 81454ff8..aad57124 100644 --- a/ssz-rs/src/merkleization/merkleize.rs +++ b/ssz-rs/src/merkleization/merkleize.rs @@ -1,13 +1,12 @@ //! Support for computing Merkle trees. use crate::{ lib::*, - merkleization::{MerkleizationError as Error, Node, BYTES_PER_CHUNK}, + merkleization::{hasher::hash_chunks, MerkleizationError as Error, Node, BYTES_PER_CHUNK}, ser::Serialize, GeneralizedIndex, }; #[cfg(feature = "serde")] use alloy_primitives::hex::FromHex; -use sha2::{Digest, Sha256}; // The generalized index for the root of the "decorated" type in any Merkleized type that supports // decoration. @@ -52,10 +51,8 @@ where Ok(buffer) } -fn hash_nodes(hasher: &mut Sha256, a: impl AsRef<[u8]>, b: impl AsRef<[u8]>, out: &mut [u8]) { - hasher.update(a); - hasher.update(b); - out.copy_from_slice(&hasher.finalize_reset()); +fn hash_nodes(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>, out: &mut [u8]) { + out.copy_from_slice(&hash_chunks(a, b)); } const MAX_MERKLE_TREE_DEPTH: usize = 64; @@ -108,13 +105,12 @@ fn merkleize_chunks_with_virtual_padding(chunks: &[u8], leaf_count: usize) -> Re let depth = height - 1; // SAFETY: index is safe while depth == leaf_count.trailing_zeros() < MAX_MERKLE_TREE_DEPTH; // qed - return Ok(CONTEXT[depth as usize].try_into().expect("can produce a single root chunk")) + return Ok(CONTEXT[depth as usize].try_into().expect("can produce a single root chunk")); } let mut layer = chunks.to_vec(); // SAFETY: checked subtraction is unnecessary, as we return early when chunk_count == 0; qed let mut last_index = chunk_count - 1; - let mut hasher = Sha256::new(); // for each layer of the tree, starting from the bottom and walking up to the root: for k in (1..height).rev() { // for each pair of nodes in this layer: @@ -171,13 +167,11 @@ fn merkleize_chunks_with_virtual_padding(chunks: &[u8], leaf_count: usize) -> Re // NOTE: nodes share memory here and so we can't use the `hash_nodes` utility // as the disjunct nature is reflect in that functions type signature // so instead we will just replicate here. - hasher.update(&left); - hasher.update(right); - left.copy_from_slice(&hasher.finalize_reset()); + left.copy_from_slice(&hash_chunks(&left, right)); } else { // SAFETY: index is safe because parent.len() % BYTES_PER_CHUNK == 0 and // parent isn't empty; qed - hash_nodes(&mut hasher, left, right, &mut parent[..BYTES_PER_CHUNK]); + hash_nodes(left, right, &mut parent[..BYTES_PER_CHUNK]); } } last_index /= 2; @@ -198,7 +192,7 @@ pub fn merkleize(chunks: &[u8], limit: Option) -> Result { let mut leaf_count = chunk_count.next_power_of_two(); if let Some(limit) = limit { if limit < chunk_count { - return Err(Error::InputExceedsLimit(limit)) + return Err(Error::InputExceedsLimit(limit)); } leaf_count = limit.next_power_of_two(); } @@ -208,9 +202,8 @@ pub fn merkleize(chunks: &[u8], limit: Option) -> Result { fn mix_in_decoration(root: Node, decoration: usize) -> Node { let decoration_data = decoration.hash_tree_root().expect("can merkleize usize"); - let mut hasher = Sha256::new(); let mut output = vec![0u8; BYTES_PER_CHUNK]; - hash_nodes(&mut hasher, root, decoration_data, &mut output); + hash_nodes(root, decoration_data, &mut output); output.as_slice().try_into().expect("can extract root") } @@ -238,17 +231,13 @@ pub(crate) fn elements_to_chunks<'a, T: HashTreeRoot + 'a>( pub struct Tree(Vec); impl Tree { - pub fn mix_in_decoration( - &mut self, - decoration: usize, - hasher: &mut Sha256, - ) -> Result<(), Error> { + pub fn mix_in_decoration(&mut self, decoration: usize) -> Result<(), Error> { let target_node = &mut self[DECORATION_GENERALIZED_INDEX]; let decoration_node = decoration.hash_tree_root()?; target_node.copy_from_slice(decoration_node.as_ref()); - hasher.update(&self[INNER_ROOT_GENERALIZED_INDEX]); - hasher.update(&self[DECORATION_GENERALIZED_INDEX]); - self[1].copy_from_slice(&hasher.finalize_reset()); + let out = + hash_chunks(&self[INNER_ROOT_GENERALIZED_INDEX], &self[DECORATION_GENERALIZED_INDEX]); + self[1].copy_from_slice(&out); Ok(()) } @@ -287,11 +276,7 @@ impl std::fmt::Debug for Tree { // Invariant: `chunks.len() % BYTES_PER_CHUNK == 0` // Invariant: `leaf_count.next_power_of_two() == leaf_count` // NOTE: naive implementation, can make much more efficient -pub fn compute_merkle_tree( - hasher: &mut Sha256, - chunks: &[u8], - leaf_count: usize, -) -> Result { +pub fn compute_merkle_tree(chunks: &[u8], leaf_count: usize) -> Result { debug_assert!(chunks.len() % BYTES_PER_CHUNK == 0); debug_assert!(leaf_count.next_power_of_two() == leaf_count); @@ -320,7 +305,7 @@ pub fn compute_merkle_tree( // NOTE: children.len() == 2 * BYTES_PER_CHUNK let (parent, children) = focus.split_at_mut(children_index); let (left, right) = children.split_at(BYTES_PER_CHUNK); - hash_nodes(hasher, left, right, &mut parent[..BYTES_PER_CHUNK]); + hash_nodes(left, right, &mut parent[..BYTES_PER_CHUNK]); } Ok(Tree(buffer)) } @@ -332,8 +317,7 @@ mod tests { // Return the root of the Merklization of a binary tree formed from `chunks`. fn merkleize_chunks(chunks: &[u8], leaf_count: usize) -> Result { - let mut hasher = Sha256::new(); - let tree = compute_merkle_tree(&mut hasher, chunks, leaf_count)?; + let tree = compute_merkle_tree(chunks, leaf_count)?; let root_index = default_generalized_index(); Ok(tree[root_index].try_into().expect("can produce a single root chunk")) } diff --git a/ssz-rs/src/merkleization/mod.rs b/ssz-rs/src/merkleization/mod.rs index 40aa4a73..3c8bd987 100644 --- a/ssz-rs/src/merkleization/mod.rs +++ b/ssz-rs/src/merkleization/mod.rs @@ -1,4 +1,5 @@ pub mod generalized_index; +mod hasher; mod merkleize; pub mod multiproofs; mod node; diff --git a/ssz-rs/src/merkleization/multiproofs.rs b/ssz-rs/src/merkleization/multiproofs.rs index 1882c13e..9c178a87 100644 --- a/ssz-rs/src/merkleization/multiproofs.rs +++ b/ssz-rs/src/merkleization/multiproofs.rs @@ -3,10 +3,10 @@ use crate::{ lib::*, merkleization::{ generalized_index::{get_bit, get_path_length, parent, sibling}, + hasher::hash_chunks, GeneralizedIndex, MerkleizationError as Error, Node, }, }; -use sha2::{Digest, Sha256}; fn get_branch_indices(tree_index: GeneralizedIndex) -> Vec { let mut focus = sibling(tree_index); @@ -52,20 +52,15 @@ pub fn calculate_merkle_root( ) -> Result { let path_length = get_path_length(index)?; if path_length != proof.len() { - return Err(Error::InvalidProof) + return Err(Error::InvalidProof); } let mut result = leaf; - let mut hasher = Sha256::new(); for (i, next) in proof.iter().enumerate() { - if get_bit(index, i) { - hasher.update(next); - hasher.update(result); - } else { - hasher.update(result); - hasher.update(next); - } - result.copy_from_slice(&hasher.finalize_reset()); + let out = + if get_bit(index, i) { hash_chunks(next, result) } else { hash_chunks(result, next) }; + + result.copy_from_slice(&out); } Ok(result) } @@ -89,11 +84,11 @@ pub fn calculate_multi_merkle_root( indices: &[GeneralizedIndex], ) -> Result { if leaves.len() != indices.len() { - return Err(Error::InvalidProof) + return Err(Error::InvalidProof); } let helper_indices = get_helper_indices(indices); if proof.len() != helper_indices.len() { - return Err(Error::InvalidProof) + return Err(Error::InvalidProof); } let mut objects = HashMap::new(); @@ -107,7 +102,6 @@ pub fn calculate_multi_merkle_root( let mut keys = objects.keys().cloned().collect::>(); keys.sort_by(|a, b| b.cmp(a)); - let mut hasher = Sha256::new(); let mut pos = 0; while pos < keys.len() { let key = keys.get(pos).unwrap(); @@ -121,11 +115,11 @@ pub fn calculate_multi_merkle_root( let left_index = sibling(right_index); let left_input = objects.get(&left_index).expect("contains index"); let right_input = objects.get(&right_index).expect("contains index"); - hasher.update(left_input); - hasher.update(right_input); + + let out = hash_chunks(left_input, right_input); let parent = objects.entry(parent_index).or_default(); - parent.copy_from_slice(&hasher.finalize_reset()); + parent.copy_from_slice(&out); keys.push(parent_index); } pos += 1; diff --git a/ssz-rs/src/merkleization/proofs.rs b/ssz-rs/src/merkleization/proofs.rs index 7a8e73df..805158ac 100644 --- a/ssz-rs/src/merkleization/proofs.rs +++ b/ssz-rs/src/merkleization/proofs.rs @@ -7,7 +7,8 @@ use crate::{ Node, Path, }, }; -use sha2::{Digest, Sha256}; + +use super::hasher::hash_chunks; /// Convenience type for a Merkle proof and the root of the Merkle tree, which serves as /// "witness" that the proof is valid. @@ -45,7 +46,6 @@ pub(crate) fn compute_local_merkle_coordinates( /// A type that knows how to compute Merkle proofs assuming a target type is `Prove`. #[derive(Debug)] pub struct Prover { - hasher: Sha256, proof: Proof, witness: Node, } @@ -94,9 +94,9 @@ impl Prover { is_leaf_local = true; } let chunks = data.chunks()?; - let mut tree = compute_merkle_tree(&mut self.hasher, &chunks, leaf_count)?; + let mut tree = compute_merkle_tree(&chunks, leaf_count)?; if let Some(decoration) = decoration { - tree.mix_in_decoration(decoration, &mut self.hasher)?; + tree.mix_in_decoration(decoration)?; } if is_leaf_local { @@ -126,7 +126,6 @@ impl From for ProofAndWitness { impl From for Prover { fn from(index: GeneralizedIndex) -> Self { Self { - hasher: Sha256::new(), proof: Proof { leaf: Default::default(), branch: vec![], index }, witness: Default::default(), } @@ -207,21 +206,19 @@ pub fn is_valid_merkle_branch( root: Node, ) -> Result<(), Error> { if branch.len() != depth { - return Err(Error::InvalidProof) + return Err(Error::InvalidProof); } let mut derived_root = leaf; - let mut hasher = Sha256::new(); for (i, node) in branch.iter().enumerate() { - if (index / 2usize.pow(i as u32)) % 2 != 0 { - hasher.update(node); - hasher.update(derived_root); + let out = if (index / 2usize.pow(i as u32)) % 2 != 0 { + hash_chunks(node, derived_root) } else { - hasher.update(derived_root); - hasher.update(node); - } - derived_root.copy_from_slice(&hasher.finalize_reset()); + hash_chunks(derived_root, node) + }; + + derived_root.copy_from_slice(&out); } if derived_root == root { diff --git a/ssz-rs/src/union.rs b/ssz-rs/src/union.rs index d71258a5..6f1e1892 100644 --- a/ssz-rs/src/union.rs +++ b/ssz-rs/src/union.rs @@ -50,7 +50,7 @@ where { fn deserialize(encoding: &[u8]) -> Result { if encoding.is_empty() { - return Err(DeserializeError::ExpectedFurtherInput { provided: 0, expected: 1 }) + return Err(DeserializeError::ExpectedFurtherInput { provided: 0, expected: 1 }); } // SAFETY: index is safe because encoding is not empty; qed @@ -60,7 +60,7 @@ where return Err(DeserializeError::AdditionalInput { provided: encoding.len(), expected: 1, - }) + }); } Ok(None) } @@ -99,7 +99,7 @@ where match next { PathElement::Index(i) => { if *i >= 2 { - return Err(MerkleizationError::InvalidPathElement(next.clone())) + return Err(MerkleizationError::InvalidPathElement(next.clone())); } let child = parent * 2; match i { diff --git a/ssz-rs/src/vector.rs b/ssz-rs/src/vector.rs index c73956ac..d9dbece1 100644 --- a/ssz-rs/src/vector.rs +++ b/ssz-rs/src/vector.rs @@ -40,7 +40,7 @@ impl TryFrom> for Vector { fn try_from(data: Vec) -> Result { if N == 0 { - return Err((data, Error::Type(TypeError::InvalidBound(N)))) + return Err((data, Error::Type(TypeError::InvalidBound(N)))); } if data.len() != N { let len = data.len(); @@ -59,7 +59,7 @@ where fn try_from(data: &[T]) -> Result { if N == 0 { - return Err(Error::Type(TypeError::InvalidBound(N))) + return Err(Error::Type(TypeError::InvalidBound(N))); } if data.len() != N { let len = data.len(); @@ -164,7 +164,7 @@ where { fn serialize(&self, buffer: &mut Vec) -> Result { if N == 0 { - return Err(TypeError::InvalidBound(N).into()) + return Err(TypeError::InvalidBound(N).into()); } let mut serializer = Serializer::default(); for element in &self.data { @@ -180,7 +180,7 @@ where { fn deserialize(encoding: &[u8]) -> Result { if N == 0 { - return Err(TypeError::InvalidBound(N).into()) + return Err(TypeError::InvalidBound(N).into()); } if !T::is_variable_size() { let expected_length = N * T::size_hint(); @@ -188,13 +188,13 @@ where return Err(DeserializeError::ExpectedFurtherInput { provided: encoding.len(), expected: expected_length, - }) + }); } if encoding.len() > expected_length { return Err(DeserializeError::AdditionalInput { provided: encoding.len(), expected: expected_length, - }) + }); } } let inner = deserialize_homogeneous_composite(encoding)?; @@ -251,7 +251,7 @@ where match next { PathElement::Index(i) => { if *i >= N { - return Err(MerkleizationError::InvalidPathElement(next.clone())) + return Err(MerkleizationError::InvalidPathElement(next.clone())); } let chunk_position = i * T::item_length() / 32; let child = From aba2b56c3ac5563d969c311559a4b9b4308dbb4b Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 12:29:44 +0200 Subject: [PATCH 2/7] feat: add criterion benchmarks --- rust-toolchain.toml | 2 +- ssz-rs/benches/proofs.rs | 47 +++++++++++++++++++- ssz-rs/src/merkleization/hasher.rs | 69 ++++++++++++++++++------------ 3 files changed, 89 insertions(+), 29 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 22048ac5..639f4f17 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.70.0" +channel = "1.74.0" diff --git a/ssz-rs/benches/proofs.rs b/ssz-rs/benches/proofs.rs index f328e4d9..cf0e4da4 100644 --- a/ssz-rs/benches/proofs.rs +++ b/ssz-rs/benches/proofs.rs @@ -1 +1,46 @@ -fn main() {} +use criterion::{criterion_group, criterion_main, Criterion}; +use ssz_rs::{PathElement, Prove}; + +fn generate_id(name: &str) -> String { + let tag = if cfg!(feature = "hashtree") { + "hashtree" + } else if cfg!(feature = "sha2-asm") { + "sha256-asm" + } else { + "sha256" + }; + + format!("{}_{}", name, tag) +} + +fn bench_hash_tree_root(c: &mut Criterion) { + use ssz_rs::{HashTreeRoot, List}; + + let inner: Vec> = vec![ + vec![0u8, 1u8, 2u8].try_into().unwrap(), + vec![3u8, 4u8, 5u8].try_into().unwrap(), + vec![6u8, 7u8, 8u8].try_into().unwrap(), + vec![9u8, 10u8, 11u8].try_into().unwrap(), + ]; + + // Emulate a transactions tree + let outer: List, 1048576> = List::try_from(inner).unwrap(); + + c.bench_function(&generate_id("hash_tree_root"), |b| { + b.iter(|| { + let _ = outer.hash_tree_root().unwrap(); + }) + }); + + // let root = outer.hash_tree_root().unwrap(); + let index = PathElement::from(1); + c.bench_function(&generate_id("generate_proof"), |b| { + b.iter(|| { + let (_proof, _witness) = outer.prove(&[index.clone()]).unwrap(); + }) + }); +} + +criterion_group!(benches, bench_hash_tree_root,); + +criterion_main!(benches); diff --git a/ssz-rs/src/merkleization/hasher.rs b/ssz-rs/src/merkleization/hasher.rs index a8911d3a..0792e417 100644 --- a/ssz-rs/src/merkleization/hasher.rs +++ b/ssz-rs/src/merkleization/hasher.rs @@ -9,40 +9,55 @@ use ::sha2::{Digest, Sha256}; #[cfg(feature = "hashtree")] static INIT: Once = Once::new(); +#[inline] +#[cfg(feature = "hashtree")] +pub fn hash_chunks_hashtree( + left: impl AsRef<[u8]>, + right: impl AsRef<[u8]>, +) -> [u8; BYTES_PER_CHUNK] { + // Initialize the hashtree library (once) + INIT.call_once(|| { + hashtree::init(); + }); + + debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); + debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); + + let mut out = [0u8; BYTES_PER_CHUNK]; + + let mut chunks = Vec::with_capacity(2 * BYTES_PER_CHUNK); + chunks.extend_from_slice(left.as_ref()); + chunks.extend_from_slice(right.as_ref()); + + // NOTE: hashtree "chunks" are 64 bytes long, not 32. That's why we + // specify "1" as the chunk count. + hashtree::hash(&mut out, chunks.as_slice(), 1); + + out +} + +#[inline] +#[cfg(not(feature = "hashtree"))] +pub fn hash_chunks_sha256( + left: impl AsRef<[u8]>, + right: impl AsRef<[u8]>, +) -> [u8; BYTES_PER_CHUNK] { + let mut hasher = Sha256::new(); + hasher.update(left.as_ref()); + hasher.update(right.as_ref()); + hasher.finalize_reset().into() +} + /// Function that hashes 2 [BYTES_PER_CHUNK] (32) len byte slices together. Depending on the feature flags, /// this will either use: /// - sha256 (default) /// - sha256 with assembly support (with the "sha2-asm" feature flag) /// - hashtree (with the "hashtree" feature flag) +#[inline] pub fn hash_chunks(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> [u8; BYTES_PER_CHUNK] { #[cfg(feature = "hashtree")] - { - // Initialize the hashtree library (once) - INIT.call_once(|| { - hashtree::init(); - }); - - debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); - debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); - - let mut out = [0u8; BYTES_PER_CHUNK]; - - let mut chunks = Vec::with_capacity(2 * BYTES_PER_CHUNK); - chunks.extend_from_slice(left.as_ref()); - chunks.extend_from_slice(right.as_ref()); - - // NOTE: hashtree "chunks" are 64 bytes long, not 32. That's why we - // specify "1" as the chunk count. - hashtree::hash(&mut out, chunks.as_slice(), 1); - - out - } + return hash_chunks_hashtree(left, right); #[cfg(not(feature = "hashtree"))] - { - let mut hasher = Sha256::new(); - hasher.update(left.as_ref()); - hasher.update(right.as_ref()); - hasher.finalize_reset().into() - } + return hash_chunks_sha256(left, right); } From e90cdbf2ae1ead6e5d7e57db63fe33bac5e56912 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 12:35:35 +0200 Subject: [PATCH 3/7] clippy: fmt --- ssz-rs/src/merkleization/hasher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssz-rs/src/merkleization/hasher.rs b/ssz-rs/src/merkleization/hasher.rs index 0792e417..2bd01017 100644 --- a/ssz-rs/src/merkleization/hasher.rs +++ b/ssz-rs/src/merkleization/hasher.rs @@ -48,8 +48,8 @@ pub fn hash_chunks_sha256( hasher.finalize_reset().into() } -/// Function that hashes 2 [BYTES_PER_CHUNK] (32) len byte slices together. Depending on the feature flags, -/// this will either use: +/// Function that hashes 2 [BYTES_PER_CHUNK] (32) len byte slices together. Depending on the feature +/// flags, this will either use: /// - sha256 (default) /// - sha256 with assembly support (with the "sha2-asm" feature flag) /// - hashtree (with the "hashtree" feature flag) From 80e66200b2468e9f8a38c55ab6573c23b2a67d9c Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 12:39:03 +0200 Subject: [PATCH 4/7] refactor: rename benches --- ssz-rs/Cargo.toml | 2 +- ssz-rs/benches/{proofs.rs => merkleization.rs} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename ssz-rs/benches/{proofs.rs => merkleization.rs} (92%) diff --git a/ssz-rs/Cargo.toml b/ssz-rs/Cargo.toml index 059cbc8b..1b722908 100644 --- a/ssz-rs/Cargo.toml +++ b/ssz-rs/Cargo.toml @@ -40,5 +40,5 @@ criterion = { version = "0.5", features = ["html_reports"] } sha2 = "0.9.8" [[bench]] -name = "proofs" +name = "merkleization" harness = false diff --git a/ssz-rs/benches/proofs.rs b/ssz-rs/benches/merkleization.rs similarity index 92% rename from ssz-rs/benches/proofs.rs rename to ssz-rs/benches/merkleization.rs index cf0e4da4..4159b003 100644 --- a/ssz-rs/benches/proofs.rs +++ b/ssz-rs/benches/merkleization.rs @@ -13,7 +13,7 @@ fn generate_id(name: &str) -> String { format!("{}_{}", name, tag) } -fn bench_hash_tree_root(c: &mut Criterion) { +fn bench_merkleization(c: &mut Criterion) { use ssz_rs::{HashTreeRoot, List}; let inner: Vec> = vec![ @@ -41,6 +41,6 @@ fn bench_hash_tree_root(c: &mut Criterion) { }); } -criterion_group!(benches, bench_hash_tree_root,); +criterion_group!(benches, bench_merkleization,); criterion_main!(benches); From 61637581fcf7f1a7ce8f820e156e9792b1d4295d Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 20:16:59 +0200 Subject: [PATCH 5/7] feat: get rid of allocation in `hash_chunks_hashtree` --- ssz-rs/src/merkleization/hasher.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ssz-rs/src/merkleization/hasher.rs b/ssz-rs/src/merkleization/hasher.rs index 2bd01017..9bc742ba 100644 --- a/ssz-rs/src/merkleization/hasher.rs +++ b/ssz-rs/src/merkleization/hasher.rs @@ -25,13 +25,14 @@ pub fn hash_chunks_hashtree( let mut out = [0u8; BYTES_PER_CHUNK]; - let mut chunks = Vec::with_capacity(2 * BYTES_PER_CHUNK); - chunks.extend_from_slice(left.as_ref()); - chunks.extend_from_slice(right.as_ref()); + let mut chunks = [0u8; 2 * BYTES_PER_CHUNK]; + + chunks[..BYTES_PER_CHUNK].copy_from_slice(left.as_ref()); + chunks[BYTES_PER_CHUNK..].copy_from_slice(right.as_ref()); // NOTE: hashtree "chunks" are 64 bytes long, not 32. That's why we // specify "1" as the chunk count. - hashtree::hash(&mut out, chunks.as_slice(), 1); + hashtree::hash(&mut out, &chunks, 1); out } From 2f5fbd59a3fb71784311d3581518c71eaddce8bf Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 20:33:13 +0200 Subject: [PATCH 6/7] fix: add chunk length assertions in `hash_chunks_sha256` --- ssz-rs/src/merkleization/hasher.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ssz-rs/src/merkleization/hasher.rs b/ssz-rs/src/merkleization/hasher.rs index 9bc742ba..b2223825 100644 --- a/ssz-rs/src/merkleization/hasher.rs +++ b/ssz-rs/src/merkleization/hasher.rs @@ -43,6 +43,9 @@ pub fn hash_chunks_sha256( left: impl AsRef<[u8]>, right: impl AsRef<[u8]>, ) -> [u8; BYTES_PER_CHUNK] { + debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); + debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); + let mut hasher = Sha256::new(); hasher.update(left.as_ref()); hasher.update(right.as_ref()); From f8f2e2c4f2b226709b14c68ed24a8e53b063b6be Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 6 Sep 2024 20:38:42 +0200 Subject: [PATCH 7/7] refactor: everything in `hash_chunks` --- ssz-rs/src/merkleization/hasher.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/ssz-rs/src/merkleization/hasher.rs b/ssz-rs/src/merkleization/hasher.rs index b2223825..46d465b3 100644 --- a/ssz-rs/src/merkleization/hasher.rs +++ b/ssz-rs/src/merkleization/hasher.rs @@ -11,18 +11,12 @@ static INIT: Once = Once::new(); #[inline] #[cfg(feature = "hashtree")] -pub fn hash_chunks_hashtree( - left: impl AsRef<[u8]>, - right: impl AsRef<[u8]>, -) -> [u8; BYTES_PER_CHUNK] { +fn hash_chunks_hashtree(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> [u8; BYTES_PER_CHUNK] { // Initialize the hashtree library (once) INIT.call_once(|| { hashtree::init(); }); - debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); - debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); - let mut out = [0u8; BYTES_PER_CHUNK]; let mut chunks = [0u8; 2 * BYTES_PER_CHUNK]; @@ -39,13 +33,7 @@ pub fn hash_chunks_hashtree( #[inline] #[cfg(not(feature = "hashtree"))] -pub fn hash_chunks_sha256( - left: impl AsRef<[u8]>, - right: impl AsRef<[u8]>, -) -> [u8; BYTES_PER_CHUNK] { - debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); - debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); - +fn hash_chunks_sha256(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> [u8; BYTES_PER_CHUNK] { let mut hasher = Sha256::new(); hasher.update(left.as_ref()); hasher.update(right.as_ref()); @@ -59,6 +47,9 @@ pub fn hash_chunks_sha256( /// - hashtree (with the "hashtree" feature flag) #[inline] pub fn hash_chunks(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> [u8; BYTES_PER_CHUNK] { + debug_assert!(left.as_ref().len() == BYTES_PER_CHUNK); + debug_assert!(right.as_ref().len() == BYTES_PER_CHUNK); + #[cfg(feature = "hashtree")] return hash_chunks_hashtree(left, right);