diff --git a/blake2/src/blake2x.rs b/blake2/src/blake2x.rs new file mode 100644 index 000000000..5ed273045 --- /dev/null +++ b/blake2/src/blake2x.rs @@ -0,0 +1,154 @@ +use crate::Blake2Parameters; +use crate::Blake2bVarCore; +use digest::{ + ExtendableOutput, Update, XofReader, + block_buffer::{LazyBuffer, ReadBuffer}, + consts::U64, + core_api::{Buffer, BufferKindUser, UpdateCore, VariableOutputCore}, +}; + +use super::{Blake2b512, BlockSizeUser, InvalidLength, Unsigned}; + +/// Blake2Xb root hasher +pub struct Blake2Xb { + root_hasher: Blake2bVarCore, + buffer: LazyBuffer<::BlockSize>, + max_length: Option, +} + +impl Blake2Xb { + /// Create new instance using provided key. + /// + /// Setting key to `None` indicates unkeyed usage. + /// + /// # Errors + /// + /// If key is `Some`, then its length should not be zero or bigger + /// than the block size. If this conditions is false the method will + /// return an error. + #[inline] + pub fn new(key: Option<&[u8]>, max_length: Option) -> Result { + let kl = key.map_or(0, |k| k.len()); + let bs = ::BlockSize::USIZE; + if key.is_some() && kl == 0 || kl > bs { + return Err(InvalidLength); + } + + let params = Blake2Parameters { + digest_length: 64, + key_size: kl.try_into().unwrap(), + fanout: 1, + depth: 1, + xof_digest_length: Some(max_length.unwrap_or(u32::MAX)), + ..<_>::default() + }; + let root_hasher = Blake2bVarCore::from_params(params); + + let mut hasher = Self { + root_hasher, + buffer: <_>::default(), + max_length, + }; + + if let Some(k) = key { + // Update state with key + hasher.update(k); + // Pad key with zeros + let pad_len = 128 - kl; + let padding = [0; 128]; + hasher.update(&padding[..pad_len]); + } + + Ok(hasher) + } +} + +/// Finalized XOF instance over Blake2b +pub struct Blake2XbReader { + h0: [u8; 64], + buffer: ReadBuffer<::BlockSize>, + node_offset: u32, + total_length: u32, +} + +impl BlockSizeUser for Blake2XbReader { + type BlockSize = U64; +} + +impl BufferKindUser for Blake2XbReader { + type BufferKind = ::BufferKind; +} + +impl XofReader for Blake2XbReader { + fn read(&mut self, buffer: &mut [u8]) { + let Self { buffer: buf, .. } = self; + buf.read(buffer, |block| { + let digest_length = 64.min(self.total_length - self.node_offset * 64) as u8; + + let mut hasher = Blake2bVarCore::from_params(Blake2Parameters { + digest_length, + leaf_length: 64, + node_offset: self.node_offset as u64, + xof_digest_length: Some(self.total_length), + inner_length: 64, + ..<_>::default() + }); + + self.node_offset += 1; + + hasher.finalize_variable_core(&mut Buffer::::new(&self.h0), block); + }); + } +} + +#[cfg(feature = "std")] +impl std::io::Read for Blake2XbReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + XofReader::read(self, buf); + Ok(buf.len()) + } +} + +impl BlockSizeUser for Blake2Xb { + type BlockSize = ::BlockSize; +} + +impl BufferKindUser for Blake2Xb { + type BufferKind = ::BufferKind; +} + +impl Update for Blake2Xb { + fn update(&mut self, data: &[u8]) { + let Self { + root_hasher, + buffer, + .. + } = self; + buffer.digest_blocks(data, |blocks| root_hasher.update_blocks(blocks)); + } +} + +impl ExtendableOutput for Blake2Xb { + type Reader = Blake2XbReader; + + fn finalize_xof(self) -> Self::Reader { + let mut m = <_>::default(); + let Self { + mut root_hasher, + mut buffer, + max_length, + } = self; + root_hasher.finalize_variable_core(&mut buffer, &mut m); + + let mut h0 = [0; 64]; + h0.copy_from_slice(&m); + + Blake2XbReader { + h0, + buffer: <_>::default(), + node_offset: 0, + total_length: max_length.unwrap_or(u32::MAX), + } + } +} diff --git a/blake2/src/lib.rs b/blake2/src/lib.rs index 7b9794f82..b81c10181 100644 --- a/blake2/src/lib.rs +++ b/blake2/src/lib.rs @@ -12,7 +12,7 @@ pub use digest::{self, Digest}; -use core::{fmt, marker::PhantomData, ops::Div}; +use core::{fmt, marker::PhantomData}; use digest::{ CustomizedInit, FixedOutput, HashMarker, InvalidOutputSize, MacMarker, Output, Update, VarOutputCustomized, @@ -22,7 +22,7 @@ use digest::{ UpdateCore, VariableOutputCore, }, block_buffer::{Lazy, LazyBuffer}, - consts::{U4, U16, U32, U64, U128}, + consts::{U16, U32, U64, U128}, crypto_common::{InvalidLength, Key, KeyInit, KeySizeUser}, typenum::{IsLessOrEqual, True, Unsigned}, }; @@ -33,6 +33,7 @@ use digest::{FixedOutputReset, Reset}; use digest::zeroize::{Zeroize, ZeroizeOnDrop}; mod as_bytes; +mod blake2x; mod consts; mod simd; @@ -41,6 +42,7 @@ mod simd; mod macros; use as_bytes::AsBytes; +pub use blake2x::Blake2XbReader; use consts::{BLAKE2B_IV, BLAKE2S_IV}; use simd::{Vector4, u32x4, u64x4}; @@ -95,6 +97,14 @@ pub type Blake2b512 = Blake2b; blake2_mac_impl!(Blake2bMac, Blake2bVarCore, U64, "Blake2b MAC function"); +/// Create a blake2xb generator with maximum output +pub fn blake2xb(seed: &[u8]) -> Blake2XbReader { + use digest::ExtendableOutput; + blake2x::Blake2Xb::new(Some(seed), None) + .unwrap() + .finalize_xof() +} + /// BLAKE2b-512 MAC state. pub type Blake2bMac512 = Blake2bMac; @@ -149,3 +159,122 @@ blake2_mac_impl!(Blake2sMac, Blake2sVarCore, U32, "Blake2s MAC function"); /// BLAKE2s-256 MAC state. pub type Blake2sMac256 = Blake2sMac; + +#[derive(Clone, Copy, Default)] +struct Blake2Parameters<'a> { + digest_length: u8, + key_size: u8, + fanout: u8, + depth: u8, + leaf_length: u32, + node_offset: u64, + xof_digest_length: Option, + node_depth: u8, + inner_length: u8, + salt: &'a [u8], + persona: &'a [u8], +} + +macro_rules! pair_from_bytes { + ($word:ident, $data:expr, $dword_len:literal) => { + if $data.len() < $dword_len { + let mut padded_data = [0; $dword_len]; + for i in 0..$data.len() { + padded_data[i] = $data[i]; + } + ( + $word::from_le_bytes(padded_data[0..$dword_len / 2].try_into().unwrap()), + $word::from_le_bytes( + padded_data[$dword_len / 2..padded_data.len()] + .try_into() + .unwrap(), + ), + ) + } else { + ( + $word::from_le_bytes($data[0..$data.len() / 2].try_into().unwrap()), + $word::from_le_bytes($data[$data.len() / 2..$data.len()].try_into().unwrap()), + ) + } + }; +} + +// Private helper trait +trait ToParamBlock { + fn to_param_block(self) -> [W; 8]; +} + +impl ToParamBlock for Blake2Parameters<'_> { + fn to_param_block(self) -> [u64; 8] { + assert!(self.key_size <= 64); + assert!(self.digest_length <= 64); + + // The number of bytes needed to express two words. + let length = 16; + assert!(self.salt.len() <= length); + assert!(self.persona.len() <= length); + + // Build a parameter block + let mut p = [0; 8]; + p[0] = (self.digest_length as u64) + ^ ((self.key_size as u64) << 8) + ^ ((self.fanout as u64) << 16) + ^ ((self.depth as u64) << 24) + ^ ((self.leaf_length as u64) << 32); + + p[1] = match self.xof_digest_length { + None => self.node_offset, + Some(xof_len) => { + assert!(self.node_offset <= u32::MAX as u64); + self.node_offset ^ ((xof_len as u64) << 32) + } + }; + p[2] = (self.node_depth as u64) ^ ((self.inner_length as u64) << 8); + + // salt is two words long + (p[4], p[5]) = pair_from_bytes!(u64, self.salt, 16); + // persona is also two words long + (p[6], p[7]) = pair_from_bytes!(u64, self.persona, 16); + + p + } +} + +impl ToParamBlock for Blake2Parameters<'_> { + fn to_param_block(self) -> [u32; 8] { + assert!(self.key_size <= 32); + assert!(self.digest_length <= 32); + + // The number of bytes needed to express two words. + let length = 8; + assert!(self.salt.len() <= length); + assert!(self.persona.len() <= length); + + // Build a parameter block + let mut p = [0; 8]; + p[0] = (self.digest_length as u32) + ^ ((self.key_size as u32) << 8) + ^ ((self.fanout as u32) << 16) + ^ ((self.depth as u32) << 24); + p[1] = self.leaf_length.to_le(); + + (p[2], p[3]) = match self.xof_digest_length { + None => { + assert!(self.node_offset < 1 << 48); + pair_from_bytes!(u32, self.node_offset.to_le_bytes(), 8) + } + Some(xof_len) => { + assert!(self.node_offset <= u32::MAX as u64); + ((self.node_offset as u32).to_le(), xof_len.to_le()) + } + }; + p[3] ^= ((self.node_depth as u32) << 16) ^ ((self.inner_length as u32) << 24); + + // salt is two words long + (p[4], p[5]) = pair_from_bytes!(u32, self.salt, 8); + // persona is also two words long + (p[6], p[7]) = pair_from_bytes!(u32, self.persona, 8); + + p + } +} diff --git a/blake2/src/macros.rs b/blake2/src/macros.rs index a29fcdd2c..d1027494c 100644 --- a/blake2/src/macros.rs +++ b/blake2/src/macros.rs @@ -30,55 +30,20 @@ macro_rules! blake2_impl { key_size: usize, output_size: usize, ) -> Self { - assert!(key_size <= $bytes::to_usize()); - assert!(output_size <= $bytes::to_usize()); - - // The number of bytes needed to express two words. - let length = $bytes::to_usize() / 4; - assert!(salt.len() <= length); - assert!(persona.len() <= length); - - // Build a parameter block - let mut p = [0 as $word; 8]; - p[0] = 0x0101_0000 ^ ((key_size as $word) << 8) ^ (output_size as $word); - - // salt is two words long - if salt.len() < length { - let mut padded_salt = Array::>::Output>::default(); - for i in 0..salt.len() { - padded_salt[i] = salt[i]; - } - p[4] = $word::from_le_bytes(padded_salt[0..length / 2].try_into().unwrap()); - p[5] = $word::from_le_bytes( - padded_salt[length / 2..padded_salt.len()] - .try_into() - .unwrap(), - ); - } else { - p[4] = $word::from_le_bytes(salt[0..salt.len() / 2].try_into().unwrap()); - p[5] = - $word::from_le_bytes(salt[salt.len() / 2..salt.len()].try_into().unwrap()); - } - - // persona is also two words long - if persona.len() < length { - let mut padded_persona = Array::>::Output>::default(); - for i in 0..persona.len() { - padded_persona[i] = persona[i]; - } - p[6] = $word::from_le_bytes(padded_persona[0..length / 2].try_into().unwrap()); - p[7] = $word::from_le_bytes( - padded_persona[length / 2..padded_persona.len()] - .try_into() - .unwrap(), - ); - } else { - p[6] = $word::from_le_bytes(persona[0..length / 2].try_into().unwrap()); - p[7] = $word::from_le_bytes( - persona[length / 2..persona.len()].try_into().unwrap(), - ); - } + let p = Blake2Parameters { + digest_length: output_size.try_into().unwrap(), + key_size: key_size.try_into().unwrap(), + fanout: 1, + depth: 1, + salt, + persona, + ..<_>::default() + }; + Self::from_params(p) + } + fn from_params(p: Blake2Parameters) -> Self { + let p = p.to_param_block(); let h = [ Self::iv0() ^ $vec::new(p[0], p[1], p[2], p[3]), Self::iv1() ^ $vec::new(p[4], p[5], p[6], p[7]), diff --git a/blake2/tests/xof.rs b/blake2/tests/xof.rs new file mode 100644 index 000000000..db9eddc08 --- /dev/null +++ b/blake2/tests/xof.rs @@ -0,0 +1,30 @@ +use blake2::blake2xb; +use digest::XofReader; +use hex_literal::hex; + +#[test] +fn blake2bx() { + let seed = hex!( + "7201a801c4f9957c7665c2fd42761f5d" + "a6c05551f15c2153788ba70d9560d7ee" + ); + let mut b = blake2xb(&seed[..]); + + let expected = hex!( + "4bd410911bf5dcb1992eb723835498da" + "bf58ce3482393c2bd2aa3b79c4e22cb8" + "06e631652e2aff3c339864512eddc1e0" + "2717b2ebd499a6e9e1b8967d230054a4" + "1658a3f4fe04b0629fc8e69f6bf51de7" + "59090ce54d82c0dadac921a33f18b1b6" + "be8e9b124d46f26b9cb0dbecae21f504" + "886bc0753e9e62d498dfb018b34a14d5" + "fceef4c0d978e1da27a071564d7ebd56" + "fd092765199e1791ddad7b601d26ce39" + "2639ad17c2eb607f9e82782e5f725d19" + "69b6b4f08b919ff4c7f41c04a9b8ee08" + ); + let mut buf = [0; 64 * 3]; + b.read(&mut buf); + assert_eq!(expected, buf); +}