diff --git a/Cargo.toml b/Cargo.toml index d8d4d5d..b9b1f46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,10 +15,11 @@ name = "hash" harness = false [features] -default = ["std", "winter_crypto/default", "winter_math/default", "winter_utils/default"] -std = ["winter_crypto/std", "winter_math/std", "winter_utils/std"] +default = ["blake3/default", "std", "winter_crypto/default", "winter_math/default", "winter_utils/default"] +std = ["blake3/std", "winter_crypto/std", "winter_math/std", "winter_utils/std"] [dependencies] +blake3 = { version = "1.0", default-features = false } winter_crypto = { version = "0.4.1", package = "winter-crypto", default-features = false } winter_math = { version = "0.4.1", package = "winter-math", default-features = false } winter_utils = { version = "0.4.1", package = "winter-utils", default-features = false } diff --git a/src/hash/blake/mod.rs b/src/hash/blake/mod.rs new file mode 100644 index 0000000..23fe897 --- /dev/null +++ b/src/hash/blake/mod.rs @@ -0,0 +1,230 @@ +use crate::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, ElementHasher, Felt, + FieldElement, HashFn, Serializable, StarkField, +}; +use core::{ + mem::{size_of, transmute, transmute_copy}, + ops::Deref, + slice::from_raw_parts, +}; + +#[cfg(test)] +mod tests; + +// BLAKE3 N-BIT OUTPUT +// ================================================================================================ + +/// N-bytes output of a blake3 function. +/// +/// Note: `N` can't be greater than `32` because [`Digest::as_bytes`] currently supports only 32 +/// bytes. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Blake3Digest([u8; N]); + +impl Default for Blake3Digest { + fn default() -> Self { + Self([0; N]) + } +} + +impl Deref for Blake3Digest { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serializable for Blake3Digest { + fn write_into(&self, target: &mut W) { + target.write_u8_slice(&self.0); + } +} + +impl Deserializable for Blake3Digest { + fn read_from(source: &mut R) -> Result { + source.read_u8_array().map(Self) + } +} + +impl Digest for Blake3Digest { + fn as_bytes(&self) -> [u8; 32] { + // compile-time assertion + assert!(N <= 32, "digest currently supports only 32 bytes!"); + expand_bytes(&self.0) + } +} + +// BLAKE3 256-BIT OUTPUT +// ================================================================================================ + +/// 256-bit output blake3 hasher. +pub struct Blake3_256; + +impl HashFn for Blake3_256 { + type Digest = Blake3Digest<32>; + + fn hash(bytes: &[u8]) -> Self::Digest { + Blake3Digest(blake3::hash(bytes).into()) + } + + fn merge(values: &[Self::Digest; 2]) -> Self::Digest { + Self::hash(prepare_merge(values)) + } + + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { + let mut hasher = blake3::Hasher::new(); + hasher.update(&seed.0); + hasher.update(&value.to_le_bytes()); + Blake3Digest(hasher.finalize().into()) + } +} + +impl ElementHasher for Blake3_256 { + type BaseField = Felt; + + fn hash_elements(elements: &[E]) -> Self::Digest + where + E: FieldElement, + { + Blake3Digest(hash_elements(elements)) + } +} + +// BLAKE3 192-BIT OUTPUT +// ================================================================================================ + +/// 192-bit output blake3 hasher. +pub struct Blake3_192; + +impl HashFn for Blake3_192 { + type Digest = Blake3Digest<24>; + + fn hash(bytes: &[u8]) -> Self::Digest { + Blake3Digest(*shrink_bytes(&blake3::hash(bytes).into())) + } + + fn merge(values: &[Self::Digest; 2]) -> Self::Digest { + Self::hash(prepare_merge(values)) + } + + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { + let mut hasher = blake3::Hasher::new(); + hasher.update(&seed.0); + hasher.update(&value.to_le_bytes()); + Blake3Digest(*shrink_bytes(&hasher.finalize().into())) + } +} + +impl ElementHasher for Blake3_192 { + type BaseField = Felt; + + fn hash_elements(elements: &[E]) -> Self::Digest + where + E: FieldElement, + { + Blake3Digest(hash_elements(elements)) + } +} + +// BLAKE3 160-BIT OUTPUT +// ================================================================================================ + +/// 160-bit output blake3 hasher. +pub struct Blake3_160; + +impl HashFn for Blake3_160 { + type Digest = Blake3Digest<20>; + + fn hash(bytes: &[u8]) -> Self::Digest { + Blake3Digest(*shrink_bytes(&blake3::hash(bytes).into())) + } + + fn merge(values: &[Self::Digest; 2]) -> Self::Digest { + Self::hash(prepare_merge(values)) + } + + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { + let mut hasher = blake3::Hasher::new(); + hasher.update(&seed.0); + hasher.update(&value.to_le_bytes()); + Blake3Digest(*shrink_bytes(&hasher.finalize().into())) + } +} + +impl ElementHasher for Blake3_160 { + type BaseField = Felt; + + fn hash_elements(elements: &[E]) -> Self::Digest + where + E: FieldElement, + { + Blake3Digest(hash_elements(elements)) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Zero-copy ref shrink to array. +fn shrink_bytes(bytes: &[u8; M]) -> &[u8; N] { + // compile-time assertion + assert!( + M >= N, + "N should fit in M so it can be safely transmuted into a smaller slice!" + ); + // safety: bytes len is asserted + unsafe { transmute(bytes) } +} + +/// Hash the elements into bytes and shrink the output. +fn hash_elements(elements: &[E]) -> [u8; N] +where + E: FieldElement, +{ + // don't leak assumptions from felt and check its actual implementation. + // 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 { + E::as_base_elements(elements) + .iter() + .fold(blake3::Hasher::new(), |mut hasher, felt| { + hasher.update(&felt.as_int().to_le_bytes()); + hasher + }) + .finalize() + }; + *shrink_bytes(&digest.into()) +} + +/// Owned bytes expansion. +fn expand_bytes(bytes: &[u8; M]) -> [u8; N] { + // compile-time assertion + assert!(M <= N, "M should fit in N so M can be expanded!"); + // this branch is constant so it will be optimized to be either one of the variants in release + // mode + if M == N { + // safety: the sizes are checked to be the same + unsafe { transmute_copy(bytes) } + } else { + let mut expanded = [0u8; N]; + expanded[..M].copy_from_slice(bytes); + expanded + } +} + +// Cast the slice into contiguous bytes. +fn prepare_merge(args: &[D; N]) -> &[u8] +where + D: Deref, +{ + // compile-time assertion + assert!(N > 0, "N shouldn't represent an empty slice!"); + let values = args.as_ptr() as *const u8; + let len = size_of::() * N; + // safety: the values are tested to be contiguous + let bytes = unsafe { from_raw_parts(values, len) }; + debug_assert_eq!(args[0].deref(), &bytes[..len / N]); + bytes +} diff --git a/src/hash/blake/tests.rs b/src/hash/blake/tests.rs new file mode 100644 index 0000000..d397828 --- /dev/null +++ b/src/hash/blake/tests.rs @@ -0,0 +1,20 @@ +use super::*; +use crate::Vec; +use proptest::prelude::*; + +proptest! { + #[test] + fn blake160_wont_panic_with_arbitrary_input(ref vec in any::>()) { + Blake3_160::hash(vec); + } + + #[test] + fn blake192_wont_panic_with_arbitrary_input(ref vec in any::>()) { + Blake3_192::hash(vec); + } + + #[test] + fn blake256_wont_panic_with_arbitrary_input(ref vec in any::>()) { + Blake3_256::hash(vec); + } +} diff --git a/src/hash/mod.rs b/src/hash/mod.rs index dec2761..30562c5 100644 --- a/src/hash/mod.rs +++ b/src/hash/mod.rs @@ -1,5 +1,8 @@ use crate::{ElementHasher, HashFn}; +mod blake; +pub use blake::{Blake3Digest, Blake3_160, Blake3_192, Blake3_256}; + mod rpo; pub use rpo::Rpo256 as Hasher; pub use rpo::{INV_MDS, MDS};