arnaucube
arnaucube/miden-cryptoPublic Self-hosted repository
feat: optimized hash_elements for blake3 hasher
Bobbin Threadbare bobbinth@protonmail.com
Thu, 23 Feb 2023 17:06:19 -0800
commit

433b467953c15d760166fd4c9a82f2be62dd5481

parent

f46d913b2046700bd215564166bba22e77290902

3 files changed, 45 insertions(+), 10 deletions(-)

jump to
M benches/README.md → benches/README.md

@@ -28,7 +28,7 @@ #### Scenario 2: Sequential hashing of 100 elements `h([a_0,...,a_99])`

| Function | BLAKE3 | SHA3 | Poseidon | Rp64_256 | RPO_256 | | ------------------- | -------| ------- | --------- | --------- | ------- | -| Apple M1 Pro | 1.1 us | 1.5 us | 19.4 us | 118 us | 70 us | +| Apple M1 Pro | 1.0 us | 1.5 us | 19.4 us | 118 us | 70 us | | Apple M2 | 1.0 us | 1.5 us | 17.4 us | 103 us | 65 us | | Amazon Graviton 3 | 1.4 us | | | | 114 us | | AMD Ryzen 9 5950X | 0.8 us | 1.7 us | 15.7 us | 120 us | 72 us |
M src/hash/blake/mod.rs → src/hash/blake/mod.rs

@@ -1,7 +1,5 @@

use super::{Digest, ElementHasher, Felt, FieldElement, Hasher, StarkField}; -use crate::utils::{ - uninit_vector, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, -}; +use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use core::{ mem::{size_of, transmute, transmute_copy}, ops::Deref,

@@ -290,15 +288,25 @@ // this is a compile-time branch so it is for free

let digest = if Felt::IS_CANONICAL { blake3::hash(E::elements_as_bytes(elements)) } else { - let base_elements = E::slice_as_base_elements(elements); - let blen = base_elements.len() << 3; + let mut hasher = blake3::Hasher::new(); - let mut bytes = unsafe { uninit_vector(blen) }; - for (idx, element) in base_elements.iter().enumerate() { - bytes[idx * 8..(idx + 1) * 8].copy_from_slice(&element.as_int().to_le_bytes()); + // BLAKE3 state is 64 bytes - so, we can absorb 64 bytes into the state in a single + // permutation. we move the elements into the hasher via the buffer to give the CPU + // a chance to process multiple element-to-byte conversions in parallel + let mut buf = [0_u8; 64]; + let mut chunk_iter = E::slice_as_base_elements(elements).chunks_exact(8); + for chunk in chunk_iter.by_ref() { + for i in 0..8 { + buf[i * 8..(i + 1) * 8].copy_from_slice(&chunk[i].as_int().to_le_bytes()); + } + hasher.update(&buf); } - blake3::hash(&bytes) + for element in chunk_iter.remainder() { + hasher.update(&element.as_int().to_le_bytes()); + } + + hasher.finalize() }; *shrink_bytes(&digest.into()) }
M src/hash/blake/tests.rs → src/hash/blake/tests.rs

@@ -1,6 +1,22 @@

use super::*; use crate::utils::collections::Vec; use proptest::prelude::*; +use rand_utils::rand_vector; + +#[test] +fn blake3_hash_elements() { + // test multiple of 8 + let elements = rand_vector::<Felt>(16); + let expected = compute_expected_element_hash(&elements); + let actual: [u8; 32] = hash_elements(&elements); + assert_eq!(&expected, &actual); + + // test not multiple of 8 + let elements = rand_vector::<Felt>(17); + let expected = compute_expected_element_hash(&elements); + let actual: [u8; 32] = hash_elements(&elements); + assert_eq!(&expected, &actual); +} proptest! { #[test]

@@ -18,3 +34,14 @@ fn blake256_wont_panic_with_arbitrary_input(ref vec in any::<Vec<u8>>()) {

Blake3_256::hash(vec); } } + +// HELPER FUNCTIONS +// ================================================================================================ + +fn compute_expected_element_hash(elements: &[Felt]) -> blake3::Hash { + let mut bytes = Vec::new(); + for element in elements.iter() { + bytes.extend_from_slice(&element.as_int().to_le_bytes()); + } + blake3::hash(&bytes) +}