diff --git a/sha3/src/lib.rs b/sha3/src/lib.rs index 394faa383..7941e349f 100644 --- a/sha3/src/lib.rs +++ b/sha3/src/lib.rs @@ -93,6 +93,9 @@ const SHA3: u8 = 0x06; const SHAKE: u8 = 0x1f; const CSHAKE: u8 = 0x4; +// Round counts +const TURBO_SHAKE_ROUND_COUNT: usize = 12; + impl_sha3!(Keccak224Core, Keccak224, U28, U144, KECCAK, "Keccak-224"); impl_sha3!(Keccak256Core, Keccak256, U32, U136, KECCAK, "Keccak-256"); impl_sha3!(Keccak384Core, Keccak384, U48, U104, KECCAK, "Keccak-384"); @@ -165,6 +168,25 @@ impl_shake!( "2.16.840.1.101.3.4.2.11", ); +impl_turbo_shake!( + TurboShake128Core, + TurboShake128, + TurboShake128ReaderCore, + TurboShake128Reader, + U168, + "TurboSHAKE128", + "", +); +impl_turbo_shake!( + TurboShake256Core, + TurboShake256, + TurboShake256ReaderCore, + TurboShake256Reader, + U136, + "TurboSHAKE256", + "", +); + impl_cshake!( CShake128Core, CShake128, diff --git a/sha3/src/macros.rs b/sha3/src/macros.rs index 4b8397d44..f26291305 100644 --- a/sha3/src/macros.rs +++ b/sha3/src/macros.rs @@ -193,7 +193,7 @@ macro_rules! impl_shake { fn read_block(&mut self) -> Block { let mut block = Block::::default(); self.state.as_bytes(&mut block); - self.state.apply_f(); + self.state.permute(); block } } @@ -228,6 +228,135 @@ macro_rules! impl_shake { }; } +macro_rules! impl_turbo_shake { + ( + $name:ident, $full_name:ident, $reader:ident, $reader_full:ident, + $rate:ident, $alg_name:expr $(,)? + ) => { + #[doc = "Core "] + #[doc = $alg_name] + #[doc = " hasher state."] + #[derive(Clone)] + #[allow(non_camel_case_types)] + pub struct $name { + domain_separation: u8, + state: Sha3State, + } + + impl $name { + /// Creates a new TurboSHAKE instance with the given domain separation. + /// Note that the domain separation needs to be a byte with a value in + /// the range [0x01, . . . , 0x7F] + pub fn new(domain_separation: u8) -> Self { + assert!((0x01..=0x7F).contains(&domain_separation)); + Self { + domain_separation, + state: Sha3State::new(TURBO_SHAKE_ROUND_COUNT), + } + } + } + + impl HashMarker for $name {} + + impl BlockSizeUser for $name { + type BlockSize = $rate; + } + + impl BufferKindUser for $name { + type BufferKind = Eager; + } + + impl UpdateCore for $name { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + for block in blocks { + self.state.absorb_block(block) + } + } + } + + impl ExtendableOutputCore for $name { + type ReaderCore = $reader; + + #[inline] + fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore { + let pos = buffer.get_pos(); + let block = buffer.pad_with_zeros(); + block[pos] = self.domain_separation; + let n = block.len(); + block[n - 1] |= 0x80; + + self.state.absorb_block(block); + $reader { + state: self.state.clone(), + } + } + } + + impl Reset for $name { + #[inline] + fn reset(&mut self) { + *self = Self::new(self.domain_separation); + } + } + + impl AlgorithmName for $name { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($full_name)) + } + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(concat!(stringify!($name), " { ... }")) + } + } + + #[doc = "Core "] + #[doc = $alg_name] + #[doc = " reader state."] + #[derive(Clone)] + #[allow(non_camel_case_types)] + pub struct $reader { + state: Sha3State, + } + + impl BlockSizeUser for $reader { + type BlockSize = $rate; + } + + impl XofReaderCore for $reader { + #[inline] + fn read_block(&mut self) -> Block { + let mut block = Block::::default(); + self.state.as_bytes(&mut block); + self.state.permute(); + block + } + } + + #[doc = $alg_name] + #[doc = " hasher state."] + pub type $full_name = CoreWrapper<$name>; + + #[doc = $alg_name] + #[doc = " reader state."] + pub type $reader_full = XofReaderCoreWrapper<$reader>; + }; + ( + $name:ident, $full_name:ident, $reader:ident, $reader_full:ident, + $rate:ident, $alg_name:expr, $oid:literal $(,)? + ) => { + impl_turbo_shake!($name, $full_name, $reader, $reader_full, $rate, $alg_name); + + #[cfg(feature = "oid")] + #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] + impl AssociatedOid for $name { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap($oid); + } + }; +} + macro_rules! impl_cshake { ( $name:ident, $full_name:ident, $reader:ident, $reader_full:ident, @@ -384,7 +513,7 @@ macro_rules! impl_cshake { fn read_block(&mut self) -> Block { let mut block = Block::::default(); self.state.as_bytes(&mut block); - self.state.apply_f(); + self.state.permute(); block } } diff --git a/sha3/src/state.rs b/sha3/src/state.rs index 282515434..7bd88d7a7 100644 --- a/sha3/src/state.rs +++ b/sha3/src/state.rs @@ -1,13 +1,31 @@ use core::convert::TryInto; const PLEN: usize = 25; +const DEFAULT_ROUND_COUNT: usize = 24; -#[derive(Clone, Default)] +#[derive(Clone)] pub(crate) struct Sha3State { pub state: [u64; PLEN], + round_count: usize, +} + +impl Default for Sha3State { + fn default() -> Self { + Self { + state: [0u64; PLEN], + round_count: DEFAULT_ROUND_COUNT, + } + } } impl Sha3State { + pub(crate) fn new(round_count: usize) -> Self { + Self { + state: [0u64; PLEN], + round_count, + } + } + #[inline(always)] pub(crate) fn absorb_block(&mut self, block: &[u8]) { debug_assert_eq!(block.len() % 8, 0); @@ -16,7 +34,7 @@ impl Sha3State { *s ^= u64::from_le_bytes(b.try_into().unwrap()); } - keccak::f1600(&mut self.state); + keccak::keccak_p(&mut self.state, self.round_count); } #[inline(always)] @@ -27,7 +45,7 @@ impl Sha3State { } #[inline(always)] - pub(crate) fn apply_f(&mut self) { - keccak::f1600(&mut self.state); + pub(crate) fn permute(&mut self) { + keccak::keccak_p(&mut self.state, self.round_count); } } diff --git a/sha3/tests/data/turboshake128.blb b/sha3/tests/data/turboshake128.blb new file mode 100644 index 000000000..5f1d64d11 Binary files /dev/null and b/sha3/tests/data/turboshake128.blb differ diff --git a/sha3/tests/data/turboshake256.blb b/sha3/tests/data/turboshake256.blb new file mode 100644 index 000000000..6c6635314 Binary files /dev/null and b/sha3/tests/data/turboshake256.blb differ diff --git a/sha3/tests/turboshake.rs b/sha3/tests/turboshake.rs new file mode 100644 index 000000000..3d08df196 --- /dev/null +++ b/sha3/tests/turboshake.rs @@ -0,0 +1,108 @@ +use core::{convert::TryInto, fmt::Debug}; +use digest::ExtendableOutput; + +pub(crate) fn turbo_shake_test( + input: &[u8], + output: &[u8], + truncate_output: usize, + new: F, +) -> Option<&'static str> +where + D: ExtendableOutput + Debug + Clone, + F: Fn() -> D, +{ + let mut hasher = new(); + let mut buf = [0u8; 16 * 1024]; + let buf = &mut buf[..truncate_output + output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + hasher.finalize_xof_into(buf); + if &buf[truncate_output..] != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = new(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + hasher.finalize_xof_into(buf); + if &buf[truncate_output..] != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + + None +} + +macro_rules! new_turbo_shake_test { + ($name:ident, $test_name:expr, $hasher:ty, $hasher_core:ty, $test_func:ident $(,)?) => { + #[test] + fn $name() { + use digest::dev::blobby::Blob5Iterator; + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + + for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() { + let [domain_separation, input, input_pattern_length, output, truncate_output] = + row.unwrap(); + + let input = if (input_pattern_length.len() == 0) { + input.to_vec() + } else if (input.len() == 0) { + let pattern_length = + u64::from_be_bytes(input_pattern_length.try_into().unwrap()); + let mut input = Vec::::new(); + for value in 0..pattern_length { + input.push((value % 0xFB).try_into().unwrap()); + } + input + } else { + panic!( + "\ + failed to read tests data\n\ + input:\t{:02X?}\n\ + input_pattern_length:\t{:02X?}\n", + input, input_pattern_length, + ); + }; + + if let Some(desc) = $test_func( + &input, + output, + u64::from_be_bytes(truncate_output.try_into().unwrap()) + .try_into() + .unwrap(), + || <$hasher>::from_core(<$hasher_core>::new(domain_separation[0])), + ) { + panic!( + "\n\ + Failed test №{}: {}\n\ + input:\t{:02X?}\n\ + output:\t{:02X?}\n", + i, desc, &input, output, + ); + } + } + } + }; +} + +new_turbo_shake_test!( + turboshake128, + "turboshake128", + sha3::TurboShake128, + sha3::TurboShake128Core, + turbo_shake_test, +); +new_turbo_shake_test!( + turboshake256, + "turboshake256", + sha3::TurboShake256, + sha3::TurboShake256Core, + turbo_shake_test, +);