diff --git a/Cargo.lock b/Cargo.lock index 022295ef..4bb575a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "ascon", "digest", "hex-literal", + "sponge-cursor", ] [[package]] @@ -123,6 +124,7 @@ dependencies = [ "digest", "hex-literal", "keccak", + "sponge-cursor", ] [[package]] @@ -204,6 +206,7 @@ dependencies = [ "digest", "hex-literal", "keccak", + "sponge-cursor", ] [[package]] @@ -355,6 +358,15 @@ dependencies = [ "hex-literal", ] +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "git+https://github.com/RustCrypto/utils?branch=add-sponge-cursor#d5900bf4ff1953757ad2a0a63d427c3e28ef6b26" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "streebog" version = "0.11.0" @@ -398,6 +410,7 @@ dependencies = [ "digest", "hex-literal", "keccak", + "sponge-cursor", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9675dbbd..89605ea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ opt-level = 2 [patch.crates-io] sha1 = { path = "sha1" } whirlpool = { path = "whirlpool" } +sponge-cursor = { git = "https://github.com/RustCrypto/utils", branch = "add-sponge-cursor" } diff --git a/ascon-xof128/Cargo.toml b/ascon-xof128/Cargo.toml index 6d47a473..7a2cffe1 100644 --- a/ascon-xof128/Cargo.toml +++ b/ascon-xof128/Cargo.toml @@ -13,17 +13,18 @@ categories = ["cryptography", "no-std"] description = "Implementation of Ascon-XOF128 and Ascon-СXOF128" [dependencies] -digest = "0.11" +digest = { version = "0.11", default-features = false } ascon = "0.5" +sponge-cursor = "0.1" [dev-dependencies] -digest = { version = "0.11", features = ["dev"] } +digest = { version = "0.11", default-features = false, features = ["dev"] } hex-literal = "1" [features] default = ["alloc"] alloc = ["digest/alloc"] -zeroize = ["digest/zeroize"] +zeroize = ["digest/zeroize", "sponge-cursor/zeroize"] [package.metadata.docs.rs] all-features = true diff --git a/ascon-xof128/src/cxof.rs b/ascon-xof128/src/cxof.rs index 289c07c9..390202bd 100644 --- a/ascon-xof128/src/cxof.rs +++ b/ascon-xof128/src/cxof.rs @@ -1,11 +1,11 @@ use ascon::State; use digest::{ CollisionResistance, CustomizedInit, ExtendableOutput, HashMarker, OutputSizeUser, Update, - block_api::AlgorithmName, - block_buffer::EagerBuffer, + common::AlgorithmName, common::hazmat::{DeserializeStateError, SerializableState, SerializedState}, - consts::{U8, U16, U32, U48}, + consts::{U8, U16, U32, U41}, }; +use sponge_cursor::SpongeCursor; use crate::{AsconXof128Reader, consts::CXOF_INIT_STATE}; @@ -20,7 +20,7 @@ use crate::{AsconXof128Reader, consts::CXOF_INIT_STATE}; #[derive(Clone, Debug)] pub struct AsconCxof128 { state: State, - buffer: EagerBuffer, + cursor: SpongeCursor, } impl CustomizedInit for AsconCxof128 { @@ -52,8 +52,8 @@ impl CustomizedInit for AsconCxof128 { ascon::permute12(&mut state); - let buffer = Default::default(); - Self { state, buffer } + let cursor = Default::default(); + Self { state, cursor } } } @@ -71,12 +71,8 @@ impl CollisionResistance for AsconCxof128 { impl Update for AsconCxof128 { #[inline] fn update(&mut self, data: &[u8]) { - self.buffer.digest_blocks(data, |blocks| { - for block in blocks { - self.state[0] ^= u64::from_le_bytes(block.0); - ascon::permute12(&mut self.state); - } - }); + self.cursor + .absorb_u64_le(&mut self.state, ascon::permute12, data); } } @@ -84,13 +80,9 @@ impl ExtendableOutput for AsconCxof128 { type Reader = AsconXof128Reader; fn finalize_xof(mut self) -> Self::Reader { - let Self { state, buffer } = &mut self; - let len = buffer.get_pos(); - let last_block = buffer.pad_with_zeros(); - let pad = 1u64 << (8 * len); - state[0] ^= u64::from_le_bytes(last_block.0) ^ pad; - - AsconXof128Reader::new(state) + let pos = self.cursor.pos(); + self.state[0] ^= 1u64 << (8 * pos); + AsconXof128Reader::new(&self.state) } } @@ -102,18 +94,19 @@ impl AlgorithmName for AsconCxof128 { } impl SerializableState for AsconCxof128 { - type SerializedStateSize = U48; + type SerializedStateSize = U41; #[inline] fn serialize(&self) -> SerializedState { let mut res = SerializedState::::default(); - let (state_dst, buffer_dst) = res.split_at_mut(size_of::()); + let (state_dst, cursor_dst) = res.split_at_mut(size_of::()); let mut chunks = state_dst.chunks_exact_mut(size_of::()); for (src, dst) in self.state.iter().zip(&mut chunks) { dst.copy_from_slice(&src.to_le_bytes()); } assert!(chunks.into_remainder().is_empty()); - buffer_dst.copy_from_slice(&self.buffer.serialize()); + assert_eq!(cursor_dst.len(), 1); + cursor_dst[0] = self.cursor.raw_pos(); res } @@ -121,18 +114,16 @@ impl SerializableState for AsconCxof128 { fn deserialize( serialized_state: &SerializedState, ) -> Result { - let (state_src, buffer_src) = serialized_state.split_at(size_of::()); + let (state_src, cursor_src) = serialized_state.split_at(size_of::()); let state = core::array::from_fn(|i| { let n = size_of::(); let chunk = &state_src[n * i..][..n]; u64::from_le_bytes(chunk.try_into().expect("chunk has correct length")) }); - let buffer_src = buffer_src - .try_into() - .expect("buffer_src has correct length"); - EagerBuffer::deserialize(buffer_src) - .map_err(|_| DeserializeStateError) - .map(|buffer| Self { state, buffer }) + assert_eq!(cursor_src.len(), 1); + SpongeCursor::new(cursor_src[0]) + .ok_or(DeserializeStateError) + .map(|cursor| Self { state, cursor }) } } @@ -141,10 +132,9 @@ impl Drop for AsconCxof128 { fn drop(&mut self) { #[cfg(feature = "zeroize")] { - use digest::zeroize::{Zeroize, ZeroizeOnDrop}; - fn assert_zeroize_on_drop(_: &mut T) {} + use digest::zeroize::Zeroize; self.state.zeroize(); - assert_zeroize_on_drop(&mut self.buffer); + self.cursor.zeroize(); } } } diff --git a/ascon-xof128/src/reader.rs b/ascon-xof128/src/reader.rs index a5c76473..fef39221 100644 --- a/ascon-xof128/src/reader.rs +++ b/ascon-xof128/src/reader.rs @@ -1,27 +1,42 @@ use ascon::State; -use digest::{XofReader, block_buffer::ReadBuffer, consts::U8}; +use digest::{XofReader, consts::U8}; +use sponge_cursor::SpongeCursor; /// XOF reader used by Ascon-XOF128 and Ascon-CXOF128 #[derive(Clone, Debug)] pub struct AsconXof128Reader { state: State, - buffer: ReadBuffer, + cursor: SpongeCursor, } impl AsconXof128Reader { pub(super) fn new(state: &State) -> Self { Self { state: *state, - buffer: Default::default(), + cursor: Default::default(), } } } impl XofReader for AsconXof128Reader { + #[inline] fn read(&mut self, buf: &mut [u8]) { - self.buffer.read(buf, |dst| { - ascon::permute12(&mut self.state); - *dst = self.state[0].to_le_bytes().into(); - }); + self.cursor + .squeeze_u64_le(&mut self.state, ascon::permute12, buf); } } + +impl Drop for AsconXof128Reader { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use digest::zeroize::Zeroize; + self.state.zeroize(); + self.cursor.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for AsconXof128Reader {} diff --git a/ascon-xof128/src/xof.rs b/ascon-xof128/src/xof.rs index e25cfc10..e0e5bb2b 100644 --- a/ascon-xof128/src/xof.rs +++ b/ascon-xof128/src/xof.rs @@ -3,11 +3,11 @@ use core::fmt; use digest::{ CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, OutputSizeUser, Reset, Update, - block_api::AlgorithmName, - block_buffer::EagerBuffer, + common::AlgorithmName, common::hazmat::{DeserializeStateError, SerializableState, SerializedState}, - consts::{U8, U16, U32, U48}, + consts::{U8, U16, U32, U41}, }; +use sponge_cursor::SpongeCursor; use crate::{AsconXof128Reader, consts::XOF_INIT_STATE}; @@ -15,7 +15,7 @@ use crate::{AsconXof128Reader, consts::XOF_INIT_STATE}; #[derive(Clone)] pub struct AsconXof128 { state: State, - buffer: EagerBuffer, + cursor: SpongeCursor, } impl Default for AsconXof128 { @@ -23,7 +23,7 @@ impl Default for AsconXof128 { fn default() -> Self { Self { state: XOF_INIT_STATE, - buffer: Default::default(), + cursor: Default::default(), } } } @@ -42,24 +42,15 @@ impl CollisionResistance for AsconXof128 { impl Update for AsconXof128 { #[inline] fn update(&mut self, data: &[u8]) { - self.buffer.digest_blocks(data, |blocks| { - for block in blocks { - self.state[0] ^= u64::from_le_bytes(block.0); - ascon::permute12(&mut self.state); - } - }); + self.cursor + .absorb_u64_le(&mut self.state, ascon::permute12, data); } } impl AsconXof128 { - fn finalize_xof_dirty(&mut self) -> AsconXof128Reader { - let Self { state, buffer } = self; - let len = buffer.get_pos(); - let last_block = buffer.pad_with_zeros(); - let pad = 1u64 << (8 * len); - state[0] ^= u64::from_le_bytes(last_block.0) ^ pad; - - AsconXof128Reader::new(state) + fn pad(&mut self) { + let pos = self.cursor.pos(); + self.state[0] ^= 1u64 << (8 * pos); } } @@ -67,13 +58,15 @@ impl ExtendableOutput for AsconXof128 { type Reader = AsconXof128Reader; fn finalize_xof(mut self) -> Self::Reader { - self.finalize_xof_dirty() + self.pad(); + AsconXof128Reader::new(&self.state) } } impl ExtendableOutputReset for AsconXof128 { fn finalize_xof_reset(&mut self) -> Self::Reader { - let res = self.finalize_xof_dirty(); + self.pad(); + let res = AsconXof128Reader::new(&self.state); self.reset(); res } @@ -83,7 +76,7 @@ impl Reset for AsconXof128 { #[inline] fn reset(&mut self) { self.state = XOF_INIT_STATE; - self.buffer.reset(); + self.cursor = Default::default(); } } @@ -102,18 +95,19 @@ impl fmt::Debug for AsconXof128 { } impl SerializableState for AsconXof128 { - type SerializedStateSize = U48; + type SerializedStateSize = U41; #[inline] fn serialize(&self) -> SerializedState { let mut res = SerializedState::::default(); - let (state_dst, buffer_dst) = res.split_at_mut(size_of::()); + let (state_dst, cursor_dst) = res.split_at_mut(size_of::()); let mut chunks = state_dst.chunks_exact_mut(size_of::()); for (src, dst) in self.state.iter().zip(&mut chunks) { dst.copy_from_slice(&src.to_le_bytes()); } assert!(chunks.into_remainder().is_empty()); - buffer_dst.copy_from_slice(&self.buffer.serialize()); + assert_eq!(cursor_dst.len(), 1); + cursor_dst[0] = self.cursor.raw_pos(); res } @@ -121,18 +115,16 @@ impl SerializableState for AsconXof128 { fn deserialize( serialized_state: &SerializedState, ) -> Result { - let (state_src, buffer_src) = serialized_state.split_at(size_of::()); + let (state_src, cursor_src) = serialized_state.split_at(size_of::()); let state = core::array::from_fn(|i| { let n = size_of::(); let chunk = &state_src[n * i..][..n]; u64::from_le_bytes(chunk.try_into().expect("chunk has correct length")) }); - let buffer_src = buffer_src - .try_into() - .expect("buffer_src has correct length"); - EagerBuffer::deserialize(buffer_src) - .map_err(|_| DeserializeStateError) - .map(|buffer| Self { state, buffer }) + assert_eq!(cursor_src.len(), 1); + SpongeCursor::new(cursor_src[0]) + .ok_or(DeserializeStateError) + .map(|cursor| Self { state, cursor }) } } @@ -141,10 +133,9 @@ impl Drop for AsconXof128 { fn drop(&mut self) { #[cfg(feature = "zeroize")] { - use digest::zeroize::{Zeroize, ZeroizeOnDrop}; - fn assert_zeroize_on_drop(_: &mut T) {} + use digest::zeroize::Zeroize; self.state.zeroize(); - assert_zeroize_on_drop(&mut self.buffer); + self.cursor.zeroize(); } } } diff --git a/ascon-xof128/tests/data/ascon_cxof128_serialization.bin b/ascon-xof128/tests/data/ascon_cxof128_serialization.bin index 166cb41f..20a0b53c 100644 Binary files a/ascon-xof128/tests/data/ascon_cxof128_serialization.bin and b/ascon-xof128/tests/data/ascon_cxof128_serialization.bin differ diff --git a/ascon-xof128/tests/data/ascon_xof128_serialization.bin b/ascon-xof128/tests/data/ascon_xof128_serialization.bin index 53449f1c..6cbe9012 100644 Binary files a/ascon-xof128/tests/data/ascon_xof128_serialization.bin and b/ascon-xof128/tests/data/ascon_xof128_serialization.bin differ diff --git a/ascon-xof128/tests/mod.rs b/ascon-xof128/tests/mod.rs index 860fc329..1db703d2 100644 --- a/ascon-xof128/tests/mod.rs +++ b/ascon-xof128/tests/mod.rs @@ -1,7 +1,7 @@ use ascon_xof128::{AsconCxof128, AsconXof128}; use core::fmt::Debug; use digest::{ - CustomizedInit, ExtendableOutput, + CustomizedInit, ExtendableOutput, Update, common::hazmat::SerializableState, dev::{feed_rand_16mib, xof_reset_test}, }; @@ -35,6 +35,7 @@ fn ascon_cxof128_kat() { #[test] fn ascon_xof128_rand() { let mut h = AsconXof128::default(); + h.update(b"hello"); feed_rand_16mib(&mut h); let ser_state = h.serialize(); @@ -42,8 +43,8 @@ fn ascon_xof128_rand() { assert_eq!(ser_state[..], ser_expected[..]); let expected_hash = hex!( - "E06CA16A362949DE5D05743B663445BE6AD39CD22C2CED839FD937E31E58B1CC" - "85E67F131C61AB55EEE4B8B90C060E078B9CAE1299394498C799C310C66286C8" + "DC7B123723BE1FB8E5D57EDE65BE3C7847674ED1E3DB6E65E1237CD23B8E1B3E" + "B49BC9A253E44A8132A560EB99C7321A947B0152DA5096A45CE7D2F23E03D68A" ); let mut buf = [0u8; 64]; @@ -59,6 +60,7 @@ fn ascon_xof128_rand() { #[test] fn ascon_cxof128_rand() { let mut h = AsconCxof128::new_customized(b"randomized cxof test"); + h.update(b"hello"); feed_rand_16mib(&mut h); let ser_state = h.serialize(); @@ -66,8 +68,8 @@ fn ascon_cxof128_rand() { assert_eq!(ser_state[..], ser_expected[..]); let expected_hash = hex!( - "3E797ECD4EB7373C7283078BFCC53E5B0645B083AF703146A527FEC3AE209E85" - "DAA663270B7687540A221F87C44433906931BFE43BE3BA0333ADFAC62BC8EC1F" + "09922AD2B2ADD3774EC8BF20C720BD5C41AC82142E406B059CA99E77A233CB12" + "727D5246486D93A1419185332E1A2721ED61F538435E152CB23DE2E81BDA804F" ); let mut buf = [0u8; 64]; diff --git a/cshake/Cargo.toml b/cshake/Cargo.toml index 2dedf5fe..c2893a74 100644 --- a/cshake/Cargo.toml +++ b/cshake/Cargo.toml @@ -13,17 +13,18 @@ categories = ["cryptography", "no-std"] description = "Implementation of the cSHAKE family of extendable-output functions (XOFs)" [dependencies] -digest = "0.11" +digest = { version = "0.11", default-features = false } keccak = "0.2" +sponge-cursor = "0.1" [dev-dependencies] -digest = { version = "0.11", features = ["dev"] } +digest = { version = "0.11", default-features = false, features = ["dev"] } hex-literal = "1" [features] default = ["alloc"] alloc = ["digest/alloc"] -zeroize = ["digest/zeroize"] +zeroize = ["digest/zeroize", "sponge-cursor/zeroize"] [package.metadata.docs.rs] all-features = true diff --git a/cshake/src/lib.rs b/cshake/src/lib.rs index bc02e67a..042b6d54 100644 --- a/cshake/src/lib.rs +++ b/cshake/src/lib.rs @@ -11,15 +11,15 @@ pub use digest; -use core::fmt; +use core::{fmt, marker::PhantomData}; use digest::{ CollisionResistance, CustomizedInit, ExtendableOutput, HashMarker, Update, XofReader, - array::Array, - block_api::{AlgorithmName, BlockSizeUser}, - block_buffer::{BlockSizes, EagerBuffer, LazyBuffer, ReadBuffer}, + array::ArraySize, + common::{AlgorithmName, BlockSizeUser}, consts::{U16, U32, U136, U168}, }; use keccak::{Keccak, State1600}; +use sponge_cursor::SpongeCursor; const SHAKE_PAD: u8 = 0x1F; const CSHAKE_PAD: u8 = 0x04; @@ -33,21 +33,22 @@ pub type CShake256 = CShake; /// /// Rate MUST be either [`U168`] or [`U136`] for cSHAKE128 and cSHAKE256 respectively. #[derive(Clone)] -pub struct CShake { +pub struct CShake { state: State1600, - buffer: EagerBuffer, + cursor: SpongeCursor, pad: u8, keccak: Keccak, + _pd: PhantomData, } -impl Default for CShake { +impl Default for CShake { #[inline] fn default() -> Self { Self::new_with_function_name(b"", b"") } } -impl CShake { +impl CShake { /// Creates a new cSHAKE instance with the given function name and customization. /// /// Note that the function name is intended for use by NIST and should only be set to @@ -57,16 +58,16 @@ impl CShake { assert!(Rate::USIZE == 168 || Rate::USIZE == 136, "unsupported rate"); } - let buffer = Default::default(); let keccak = Keccak::new(); let mut state = State1600::default(); if function_name.is_empty() && customization.is_empty() { return Self { state, - buffer, - keccak, + cursor: Default::default(), pad: SHAKE_PAD, + keccak, + _pd: PhantomData, }; } @@ -79,102 +80,83 @@ impl CShake { } keccak.with_f1600(|f1600| { - let mut buffer: LazyBuffer = Default::default(); + let mut cursor: SpongeCursor = Default::default(); let state = &mut state; let mut b = [0u8; 9]; - buffer.digest_blocks(left_encode(Rate::U64, &mut b), |blocks| { - update_blocks(f1600, state, blocks) - }); + cursor.absorb_u64_le(state, f1600, left_encode(Rate::U64, &mut b)); let mut encode_str = |str: &[u8]| { let str_bits_len = 8 * u64::try_from(str.len()) .expect("in practice strings can not be longer than u64::MAX"); let encoded_len = left_encode(str_bits_len, &mut b); - buffer.digest_blocks(encoded_len, |blocks| update_blocks(f1600, state, blocks)); - buffer.digest_blocks(str, |blocks| update_blocks(f1600, state, blocks)); + cursor.absorb_u64_le(state, f1600, encoded_len); + cursor.absorb_u64_le(state, f1600, str); }; encode_str(function_name); encode_str(customization); - update_blocks(f1600, state, &[buffer.pad_with_zeros()]) + if cursor.pos() != 0 { + f1600(state); + } }); Self { state, - buffer, - keccak, + cursor: Default::default(), pad: CSHAKE_PAD, + keccak, + _pd: PhantomData, } } } -impl CustomizedInit for CShake { +impl CustomizedInit for CShake { #[inline] fn new_customized(customization: &[u8]) -> Self { Self::new_with_function_name(&[], customization) } } -impl HashMarker for CShake {} +impl HashMarker for CShake {} -impl BlockSizeUser for CShake { +impl BlockSizeUser for CShake { type BlockSize = Rate; } -impl Update for CShake { +impl Update for CShake { fn update(&mut self, data: &[u8]) { - let Self { - state, - buffer, - keccak, - .. - } = self; - - keccak.with_f1600(|f1600| { - buffer.digest_blocks(data, |blocks| update_blocks(f1600, state, blocks)); + self.keccak.with_f1600(|f1600| { + self.cursor.absorb_u64_le(&mut self.state, f1600, data); }); } } -impl CShake { - fn finalize_dirty(&mut self) { - let Self { - state, - buffer, - pad, - keccak, - } = self; - - let pos = buffer.get_pos(); - let mut block = buffer.pad_with_zeros(); - block[pos] = *pad; - let n = block.len(); - block[n - 1] |= 0x80; - - keccak.with_f1600(|f1600| { - xor_block(state, &block); - f1600(state); - }); - } -} - -impl ExtendableOutput for CShake { +impl ExtendableOutput for CShake { type Reader = CShakeReader; #[inline] fn finalize_xof(mut self) -> Self::Reader { - self.finalize_dirty(); + let pos = self.cursor.pos(); + let word_offset = pos / 8; + let byte_offset = pos % 8; + + let pad = u64::from(self.pad) << (8 * byte_offset); + self.state[word_offset] ^= pad; + self.state[Rate::USIZE / 8 - 1] ^= 1 << 63; + + // Note that `CShakeReader` applies the permutation to the state before reading from it + Self::Reader { state: self.state, + cursor: Default::default(), keccak: self.keccak, - buffer: Default::default(), } } } -impl AlgorithmName for CShake { +impl AlgorithmName for CShake { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { let alg_name = match Rate::USIZE { 168 => "cSHAKE128", @@ -185,7 +167,7 @@ impl AlgorithmName for CShake { } } -impl fmt::Debug for CShake { +impl fmt::Debug for CShake { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let debug_str = match Rate::USIZE { 168 => "CShake128 { ... }", @@ -196,53 +178,39 @@ impl fmt::Debug for CShake { } } -impl Drop for CShake { +impl Drop for CShake { fn drop(&mut self) { #[cfg(feature = "zeroize")] { use digest::zeroize::Zeroize; self.state.zeroize(); + self.cursor.zeroize(); self.pad.zeroize(); - // self.buffer is zeroized by its `Drop` } } } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for CShake {} +impl digest::zeroize::ZeroizeOnDrop for CShake {} /// Generic cSHAKE XOF reader #[derive(Clone)] -pub struct CShakeReader { +pub struct CShakeReader { state: State1600, + cursor: SpongeCursor, keccak: Keccak, - buffer: ReadBuffer, } -impl XofReader for CShakeReader { +impl XofReader for CShakeReader { #[inline] fn read(&mut self, buf: &mut [u8]) { - let Self { - state, - keccak, - buffer, - } = self; - - buffer.read(buf, |block| { - let mut chunks = block.chunks_exact_mut(8); - for (src, dst) in state.iter().zip(&mut chunks) { - dst.copy_from_slice(&src.to_le_bytes()); - } - assert!( - chunks.into_remainder().is_empty(), - "rate is either 136 or 168", - ); - keccak.with_f1600(|f1600| f1600(state)); + self.keccak.with_f1600(|f1600| { + self.cursor.squeeze_u64_le(&mut self.state, f1600, buf); }); } } -impl fmt::Debug for CShakeReader { +impl fmt::Debug for CShakeReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let debug_str = match Rate::USIZE { 168 => "TurboShakeReader128 { ... }", @@ -253,44 +221,22 @@ impl fmt::Debug for CShakeReader { } } -impl Drop for CShakeReader { +impl Drop for CShakeReader { fn drop(&mut self) { #[cfg(feature = "zeroize")] { use digest::zeroize::Zeroize; self.state.zeroize(); - // self.buffer is zeroized by its `Drop` + self.cursor.zeroize(); } } } -// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf#[{"num":68,"gen":0},{"name":"XYZ"},108,440,null] +// See Section 8.3 of NIST SP 800-185: +// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf impl CollisionResistance for CShake128 { type CollisionResistance = U16; } impl CollisionResistance for CShake256 { type CollisionResistance = U32; } - -fn xor_block(state: &mut State1600, block: &[u8]) { - assert!(size_of_val(block) < size_of_val(state)); - - let mut chunks = block.chunks_exact(8); - for (s, chunk) in state.iter_mut().zip(&mut chunks) { - *s ^= u64::from_le_bytes(chunk.try_into().unwrap()); - } - - let rem = chunks.remainder(); - assert!(rem.is_empty(), "block size is equal to 136 or 168"); -} - -fn update_blocks( - f1600: keccak::Fn1600, - state: &mut State1600, - blocks: &[Array], -) { - for block in blocks { - xor_block(state, block); - f1600(state); - } -} diff --git a/k12/Cargo.toml b/k12/Cargo.toml index a2b113f6..6bf79acd 100644 --- a/k12/Cargo.toml +++ b/k12/Cargo.toml @@ -13,17 +13,18 @@ categories = ["cryptography", "no-std"] description = "Implementation of the KangarooTwelve family of extendable-output functions" [dependencies] -digest = "0.11" +digest = { version = "0.11", default-features = false } keccak = { version = "0.2", features = ["parallel"] } +sponge-cursor = "0.1" [dev-dependencies] -digest = { version = "0.11", features = ["alloc", "dev"] } +digest = { version = "0.11", default-features = false, features = ["alloc", "dev"] } hex-literal = "1" [features] default = ["alloc"] alloc = ["digest/alloc"] -zeroize = ["digest/zeroize"] +zeroize = ["digest/zeroize", "sponge-cursor/zeroize"] [package.metadata.docs.rs] all-features = true diff --git a/k12/src/consts.rs b/k12/src/consts.rs index 21be28da..74462704 100644 --- a/k12/src/consts.rs +++ b/k12/src/consts.rs @@ -9,6 +9,4 @@ pub(crate) const INTERMEDIATE_NODE_DS: u8 = 0x0B; pub(crate) const FINAL_NODE_DS: u8 = 0x06; pub(crate) const S0_DELIM: u64 = 0x03; - -/// Padding byte -pub(crate) const PAD: u8 = 0x80; +pub(crate) const PAD: u64 = 1 << 63; diff --git a/k12/src/custom/borrow.rs b/k12/src/custom/borrow.rs index 6efa1d98..063e0233 100644 --- a/k12/src/custom/borrow.rs +++ b/k12/src/custom/borrow.rs @@ -1,7 +1,7 @@ use core::fmt; use digest::{ CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, - block_buffer::BlockSizes, + array::ArraySize, common::{AlgorithmName, BlockSizeUser}, consts::{U16, U32, U136, U168}, }; @@ -10,12 +10,12 @@ use crate::{Kt, KtReader, utils::length_encode}; /// Customized KangarooTwelve hasher generic over rate with borrrowed customization string. #[derive(Clone)] -pub struct CustomRefKt<'a, Rate: BlockSizes> { +pub struct CustomRefKt<'a, Rate: ArraySize> { customization: &'a [u8], inner: Kt, } -impl<'a, Rate: BlockSizes> CustomRefKt<'a, Rate> { +impl<'a, Rate: ArraySize> CustomRefKt<'a, Rate> { /// Create new customized KangarooTwelve hasher with borrrowed customization string. /// /// Note that this is an inherent method and `CustomRefKt` does not implement @@ -29,48 +29,48 @@ impl<'a, Rate: BlockSizes> CustomRefKt<'a, Rate> { } } -impl Default for CustomRefKt<'static, Rate> { +impl Default for CustomRefKt<'static, Rate> { #[inline] fn default() -> Self { Self::new_customized(&[]) } } -impl fmt::Debug for CustomRefKt<'_, Rate> { +impl fmt::Debug for CustomRefKt<'_, Rate> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "CustomKt{} {{ ... }}", 4 * (200 - Rate::USIZE)) } } -impl AlgorithmName for CustomRefKt<'_, Rate> { +impl AlgorithmName for CustomRefKt<'_, Rate> { #[inline] fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { Kt::::write_alg_name(f) } } -impl HashMarker for CustomRefKt<'_, Rate> {} +impl HashMarker for CustomRefKt<'_, Rate> {} -impl BlockSizeUser for CustomRefKt<'_, Rate> { +impl BlockSizeUser for CustomRefKt<'_, Rate> { type BlockSize = Rate; } -impl Update for CustomRefKt<'_, Rate> { +impl Update for CustomRefKt<'_, Rate> { #[inline] fn update(&mut self, data: &[u8]) { self.inner.update(data); } } -impl Reset for CustomRefKt<'_, Rate> { +impl Reset for CustomRefKt<'_, Rate> { #[inline] fn reset(&mut self) { self.inner.reset(); } } -impl CustomRefKt<'_, Rate> { +impl CustomRefKt<'_, Rate> { fn absorb_customization(&mut self) { self.inner.update(self.customization); let len = u64::try_from(self.customization.len()).expect("length always fits into `u64`"); @@ -78,7 +78,7 @@ impl CustomRefKt<'_, Rate> { } } -impl ExtendableOutput for CustomRefKt<'_, Rate> { +impl ExtendableOutput for CustomRefKt<'_, Rate> { type Reader = KtReader; #[inline] @@ -88,7 +88,7 @@ impl ExtendableOutput for CustomRefKt<'_, Rate> { } } -impl ExtendableOutputReset for CustomRefKt<'_, Rate> { +impl ExtendableOutputReset for CustomRefKt<'_, Rate> { #[inline] fn finalize_xof_reset(&mut self) -> Self::Reader { self.absorb_customization(); @@ -100,7 +100,7 @@ impl ExtendableOutputReset for CustomRefKt<'_, Rate> { // `inner` is zeroized by `Drop` and `customization` can not be zeroized #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for CustomRefKt<'_, Rate> {} +impl digest::zeroize::ZeroizeOnDrop for CustomRefKt<'_, Rate> {} /// Customized KT128 hasher with borrowed customization string. pub type CustomRefKt128<'a> = CustomRefKt<'a, U168>; diff --git a/k12/src/custom/owned.rs b/k12/src/custom/owned.rs index 1c109ae5..71ba00bb 100644 --- a/k12/src/custom/owned.rs +++ b/k12/src/custom/owned.rs @@ -5,7 +5,7 @@ use core::fmt; use digest::{ CollisionResistance, CustomizedInit, ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, - block_buffer::BlockSizes, + array::ArraySize, common::{AlgorithmName, BlockSizeUser}, consts::{U16, U32, U136, U168}, }; @@ -14,12 +14,12 @@ use crate::{Kt, KtReader, utils::length_encode}; /// Customized KangarooTwelve hasher generic over rate with owned customization string. #[derive(Clone)] -pub struct CustomKt { +pub struct CustomKt { customization: Vec, inner: Kt, } -impl CustomizedInit for CustomKt { +impl CustomizedInit for CustomKt { #[inline] fn new_customized(customization: &[u8]) -> Self { let len = u64::try_from(customization.len()).expect("length should always fit into `u64`"); @@ -37,48 +37,48 @@ impl CustomizedInit for CustomKt { } } -impl Default for CustomKt { +impl Default for CustomKt { #[inline] fn default() -> Self { Self::new_customized(&[]) } } -impl fmt::Debug for CustomKt { +impl fmt::Debug for CustomKt { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "CustomKt{} {{ ... }}", 4 * (200 - Rate::USIZE)) } } -impl AlgorithmName for CustomKt { +impl AlgorithmName for CustomKt { #[inline] fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { Kt::::write_alg_name(f) } } -impl HashMarker for CustomKt {} +impl HashMarker for CustomKt {} -impl BlockSizeUser for CustomKt { +impl BlockSizeUser for CustomKt { type BlockSize = Rate; } -impl Update for CustomKt { +impl Update for CustomKt { #[inline] fn update(&mut self, data: &[u8]) { self.inner.update(data); } } -impl Reset for CustomKt { +impl Reset for CustomKt { #[inline] fn reset(&mut self) { self.inner.reset(); } } -impl ExtendableOutput for CustomKt { +impl ExtendableOutput for CustomKt { type Reader = KtReader; #[inline] @@ -88,7 +88,7 @@ impl ExtendableOutput for CustomKt { } } -impl ExtendableOutputReset for CustomKt { +impl ExtendableOutputReset for CustomKt { #[inline] fn finalize_xof_reset(&mut self) -> Self::Reader { self.inner.update(&self.customization); @@ -98,7 +98,7 @@ impl ExtendableOutputReset for CustomKt { } } -impl Drop for CustomKt { +impl Drop for CustomKt { #[inline] fn drop(&mut self) { #[cfg(feature = "zeroize")] @@ -111,7 +111,7 @@ impl Drop for CustomKt { } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for CustomKt {} +impl digest::zeroize::ZeroizeOnDrop for CustomKt {} /// Customized KT128 hasher with owned customization string. pub type CustomKt128 = CustomKt; diff --git a/k12/src/lib.rs b/k12/src/lib.rs index 8c86b31a..0677ca88 100644 --- a/k12/src/lib.rs +++ b/k12/src/lib.rs @@ -13,8 +13,8 @@ pub use digest; use core::fmt; use digest::{ CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, - block_api::{AlgorithmName, BlockSizeUser}, - block_buffer::BlockSizes, + array::ArraySize, + common::{AlgorithmName, BlockSizeUser}, consts::{U16, U32, U136, U168}, }; @@ -35,23 +35,23 @@ mod utils; pub use custom::*; pub use reader::KtReader; -use consts::{CHUNK_SIZE_U64, FINAL_NODE_DS, INTERMEDIATE_NODE_DS, ROUNDS, SINGLE_NODE_DS}; +use consts::{CHUNK_SIZE_U64, FINAL_NODE_DS, ROUNDS, SINGLE_NODE_DS}; use turbo_shake::TurboShake; -use utils::{copy_cv, length_encode}; +use utils::length_encode; /// KangarooTwelve hasher generic over rate. /// /// Only `U136` and `U168` rates are supported which correspond to KT256 and KT128 respectively. /// Using other rates will result in a compilation error. #[derive(Clone)] -pub struct Kt { +pub struct Kt { accum_tshk: TurboShake, node_tshk: TurboShake, consumed_len: u64, keccak: keccak::Keccak, } -impl Default for Kt { +impl Default for Kt { #[inline] fn default() -> Self { const { assert!(matches!(Rate::USIZE, 136 | 168)) } @@ -64,27 +64,27 @@ impl Default for Kt { } } -impl fmt::Debug for Kt { +impl fmt::Debug for Kt { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "Kt{} {{ ... }}", 4 * (200 - Rate::USIZE)) } } -impl AlgorithmName for Kt { +impl AlgorithmName for Kt { #[inline] fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "KT{}", 4 * (200 - Rate::USIZE)) } } -impl HashMarker for Kt {} +impl HashMarker for Kt {} -impl BlockSizeUser for Kt { +impl BlockSizeUser for Kt { type BlockSize = Rate; } -impl Update for Kt { +impl Update for Kt { #[inline] fn update(&mut self, data: &[u8]) { let keccak = self.keccak; @@ -93,7 +93,7 @@ impl Update for Kt { } } -impl Reset for Kt { +impl Reset for Kt { #[inline] fn reset(&mut self) { self.accum_tshk.reset(); @@ -102,41 +102,38 @@ impl Reset for Kt { } } -impl Kt { +impl Kt { #[inline] fn raw_finalize(&mut self) -> KtReader { let keccak = self.keccak; - keccak.with_p1600::(|p1600| { - if self.consumed_len <= CHUNK_SIZE_U64 { - self.accum_tshk.finalize::(p1600); - } else { + // Note that the reader applies permutation before reading from the state, + // so we only need to absorb the remaining data and pad the state + if self.consumed_len <= CHUNK_SIZE_U64 { + self.accum_tshk.pad::(); + } else { + keccak.with_p1600::(|p1600| { let nodes_len = (self.consumed_len - 1) / CHUNK_SIZE_U64; let partial_node_len = self.consumed_len % CHUNK_SIZE_U64; if partial_node_len != 0 { - self.node_tshk.finalize::(p1600); // TODO: this should be [0u8; {200 - Rate}] let cv_dst = &mut [0u8; 200][..200 - Rate::USIZE]; - copy_cv(self.node_tshk.state(), cv_dst); + self.node_tshk.finalize_intermediate_node(p1600, cv_dst); self.accum_tshk.absorb(p1600, cv_dst); } length_encode(nodes_len, |enc_len| self.accum_tshk.absorb(p1600, enc_len)); self.accum_tshk.absorb(p1600, b"\xFF\xFF"); - self.accum_tshk.finalize::(p1600); - }; - }); - - KtReader { - state: *self.accum_tshk.state(), - buffer: Default::default(), - keccak, - } + self.accum_tshk.pad::(); + }); + }; + + KtReader::new(self.accum_tshk.state(), keccak) } } -impl ExtendableOutput for Kt { +impl ExtendableOutput for Kt { type Reader = KtReader; #[inline] @@ -146,7 +143,7 @@ impl ExtendableOutput for Kt { } } -impl ExtendableOutputReset for Kt { +impl ExtendableOutputReset for Kt { #[inline] fn finalize_xof_reset(&mut self) -> Self::Reader { self.update(&[0x00]); @@ -156,7 +153,7 @@ impl ExtendableOutputReset for Kt { } } -impl Drop for Kt { +impl Drop for Kt { fn drop(&mut self) { #[cfg(feature = "zeroize")] { @@ -168,7 +165,7 @@ impl Drop for Kt { } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for Kt {} +impl digest::zeroize::ZeroizeOnDrop for Kt {} /// KT128 hasher. pub type Kt128 = Kt; @@ -180,12 +177,12 @@ pub type Kt128Reader = KtReader; /// KT256 XOF reader. pub type Kt256Reader = KtReader; +// https://www.rfc-editor.org/rfc/rfc9861.html#section-7-7 impl CollisionResistance for Kt128 { - // https://www.rfc-editor.org/rfc/rfc9861.html#section-7-7 type CollisionResistance = U16; } +// https://www.rfc-editor.org/rfc/rfc9861.html#section-7-8 impl CollisionResistance for Kt256 { - // https://www.rfc-editor.org/rfc/rfc9861.html#section-7-8 type CollisionResistance = U32; } diff --git a/k12/src/node_turbo_shake.rs b/k12/src/node_turbo_shake.rs index 4b6bfba1..5c07c8e5 100644 --- a/k12/src/node_turbo_shake.rs +++ b/k12/src/node_turbo_shake.rs @@ -2,10 +2,7 @@ use crate::{ consts::{CHUNK_SIZE, INTERMEDIATE_NODE_DS, PAD}, utils::{copy_cv, xor_block}, }; -use digest::{ - array::{Array, ArraySize}, - block_buffer::BlockSizes, -}; +use digest::array::{Array, ArraySize}; use keccak::{Fn1600, State1600}; /// Parallel version of TurboSHAKE specialized for computation of chaining values. @@ -15,13 +12,13 @@ use keccak::{Fn1600, State1600}; /// fn par_turbo_shake( /// p1600: ParFn1600, /// data: &[u8; CHUNK_SIZE * B::PAR_SIZE_1600], -/// ) -> [[u8; {200 - RATE}]; B::PAR_SIZE_1600] { ... } +/// ) -> [[u64; {200 - RATE} / 8]; B::PAR_SIZE_1600] { ... } /// ``` /// But it requires advanced const generics or to deal with annoying `typenum`-based trait bounds, /// so instead we use "runtime" asserts which should be optimized out by the compiler, see: /// https://rust.godbolt.org/z/4Y7ervTd7 // TODO(MSRV-1.88): use `as_chunks::()` -pub(crate) fn parallel( +pub(crate) fn parallel( par_p1600: fn(&mut Array), data: &[u8], par_cv_dst: &mut [u8], @@ -75,9 +72,9 @@ pub(crate) fn parallel( /// fn turbo_shake-cv( /// p1600: Fn1600, /// data: &[u8; CHUNK_SIZE], -/// ) -> [u8; {200 - RATE}] { ... } +/// ) -> [u64; {200 - RATE} / 8] { ... } /// ``` -pub(crate) fn scalar(p1600: Fn1600, data: &[u8], cv_dst: &mut [u8]) { +pub(crate) fn scalar(p1600: Fn1600, data: &[u8], cv_dst: &mut [u8]) { assert_eq!(data.len(), CHUNK_SIZE); let cv_size = 200 - Rate::USIZE; assert_eq!(cv_dst.len(), cv_size); @@ -95,28 +92,13 @@ pub(crate) fn scalar(p1600: Fn1600, data: &[u8], cv_dst: &mut // Process the incomplete tail block let tail_data = blocks.remainder(); - finalize::(p1600, &mut state, tail_data, cv_dst); -} - -pub(crate) fn finalize( - p1600: Fn1600, - state: &mut State1600, - tail_data: &[u8], - cv_dst: &mut [u8], -) { - process_tail_data::(state, tail_data); - p1600(state); - copy_cv(state, cv_dst); + process_tail_data::(&mut state, tail_data); + p1600(&mut state); + copy_cv(&state, cv_dst); } -fn process_tail_data(state: &mut State1600, tail_data: &[u8]) { - let block_size = Rate::USIZE; - - debug_assert_eq!( - tail_data.len(), - CHUNK_SIZE % block_size, - "tail_data has unexpected length", - ); +fn process_tail_data(state: &mut State1600, tail_data: &[u8]) { + debug_assert_eq!(tail_data.len(), CHUNK_SIZE % Rate::USIZE); debug_assert_eq!(tail_data.len() % size_of::(), 0); xor_block(state, tail_data); @@ -124,9 +106,9 @@ fn process_tail_data(state: &mut State1600, tail_data: &[u8]) // Apply padding by XORing the state. // Note that we use little endian byte order. let pos = tail_data.len() / size_of::(); - let pad_pos = block_size / size_of::() - 1; + let pad_pos = Rate::USIZE / size_of::() - 1; state[pos] ^= u64::from(INTERMEDIATE_NODE_DS); - state[pad_pos] ^= u64::from(PAD) << 56; + state[pad_pos] ^= PAD; } /// Tests vectors are generated by the `turbo-shake` crate diff --git a/k12/src/reader.rs b/k12/src/reader.rs index 229b2e44..bed3b41a 100644 --- a/k12/src/reader.rs +++ b/k12/src/reader.rs @@ -1,43 +1,37 @@ use crate::consts::ROUNDS; use core::fmt; -use digest::{ - XofReader, - block_buffer::{BlockSizes, ReadBuffer}, -}; +use digest::{XofReader, array::ArraySize}; use keccak::{Keccak, State1600}; +use sponge_cursor::SpongeCursor; /// KangarooTwelve XOF reader generic over rate. #[derive(Clone)] -pub struct KtReader { - pub(crate) state: State1600, - pub(crate) buffer: ReadBuffer, - pub(crate) keccak: Keccak, +pub struct KtReader { + state: State1600, + cursor: SpongeCursor, + keccak: Keccak, } -impl XofReader for KtReader { - #[inline] - fn read(&mut self, buf: &mut [u8]) { - let Self { - state, - buffer, +impl KtReader { + pub(crate) fn new(state: &State1600, keccak: Keccak) -> Self { + Self { + state: *state, + cursor: Default::default(), keccak, - } = self; + } + } +} - buffer.read(buf, |block| { - let mut chunks = block.chunks_exact_mut(8); - for (src, dst) in state.iter().zip(&mut chunks) { - dst.copy_from_slice(&src.to_le_bytes()); - } - assert!( - chunks.into_remainder().is_empty(), - "rate is either 136 or 168", - ); - keccak.with_p1600::(|p1600| p1600(state)); +impl XofReader for KtReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) { + self.keccak.with_p1600::(|p1600| { + self.cursor.squeeze_u64_le(&mut self.state, p1600, buf); }); } } -impl fmt::Debug for KtReader { +impl fmt::Debug for KtReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let debug_str = match Rate::USIZE { 168 => "Kt128Reader { ... }", @@ -48,7 +42,7 @@ impl fmt::Debug for KtReader { } } -impl Drop for KtReader { +impl Drop for KtReader { fn drop(&mut self) { #[cfg(feature = "zeroize")] { @@ -60,4 +54,4 @@ impl Drop for KtReader { } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for KtReader {} +impl digest::zeroize::ZeroizeOnDrop for KtReader {} diff --git a/k12/src/turbo_shake.rs b/k12/src/turbo_shake.rs index c7314253..c5b57f7b 100644 --- a/k12/src/turbo_shake.rs +++ b/k12/src/turbo_shake.rs @@ -1,42 +1,35 @@ -use digest::{ - block_api::Eager, - block_buffer::{BlockBuffer, BlockSizes}, -}; +use crate::consts::{INTERMEDIATE_NODE_DS, PAD}; +use digest::array::ArraySize; use keccak::{Fn1600, State1600}; +use sponge_cursor::SpongeCursor; -use crate::{consts::PAD, utils::xor_block}; +use crate::utils::copy_cv; #[derive(Default, Clone)] -pub(crate) struct TurboShake { +pub(crate) struct TurboShake { state: State1600, - buffer: BlockBuffer, + cursor: SpongeCursor, } -impl TurboShake { +impl TurboShake { pub(crate) fn absorb(&mut self, p1600: Fn1600, data: &[u8]) { - let Self { state, buffer } = self; - buffer.digest_blocks(data, |blocks| { - for block in blocks { - xor_block(state, block); - p1600(state) - } - }) + self.cursor.absorb_u64_le(&mut self.state, p1600, data); } - pub(crate) fn finalize(&mut self, p1600: Fn1600) { - let Self { state, buffer } = self; - let pos = buffer.get_pos(); - let mut block = buffer.pad_with_zeros(); - block[pos] = DS; - let n = block.len(); - block[n - 1] |= PAD; - xor_block(state, &block); - p1600(state); + pub(crate) fn pad(&mut self) { + let pos = self.cursor.pos(); + let word_offset = pos / 8; + let byte_offset = pos % 8; + + let pad = u64::from(DS) << (8 * byte_offset); + self.state[word_offset] ^= pad; + self.state[Rate::USIZE / 8 - 1] ^= PAD; } - pub(crate) fn full_node_finalize(&mut self, p1600: Fn1600, cv_dst: &mut [u8]) { - let tail_data = self.buffer.get_data(); - crate::node_turbo_shake::finalize::(p1600, &mut self.state, tail_data, cv_dst); + pub(crate) fn finalize_intermediate_node(&mut self, p1600: Fn1600, cv_dst: &mut [u8]) { + self.pad::(); + p1600(&mut self.state); + copy_cv(self.state(), cv_dst); } pub(crate) fn state(&self) -> &State1600 { @@ -45,17 +38,17 @@ impl TurboShake { pub(crate) fn reset(&mut self) { self.state = Default::default(); - self.buffer.reset(); + self.cursor = Default::default(); } } -impl Drop for TurboShake { +impl Drop for TurboShake { fn drop(&mut self) { #[cfg(feature = "zeroize")] { use digest::zeroize::Zeroize; self.state.zeroize(); - // `buffer` is zeroized by `Drop` + self.cursor.zeroize(); } } } diff --git a/k12/src/update.rs b/k12/src/update.rs index 9031c9ee..56bf27ad 100644 --- a/k12/src/update.rs +++ b/k12/src/update.rs @@ -3,7 +3,7 @@ use crate::{ consts::{CHUNK_SIZE, CHUNK_SIZE_U64, ROUNDS, S0_DELIM}, node_turbo_shake, }; -use digest::{block_buffer::BlockSizes, typenum::Unsigned}; +use digest::{array::ArraySize, typenum::Unsigned}; use keccak::{Backend, BackendClosure}; /// Buffer size used by the update closure. @@ -11,12 +11,12 @@ use keccak::{Backend, BackendClosure}; /// 512 byte buffer is sufficient for 16x and 8x parallel KT128 and KT256 respectively. const BUFFER_LEN: usize = 512; -pub(crate) struct Closure<'a, Rate: BlockSizes> { +pub(crate) struct Closure<'a, Rate: ArraySize> { pub(crate) data: &'a [u8], pub(crate) kt: &'a mut Kt, } -impl BackendClosure for Closure<'_, Rate> { +impl BackendClosure for Closure<'_, Rate> { #[inline(always)] fn call_once(self) { let Kt { @@ -59,9 +59,8 @@ impl BackendClosure for Closure<'_, Rate> { // Handle partially absorbed chunk if partial_chunk_len != 0 { let rem_len = CHUNK_SIZE - partial_chunk_len; - let split = data.split_at_checked(rem_len); - let Some((part_data, rem_data)) = split else { + let Some((part_data, rem_data)) = data.split_at_checked(rem_len) else { node_tshk.absorb(p1600, data); return; }; @@ -69,10 +68,10 @@ impl BackendClosure for Closure<'_, Rate> { node_tshk.absorb(p1600, part_data); let cv_dst = &mut cv_buf[..cv_len]; - node_tshk.full_node_finalize(p1600, cv_dst); + node_tshk.finalize_intermediate_node(p1600, cv_dst); accum_tshk.absorb(p1600, cv_dst); - *node_tshk = Default::default(); + node_tshk.reset(); data = rem_data; } diff --git a/turboshake/Cargo.toml b/turboshake/Cargo.toml index 33c0a41d..7d914745 100644 --- a/turboshake/Cargo.toml +++ b/turboshake/Cargo.toml @@ -13,17 +13,18 @@ categories = ["cryptography", "no-std"] description = "Implementation of the TurboSHAKE family of extendable-output functions (XOFs)" [dependencies] -digest = "0.11" +digest = { version = "0.11", default-features = false } keccak = "0.2" +sponge-cursor = "0.1" [dev-dependencies] -digest = { version = "0.11", features = ["dev"] } +digest = { version = "0.11", default-features = false, features = ["dev"] } hex-literal = "1" [features] default = ["alloc"] alloc = ["digest/alloc"] -zeroize = ["digest/zeroize"] +zeroize = ["digest/zeroize", "sponge-cursor/zeroize"] [package.metadata.docs.rs] all-features = true diff --git a/turboshake/src/lib.rs b/turboshake/src/lib.rs index c5b93f19..593ef363 100644 --- a/turboshake/src/lib.rs +++ b/turboshake/src/lib.rs @@ -11,12 +11,14 @@ pub use digest; use keccak::{Keccak, State1600}; +use sponge_cursor::SpongeCursor; use core::fmt; use digest::{ - CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, Update, XofReader, - block_api::{AlgorithmName, BlockSizeUser, Reset}, - block_buffer::{BlockSizes, EagerBuffer, ReadBuffer}, + CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, + XofReader, + array::ArraySize, + common::{AlgorithmName, BlockSizeUser}, consts::{U16, U32, U136, U168}, }; @@ -33,13 +35,13 @@ pub const DEFAULT_DS: u8 = 0x1F; /// /// Rate MUST be either [`U168`] or [`U136`] for TurboSHAKE128 and TurboSHAKE256 respectively. #[derive(Clone)] -pub struct TurboShake { +pub struct TurboShake { state: State1600, + cursor: SpongeCursor, keccak: Keccak, - buffer: EagerBuffer, } -impl Default for TurboShake { +impl Default for TurboShake { #[inline] fn default() -> Self { const { @@ -48,96 +50,76 @@ impl Default for TurboShake { } Self { state: Default::default(), + cursor: Default::default(), keccak: Keccak::new(), - buffer: Default::default(), } } } -impl HashMarker for TurboShake {} +impl HashMarker for TurboShake {} -impl BlockSizeUser for TurboShake { +impl BlockSizeUser for TurboShake { type BlockSize = Rate; } -impl Update for TurboShake { +impl Update for TurboShake { #[inline] fn update(&mut self, data: &[u8]) { - let Self { - state, - keccak, - buffer, - } = self; - - keccak.with_p1600::(|p1600| { - buffer.digest_blocks(data, |blocks| { - for block in blocks { - xor_block(state, block); - p1600(state); - } - }); + self.keccak.with_p1600::(|p1600| { + self.cursor.absorb_u64_le(&mut self.state, p1600, data); }); } } -impl TurboShake { - fn finalize_dirty(&mut self) { - let Self { - state, - keccak, - buffer, - } = self; - - let pos = buffer.get_pos(); - let mut block = buffer.pad_with_zeros(); - block[pos] = DS; - let n = block.len(); - block[n - 1] |= 0x80; +impl TurboShake { + fn pad(&mut self) { + let pos = self.cursor.pos(); + let word_offset = pos / 8; + let byte_offset = pos % 8; - keccak.with_p1600::(|p1600| { - xor_block(state, &block); - p1600(state); - }); + let pad = u64::from(DS) << (8 * byte_offset); + self.state[word_offset] ^= pad; + self.state[Rate::USIZE / 8 - 1] ^= 1 << 63; } } -impl ExtendableOutput for TurboShake { +impl ExtendableOutput for TurboShake { type Reader = TurboShakeReader; #[inline] fn finalize_xof(mut self) -> Self::Reader { - self.finalize_dirty(); + self.pad(); Self::Reader { state: self.state, + cursor: Default::default(), keccak: self.keccak, - buffer: Default::default(), } } } -impl ExtendableOutputReset for TurboShake { +impl ExtendableOutputReset for TurboShake { #[inline] fn finalize_xof_reset(&mut self) -> Self::Reader { - self.finalize_dirty(); + self.pad(); let reader = Self::Reader { state: self.state, + cursor: Default::default(), keccak: self.keccak, - buffer: Default::default(), }; self.reset(); reader } } -impl Reset for TurboShake { +impl Reset for TurboShake { #[inline] fn reset(&mut self) { self.state = Default::default(); - self.buffer.reset(); + self.cursor = Default::default(); } } -impl AlgorithmName for TurboShake { +impl AlgorithmName for TurboShake { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { let alg_name = match Rate::USIZE { 168 => "TurboSHAKE128", @@ -148,7 +130,7 @@ impl AlgorithmName for TurboShake { } } -impl fmt::Debug for TurboShake { +impl fmt::Debug for TurboShake { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let debug_str = match Rate::USIZE { 168 => "TurboShake128 { ... }", @@ -159,7 +141,7 @@ impl fmt::Debug for TurboShake { } } -impl Drop for TurboShake { +impl Drop for TurboShake { fn drop(&mut self) { #[cfg(feature = "zeroize")] { @@ -171,40 +153,26 @@ impl Drop for TurboShake { } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for TurboShake {} +impl digest::zeroize::ZeroizeOnDrop for TurboShake {} /// Generic TurboSHAKE XOF reader #[derive(Clone)] -pub struct TurboShakeReader { +pub struct TurboShakeReader { state: State1600, + cursor: SpongeCursor, keccak: Keccak, - buffer: ReadBuffer, } -impl XofReader for TurboShakeReader { +impl XofReader for TurboShakeReader { #[inline] fn read(&mut self, buf: &mut [u8]) { - let Self { - state, - keccak, - buffer, - } = self; - - buffer.read(buf, |block| { - let mut chunks = block.chunks_exact_mut(8); - for (src, dst) in state.iter().zip(&mut chunks) { - dst.copy_from_slice(&src.to_le_bytes()); - } - assert!( - chunks.into_remainder().is_empty(), - "rate is either 136 or 168", - ); - keccak.with_p1600::(|p1600| p1600(state)); + self.keccak.with_p1600::(|p1600| { + self.cursor.squeeze_u64_le(&mut self.state, p1600, buf); }); } } -impl fmt::Debug for TurboShakeReader { +impl fmt::Debug for TurboShakeReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let debug_str = match Rate::USIZE { 168 => "TurboShakeReader128 { ... }", @@ -215,19 +183,19 @@ impl fmt::Debug for TurboShakeReader { } } -impl Drop for TurboShakeReader { +impl Drop for TurboShakeReader { fn drop(&mut self) { #[cfg(feature = "zeroize")] { use digest::zeroize::Zeroize; self.state.zeroize(); - // self.buffer is zeroized by its `Drop` + self.cursor.zeroize(); } } } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for TurboShakeReader {} +impl digest::zeroize::ZeroizeOnDrop for TurboShakeReader {} /// TurboSHAKE128 hasher with domain separator. pub type TurboShake128 = TurboShake; @@ -248,15 +216,3 @@ impl CollisionResistance for TurboShake256 { // https://www.ietf.org/archive/id/draft-irtf-cfrg-kangarootwelve-17.html#section-7-8 type CollisionResistance = U32; } - -fn xor_block(state: &mut State1600, block: &[u8]) { - assert!(size_of_val(block) < size_of_val(state)); - - let mut chunks = block.chunks_exact(8); - for (s, chunk) in state.iter_mut().zip(&mut chunks) { - *s ^= u64::from_le_bytes(chunk.try_into().unwrap()); - } - - let rem = chunks.remainder(); - assert!(rem.is_empty(), "block size is equal to 136 or 168"); -}