From 15208b278cdb77923353722de3f5e89aef48f6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 20 Apr 2026 03:27:31 +0300 Subject: [PATCH 1/2] k12: new implementation with parallel processing support --- Cargo.lock | 7 +- Cargo.toml | 1 - k12/Cargo.toml | 6 +- k12/README.md | 2 +- k12/benches/kt128.rs | 14 ++ k12/benches/kt256.rs | 14 ++ k12/benches/mod.rs | 23 --- k12/src/block_api.rs | 274 --------------------------- k12/src/consts.rs | 14 ++ k12/src/custom.rs | 7 + k12/src/custom/borrow.rs | 116 ++++++++++++ k12/src/custom/owned.rs | 129 +++++++++++++ k12/src/lib.rs | 262 ++++++++++++++----------- k12/src/node_turbo_shake.rs | 270 ++++++++++++++++++++++++++ k12/src/reader.rs | 63 ++++++ k12/src/turbo_shake.rs | 61 ++++++ k12/src/update.rs | 107 +++++++++++ k12/src/utils.rs | 53 ++++++ k12/tests/data/kt128_cvs.bin | Bin 0 -> 1024 bytes k12/tests/data/kt256_cvs.bin | Bin 0 -> 2048 bytes k12/tests/mod.rs | 69 ++++--- turbo-shake/tests/data/kt128_cvs.bin | Bin 0 -> 1024 bytes turbo-shake/tests/data/kt256_cvs.bin | Bin 0 -> 2048 bytes turbo-shake/tests/k12.rs | 58 ++++++ 24 files changed, 1105 insertions(+), 445 deletions(-) create mode 100644 k12/benches/kt128.rs create mode 100644 k12/benches/kt256.rs delete mode 100644 k12/benches/mod.rs delete mode 100644 k12/src/block_api.rs create mode 100644 k12/src/consts.rs create mode 100644 k12/src/custom.rs create mode 100644 k12/src/custom/borrow.rs create mode 100644 k12/src/custom/owned.rs create mode 100644 k12/src/node_turbo_shake.rs create mode 100644 k12/src/reader.rs create mode 100644 k12/src/turbo_shake.rs create mode 100644 k12/src/update.rs create mode 100644 k12/src/utils.rs create mode 100644 k12/tests/data/kt128_cvs.bin create mode 100644 k12/tests/data/kt256_cvs.bin create mode 100644 turbo-shake/tests/data/kt128_cvs.bin create mode 100644 turbo-shake/tests/data/kt256_cvs.bin create mode 100644 turbo-shake/tests/k12.rs diff --git a/Cargo.lock b/Cargo.lock index 5979524c6..2386912cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ version = "0.4.0-rc.1" dependencies = [ "digest", "hex-literal", - "turbo-shake", + "keccak", ] [[package]] @@ -216,6 +216,7 @@ checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ "cfg-if", "cpufeatures", + "hybrid-array", ] [[package]] @@ -409,9 +410,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" diff --git a/Cargo.toml b/Cargo.toml index 355de2b6a..17e981403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,5 +34,4 @@ opt-level = 2 [patch.crates-io] sha1 = { path = "sha1" } -turbo-shake = { path = "turbo-shake" } whirlpool = { path = "whirlpool" } diff --git a/k12/Cargo.toml b/k12/Cargo.toml index a20b9ed8f..551bde636 100644 --- a/k12/Cargo.toml +++ b/k12/Cargo.toml @@ -10,11 +10,11 @@ repository = "https://github.com/RustCrypto/hashes" license = "Apache-2.0 OR MIT" keywords = ["hash", "digest"] categories = ["cryptography", "no-std"] -description = "Pure Rust implementation of the KangarooTwelve hash function" +description = "Implementation of the KangarooTwelve family of extendable-output functions" [dependencies] digest = "0.11" -turbo-shake = { version = "0.1.0", default-features = false } +keccak = { version = "0.2", features = ["parallel"] } [dev-dependencies] digest = { version = "0.11", features = ["alloc", "dev"] } @@ -23,7 +23,7 @@ hex-literal = "1" [features] default = ["alloc"] alloc = ["digest/alloc"] -zeroize = ["digest/zeroize", "turbo-shake/zeroize"] +zeroize = ["digest/zeroize"] [package.metadata.docs.rs] all-features = true diff --git a/k12/README.md b/k12/README.md index b04e0f41c..08946fbb5 100644 --- a/k12/README.md +++ b/k12/README.md @@ -6,7 +6,7 @@ ![Rust Version][rustc-image] [![Build Status][build-image]][build-link] -Pure Rust implementation of the [KangarooTwelve] eXtendable-Output Function (XOF). +Pure Rust implementation of the [KangarooTwelve] family of extendable-output functions (XOF). ## License diff --git a/k12/benches/kt128.rs b/k12/benches/kt128.rs new file mode 100644 index 000000000..a8bc1e41c --- /dev/null +++ b/k12/benches/kt128.rs @@ -0,0 +1,14 @@ +#![feature(test)] +extern crate test; + +use test::Bencher; + +digest::bench_update!( + k12::Kt128::default(); + kt128_1_10 10; + kt128_2_100 100; + kt128_3_1k 1000; + kt128_4_10k 10_000; + kt128_5_100k 100_000; + kt128_6_1m 1_000_000; +); diff --git a/k12/benches/kt256.rs b/k12/benches/kt256.rs new file mode 100644 index 000000000..6b3ae5f9e --- /dev/null +++ b/k12/benches/kt256.rs @@ -0,0 +1,14 @@ +#![feature(test)] +extern crate test; + +use test::Bencher; + +digest::bench_update!( + k12::Kt256::default(); + kt256_1_10 10; + kt256_2_100 100; + kt256_3_1k 1000; + kt256_4_10k 10_000; + kt256_5_100k 100_000; + kt256_6_1m 1_000_000; +); diff --git a/k12/benches/mod.rs b/k12/benches/mod.rs deleted file mode 100644 index 6c8dfac43..000000000 --- a/k12/benches/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![feature(test)] -extern crate test; - -use digest::bench_update; -use test::Bencher; - -bench_update!( - k12::Kt128::default(); - kt128_10 10; - kt128_100 100; - // the bigger sizes result in OOM - // kt128_1000 1000; - // kt128_10000 10000; -); - -bench_update!( - k12::Kt256::default(); - kt256_10 10; - kt256_100 100; - // the bigger sizes result in OOM - // kt256_1000 1000; - // kt256_10000 10000; -); diff --git a/k12/src/block_api.rs b/k12/src/block_api.rs deleted file mode 100644 index f211eaba7..000000000 --- a/k12/src/block_api.rs +++ /dev/null @@ -1,274 +0,0 @@ -use core::fmt; -use digest::{ - ExtendableOutputReset, HashMarker, Reset, Update, XofReader, - block_api::{ - AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, ExtendableOutputCore, - UpdateCore, XofReaderCore, - }, - consts::{U128, U136, U168}, -}; -use turbo_shake::{TurboShake128, TurboShake128Reader, TurboShake256, TurboShake256Reader}; - -const CHUNK_SIZE: usize = 8192; -const LENGTH_ENCODE_SIZE: usize = 255; - -macro_rules! impl_k12_core { - ( - $name:ident, $reader_name:ident, $ts_name:ident, $ts_reader_name:ident, $cv_size:literal, - $alg_name:literal, - ) => { - #[doc = "Core"] - #[doc = $alg_name] - #[doc = "hasher state."] - #[derive(Clone)] - #[allow(non_camel_case_types)] - pub struct $name<'cs> { - customization: &'cs [u8], - buffer: [u8; CHUNK_SIZE], - bufpos: usize, - final_tshk: $ts_name<0x06>, - chain_tshk: $ts_name<0x0B>, - chain_length: usize, - } - - impl<'cs> $name<'cs> { - const CHAINING_VALUE_SIZE: usize = $cv_size; - - #[doc = "Creates a new"] - #[doc = $alg_name] - #[doc = "instance with the given customization."] - pub fn new(customization: &'cs [u8]) -> Self { - Self { - customization, - buffer: [0u8; CHUNK_SIZE], - bufpos: 0usize, - final_tshk: Default::default(), - chain_tshk: Default::default(), - chain_length: 0usize, - } - } - - fn process_chunk(&mut self) { - debug_assert!(self.bufpos == CHUNK_SIZE); - if self.chain_length == 0 { - self.final_tshk.update(&self.buffer); - } else { - self.process_chaining_chunk(); - } - - self.chain_length += 1; - self.buffer = [0u8; CHUNK_SIZE]; - self.bufpos = 0; - } - - fn process_chaining_chunk(&mut self) { - debug_assert!(self.bufpos != 0); - if self.chain_length == 1 { - self.final_tshk - .update(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); - } - - let mut result = [0u8; Self::CHAINING_VALUE_SIZE]; - self.chain_tshk.update(&self.buffer[..self.bufpos]); - self.chain_tshk.finalize_xof_reset_into(&mut result); - self.final_tshk.update(&result); - } - } - - impl HashMarker for $name<'_> {} - - impl BlockSizeUser for $name<'_> { - type BlockSize = U128; - } - - impl BufferKindUser for $name<'_> { - type BufferKind = Eager; - } - - impl UpdateCore for $name<'_> { - #[inline] - fn update_blocks(&mut self, blocks: &[Block]) { - for block in blocks { - if self.bufpos == CHUNK_SIZE { - self.process_chunk(); - } - - self.buffer[self.bufpos..self.bufpos + 128].clone_from_slice(block); - self.bufpos += 128; - } - } - } - - impl ExtendableOutputCore for $name<'_> { - type ReaderCore = $reader_name; - - #[inline] - fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore { - let mut lenbuf = [0u8; LENGTH_ENCODE_SIZE]; - - // Digest customization - buffer.digest_blocks(self.customization, |block| self.update_blocks(block)); - buffer.digest_blocks( - length_encode(self.customization.len(), &mut lenbuf), - |block| self.update_blocks(block), - ); - - if self.bufpos == CHUNK_SIZE && buffer.get_pos() != 0 { - self.process_chunk(); - } - - // Read leftover data from buffer - self.buffer[self.bufpos..(self.bufpos + buffer.get_pos())] - .copy_from_slice(buffer.get_data()); - self.bufpos += buffer.get_pos(); - - // Calculate final node - if self.chain_length == 0 { - // Input did not exceed a single chaining value - let tshk = $ts_name::<0x07>::default() - .chain(&self.buffer[..self.bufpos]) - .finalize_xof_reset(); - return $reader_name { tshk }; - } - - // Calculate last chaining value - self.process_chaining_chunk(); - - // Pad final node calculation - self.final_tshk - .update(length_encode(self.chain_length, &mut lenbuf)); - self.final_tshk.update(&[0xff, 0xff]); - - $reader_name { - tshk: self.final_tshk.finalize_xof_reset(), - } - } - } - - impl Default for $name<'_> { - #[inline] - fn default() -> Self { - Self { - customization: &[], - buffer: [0u8; CHUNK_SIZE], - bufpos: 0usize, - final_tshk: Default::default(), - chain_tshk: Default::default(), - chain_length: 0usize, - } - } - } - - impl Reset for $name<'_> { - #[inline] - fn reset(&mut self) { - *self = Self::new(self.customization); - } - } - - impl AlgorithmName for $name<'_> { - fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str($alg_name) - } - } - - impl fmt::Debug for $name<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(concat!(stringify!($name), " { ... }")) - } - } - - impl Drop for $name<'_> { - fn drop(&mut self) { - #[cfg(feature = "zeroize")] - { - use digest::zeroize::Zeroize; - self.buffer.zeroize(); - self.bufpos.zeroize(); - self.chain_length.zeroize(); - // final_tshk and chain_tshk zeroized by their Drop impl - } - } - } - - #[cfg(feature = "zeroize")] - impl digest::zeroize::ZeroizeOnDrop for $name<'_> {} - - #[doc = "Core"] - #[doc = $alg_name] - #[doc = "reader state."] - #[derive(Clone)] - pub struct $reader_name { - tshk: $ts_reader_name, - } - - impl XofReaderCore for $reader_name { - #[inline] - fn read_block(&mut self) -> Block { - let mut block = Block::::default(); - self.tshk.read(&mut block); - block - } - } - - // `Sha3ReaderCore` and the wrapper are zeroized by their Drop impls - #[cfg(feature = "zeroize")] - impl digest::zeroize::ZeroizeOnDrop for $reader_name {} - }; -} - -impl_k12_core!( - Kt128Core, - Kt128ReaderCore, - TurboShake128, - TurboShake128Reader, - 32, - "KT128", -); -impl_k12_core!( - Kt256Core, - Kt256ReaderCore, - TurboShake256, - TurboShake256Reader, - 64, - "KT256", -); - -impl BlockSizeUser for Kt128ReaderCore { - type BlockSize = U168; // TurboSHAKE128 block size -} - -impl BlockSizeUser for Kt256ReaderCore { - type BlockSize = U136; // TurboSHAKE256 block size -} - -/// Core KT128 hasher state. -#[deprecated(since = "0.4.0-pre", note = "use `Kt128Core` instead")] -pub type KangarooTwelveCore<'cs> = Kt128Core<'cs>; - -/// Core KT128 reader state. -#[deprecated(since = "0.4.0-pre", note = "use `Kt128ReaderCore` instead")] -pub type KangarooTwelveReaderCore = Kt128ReaderCore; - -fn length_encode(mut length: usize, buffer: &mut [u8; LENGTH_ENCODE_SIZE]) -> &mut [u8] { - let mut bufpos = 0usize; - while length > 0 { - buffer[bufpos] = (length % 256) as u8; - length /= 256; - bufpos += 1; - } - buffer[..bufpos].reverse(); - - buffer[bufpos] = bufpos as u8; - bufpos += 1; - - &mut buffer[..bufpos] -} - -#[test] -fn test_length_encode() { - let mut buffer = [0u8; LENGTH_ENCODE_SIZE]; - assert_eq!(length_encode(0, &mut buffer), &[0x00]); - assert_eq!(length_encode(12, &mut buffer), &[0x0C, 0x01]); - assert_eq!(length_encode(65538, &mut buffer), &[0x01, 0x00, 0x02, 0x03]); -} diff --git a/k12/src/consts.rs b/k12/src/consts.rs new file mode 100644 index 000000000..21be28da7 --- /dev/null +++ b/k12/src/consts.rs @@ -0,0 +1,14 @@ +/// Number of permutation rounds used by k12 +pub(crate) const ROUNDS: usize = 12; +/// Chunk size used by k12 +pub(crate) const CHUNK_SIZE: usize = 1 << 13; +pub(crate) const CHUNK_SIZE_U64: u64 = CHUNK_SIZE as u64; + +pub(crate) const SINGLE_NODE_DS: u8 = 0x07; +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; diff --git a/k12/src/custom.rs b/k12/src/custom.rs new file mode 100644 index 000000000..ceab54fdd --- /dev/null +++ b/k12/src/custom.rs @@ -0,0 +1,7 @@ +mod borrow; +#[cfg(feature = "alloc")] +mod owned; + +pub use borrow::{CustomRefKt, CustomRefKt128, CustomRefKt256}; +#[cfg(feature = "alloc")] +pub use owned::{CustomKt, CustomKt128, CustomKt256}; diff --git a/k12/src/custom/borrow.rs b/k12/src/custom/borrow.rs new file mode 100644 index 000000000..6efa1d987 --- /dev/null +++ b/k12/src/custom/borrow.rs @@ -0,0 +1,116 @@ +use core::fmt; +use digest::{ + CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, + block_buffer::BlockSizes, + common::{AlgorithmName, BlockSizeUser}, + consts::{U16, U32, U136, U168}, +}; + +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> { + customization: &'a [u8], + inner: Kt, +} + +impl<'a, Rate: BlockSizes> CustomRefKt<'a, Rate> { + /// Create new customized KangarooTwelve hasher with borrrowed customization string. + /// + /// Note that this is an inherent method and `CustomRefKt` does not implement + /// the [`CustomizedInit`][digest::CustomizedInit] trait. + #[inline] + pub fn new_customized(customization: &'a [u8]) -> Self { + Self { + customization, + inner: Default::default(), + } + } +} + +impl Default for CustomRefKt<'static, Rate> { + #[inline] + fn default() -> Self { + Self::new_customized(&[]) + } +} + +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> { + #[inline] + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + Kt::::write_alg_name(f) + } +} + +impl HashMarker for CustomRefKt<'_, Rate> {} + +impl BlockSizeUser for CustomRefKt<'_, Rate> { + type BlockSize = Rate; +} + +impl Update for CustomRefKt<'_, Rate> { + #[inline] + fn update(&mut self, data: &[u8]) { + self.inner.update(data); + } +} + +impl Reset for CustomRefKt<'_, Rate> { + #[inline] + fn reset(&mut self) { + self.inner.reset(); + } +} + +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`"); + length_encode(len, |enc_len| self.inner.update(enc_len)); + } +} + +impl ExtendableOutput for CustomRefKt<'_, Rate> { + type Reader = KtReader; + + #[inline] + fn finalize_xof(mut self) -> Self::Reader { + self.absorb_customization(); + self.inner.raw_finalize() + } +} + +impl ExtendableOutputReset for CustomRefKt<'_, Rate> { + #[inline] + fn finalize_xof_reset(&mut self) -> Self::Reader { + self.absorb_customization(); + let reader = self.inner.raw_finalize(); + self.inner.reset(); + reader + } +} + +// `inner` is zeroized by `Drop` and `customization` can not be zeroized +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for CustomRefKt<'_, Rate> {} + +/// Customized KT128 hasher with borrowed customization string. +pub type CustomRefKt128<'a> = CustomRefKt<'a, U168>; +/// Customized KT256 hasher with borrowed customization string. +pub type CustomRefKt256<'a> = CustomRefKt<'a, U136>; + +impl CollisionResistance for CustomRefKt128<'_> { + type CollisionResistance = U16; +} + +impl CollisionResistance for CustomRefKt256<'_> { + type CollisionResistance = U32; +} diff --git a/k12/src/custom/owned.rs b/k12/src/custom/owned.rs new file mode 100644 index 000000000..1c109ae5b --- /dev/null +++ b/k12/src/custom/owned.rs @@ -0,0 +1,129 @@ +extern crate alloc; + +use alloc::vec::Vec; +use core::fmt; +use digest::{ + CollisionResistance, CustomizedInit, ExtendableOutput, ExtendableOutputReset, HashMarker, + Reset, Update, + block_buffer::BlockSizes, + common::{AlgorithmName, BlockSizeUser}, + consts::{U16, U32, U136, U168}, +}; + +use crate::{Kt, KtReader, utils::length_encode}; + +/// Customized KangarooTwelve hasher generic over rate with owned customization string. +#[derive(Clone)] +pub struct CustomKt { + customization: Vec, + inner: Kt, +} + +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`"); + let mut buf = Vec::new(); + length_encode(len, |enc_len| { + buf = Vec::with_capacity(customization.len() + enc_len.len()); + buf.extend_from_slice(customization); + buf.extend_from_slice(enc_len); + }); + + Self { + customization: buf, + inner: Default::default(), + } + } +} + +impl Default for CustomKt { + #[inline] + fn default() -> Self { + Self::new_customized(&[]) + } +} + +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 { + #[inline] + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + Kt::::write_alg_name(f) + } +} + +impl HashMarker for CustomKt {} + +impl BlockSizeUser for CustomKt { + type BlockSize = Rate; +} + +impl Update for CustomKt { + #[inline] + fn update(&mut self, data: &[u8]) { + self.inner.update(data); + } +} + +impl Reset for CustomKt { + #[inline] + fn reset(&mut self) { + self.inner.reset(); + } +} + +impl ExtendableOutput for CustomKt { + type Reader = KtReader; + + #[inline] + fn finalize_xof(mut self) -> Self::Reader { + self.inner.update(&self.customization); + self.inner.raw_finalize() + } +} + +impl ExtendableOutputReset for CustomKt { + #[inline] + fn finalize_xof_reset(&mut self) -> Self::Reader { + self.inner.update(&self.customization); + let reader = self.inner.raw_finalize(); + self.inner.reset(); + reader + } +} + +impl Drop for CustomKt { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use digest::zeroize::Zeroize; + self.customization.zeroize(); + // `inner` is zeroized by `Drop` + } + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for CustomKt {} + +/// Customized KT128 hasher with owned customization string. +pub type CustomKt128 = CustomKt; +/// Customized KT256 hasher with owned customization string. +pub type CustomKt256 = CustomKt; + +impl CollisionResistance for CustomKt128 { + // https://www.rfc-editor.org/rfc/rfc9861.html#section-7-7 + type CollisionResistance = U16; +} + +impl CollisionResistance for CustomKt256 { + // https://www.rfc-editor.org/rfc/rfc9861.html#section-7-8 + type CollisionResistance = U32; +} diff --git a/k12/src/lib.rs b/k12/src/lib.rs index 9b3f8bac1..8c86b31a0 100644 --- a/k12/src/lib.rs +++ b/k12/src/lib.rs @@ -10,146 +10,182 @@ pub use digest; -/// Block-level types -pub mod block_api; - use core::fmt; use digest::{ - CollisionResistance, ExtendableOutput, HashMarker, Reset, Update, XofReader, - block_api::{AlgorithmName, BlockSizeUser, ExtendableOutputCore, UpdateCore, XofReaderCore}, - block_buffer::{BlockBuffer, Eager, ReadBuffer}, - consts::{U16, U32, U128, U136, U168}, + CollisionResistance, ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, + block_api::{AlgorithmName, BlockSizeUser}, + block_buffer::BlockSizes, + consts::{U16, U32, U136, U168}, }; -macro_rules! impl_k12 { - ( - $name:ident, $reader_name:ident, $core_name:ident, $reader_core_name:ident, $rate:ty, - $alg_name:literal, - ) => { - #[doc = $alg_name] - #[doc = "hasher."] - #[derive(Default, Clone)] - pub struct $name<'cs> { - core: block_api::$core_name<'cs>, - buffer: BlockBuffer, - } - - impl<'cs> $name<'cs> { - #[doc = "Creates a new"] - #[doc = $alg_name] - #[doc = "instance with the given customization."] - pub fn new(customization: &'cs [u8]) -> Self { - Self { - core: block_api::$core_name::new(customization), - buffer: Default::default(), - } - } - } +mod consts; +/// Customized variants. +pub mod custom; +/// Implementation of TurboSHAKE specialized for computation of chaining values on full nodes +mod node_turbo_shake; +/// Implementation of the XOF reader +mod reader; +/// Vendored implementation of TurboSHAKE +mod turbo_shake; +/// Implementation of the update closure generic over Keccak backend +mod update; +/// Utility functions +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 turbo_shake::TurboShake; +use utils::{copy_cv, 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 { + accum_tshk: TurboShake, + node_tshk: TurboShake, + consumed_len: u64, + keccak: keccak::Keccak, +} - impl fmt::Debug for $name<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(concat!(stringify!($name), " { .. }")) - } +impl Default for Kt { + #[inline] + fn default() -> Self { + const { assert!(matches!(Rate::USIZE, 136 | 168)) } + Self { + accum_tshk: Default::default(), + node_tshk: Default::default(), + consumed_len: 0, + keccak: Default::default(), } + } +} - impl AlgorithmName for $name<'_> { - fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str($alg_name) - } - } +impl fmt::Debug for Kt { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "Kt{} {{ ... }}", 4 * (200 - Rate::USIZE)) + } +} - impl HashMarker for $name<'_> {} +impl AlgorithmName for Kt { + #[inline] + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "KT{}", 4 * (200 - Rate::USIZE)) + } +} - impl BlockSizeUser for $name<'_> { - type BlockSize = U128; - } +impl HashMarker for Kt {} - impl Update for $name<'_> { - fn update(&mut self, data: &[u8]) { - let Self { core, buffer } = self; - buffer.digest_blocks(data, |blocks| core.update_blocks(blocks)); - } - } +impl BlockSizeUser for Kt { + type BlockSize = Rate; +} - impl Reset for $name<'_> { - fn reset(&mut self) { - self.core.reset(); - self.buffer.reset(); - } - } +impl Update for Kt { + #[inline] + fn update(&mut self, data: &[u8]) { + let keccak = self.keccak; + let closure = update::Closure::<'_, Rate> { data, kt: self }; + keccak.with_backend(closure); + } +} - impl ExtendableOutput for $name<'_> { - type Reader = $reader_name; +impl Reset for Kt { + #[inline] + fn reset(&mut self) { + self.accum_tshk.reset(); + self.node_tshk.reset(); + self.consumed_len = 0; + } +} - #[inline] - fn finalize_xof(mut self) -> Self::Reader { - Self::Reader { - core: self.core.finalize_xof_core(&mut self.buffer), - buffer: Default::default(), +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 { + 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.accum_tshk.absorb(p1600, cv_dst); } - } - } - #[cfg(feature = "zeroize")] - impl digest::zeroize::ZeroizeOnDrop for $name<'_> {} + 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); + }; + }); - #[doc = $alg_name] - #[doc = "XOF reader."] - pub struct $reader_name { - core: block_api::$reader_core_name, - buffer: ReadBuffer<$rate>, + KtReader { + state: *self.accum_tshk.state(), + buffer: Default::default(), + keccak, } + } +} - impl fmt::Debug for $reader_name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(concat!(stringify!($reader_name), " { .. }")) - } - } +impl ExtendableOutput for Kt { + type Reader = KtReader; - impl XofReader for $reader_name { - #[inline] - fn read(&mut self, buffer: &mut [u8]) { - let Self { core, buffer: buf } = self; - buf.read(buffer, |block| *block = core.read_block()); - } - } + #[inline] + fn finalize_xof(mut self) -> Self::Reader { + self.update(&[0x00]); + self.raw_finalize() + } +} +impl ExtendableOutputReset for Kt { + #[inline] + fn finalize_xof_reset(&mut self) -> Self::Reader { + self.update(&[0x00]); + let reader = self.raw_finalize(); + self.reset(); + reader + } +} + +impl Drop for Kt { + fn drop(&mut self) { #[cfg(feature = "zeroize")] - impl digest::zeroize::ZeroizeOnDrop for $reader_name {} - }; + { + use digest::zeroize::Zeroize; + self.consumed_len.zeroize(); + // `accum_tshk` and `node_tshk` are zeroized by `Drop` + } + } } -impl_k12!( - Kt128, - Kt128Reader, - Kt128Core, - Kt128ReaderCore, - U168, - "KT128", -); -impl_k12!( - Kt256, - Kt256Reader, - Kt256Core, - Kt256ReaderCore, - U136, - "KT256", -); - -impl CollisionResistance for Kt128<'_> { +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for Kt {} + +/// KT128 hasher. +pub type Kt128 = Kt; +/// KT256 hasher. +pub type Kt256 = Kt; + +/// KT128 XOF reader. +pub type Kt128Reader = KtReader; +/// KT256 XOF reader. +pub type Kt256Reader = KtReader; + +impl CollisionResistance for Kt128 { // https://www.rfc-editor.org/rfc/rfc9861.html#section-7-7 type CollisionResistance = U16; } -impl CollisionResistance for Kt256<'_> { +impl CollisionResistance for Kt256 { // https://www.rfc-editor.org/rfc/rfc9861.html#section-7-8 type CollisionResistance = U32; } - -/// KT128 hasher. -#[deprecated(since = "0.4.0-pre", note = "use `Kt128` instead")] -pub type KangarooTwelve<'cs> = Kt128<'cs>; - -/// KT128 XOF reader. -#[deprecated(since = "0.4.0-pre", note = "use `Kt128Reader` instead")] -pub type KangarooTwelveReader = Kt128Reader; diff --git a/k12/src/node_turbo_shake.rs b/k12/src/node_turbo_shake.rs new file mode 100644 index 000000000..c01a8bc6d --- /dev/null +++ b/k12/src/node_turbo_shake.rs @@ -0,0 +1,270 @@ +use crate::{ + consts::{CHUNK_SIZE, INTERMEDIATE_NODE_DS, PAD}, + utils::{copy_cv, xor_block}, +}; +use digest::{ + array::{Array, ArraySize}, + block_buffer::BlockSizes, +}; +use keccak::{Fn1600, State1600}; + +/// Parallel version of TurboSHAKE specialized for computation of chaining values. +/// +/// Ideally, this function should have the following signature: +/// ```rust,ignore +/// fn par_turbo_shake( +/// p1600: ParFn1600, +/// data: &[u8; CHUNK_SIZE * B::PAR_SIZE_1600], +/// ) -> [[u8; {200 - RATE}]; 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( + par_p1600: fn(&mut Array), + data: &[u8], + par_cv_dst: &mut [u8], +) { + let par_size = ParSize::USIZE; + assert_eq!(data.len(), CHUNK_SIZE * par_size); + let cv_size = 200 - Rate::USIZE; + assert_eq!(par_cv_dst.len(), cv_size * par_size); + + let mut par_state: Array = Default::default(); + + let block_size = Rate::USIZE; + let full_blocks = CHUNK_SIZE / block_size; + + // Process full blocks + for block_idx in 0..full_blocks { + for (state_idx, state) in par_state.iter_mut().enumerate() { + let chunk_offset = state_idx * CHUNK_SIZE; + let block_offset = chunk_offset + block_idx * block_size; + + let block = &data[block_offset..][..block_size]; + xor_block(state, block); + } + par_p1600(&mut par_state); + } + + // Process tail blocks + let tail_block_size = CHUNK_SIZE - full_blocks * block_size; + assert_ne!(tail_block_size, 0); + for (state_idx, state) in par_state.iter_mut().enumerate() { + let chunk_offset = state_idx * CHUNK_SIZE; + let block_offset = chunk_offset + full_blocks * block_size; + + let tail_data = &data[block_offset..][..tail_block_size]; + process_tail_data::(state, tail_data); + } + par_p1600(&mut par_state); + + // Copy the resulting chain values + let mut cvs = par_cv_dst.chunks_exact_mut(cv_size); + for (state, cv_dst) in par_state.iter_mut().zip(&mut cvs) { + copy_cv(state, cv_dst); + } + assert!(cvs.into_remainder().is_empty()); +} + +/// Scalar version of TurboSHAKE specialized for computation of chaining values. +/// +/// Ideally, this function should have the following signature: +/// ```rust,ignore +/// fn turbo_shake-cv( +/// p1600: Fn1600, +/// data: &[u8; CHUNK_SIZE], +/// ) -> [u8; {200 - RATE}] { ... } +/// ``` +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); + + let mut state = State1600::default(); + + let block_size = Rate::USIZE; + let mut blocks = data.chunks_exact(block_size); + + // Process full blocks + for block in &mut blocks { + xor_block(&mut state, block); + p1600(&mut state); + } + + // 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); +} + +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", + ); + debug_assert_eq!(tail_data.len() % size_of::(), 0); + + xor_block(state, tail_data); + + // 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; + state[pos] ^= u64::from(INTERMEDIATE_NODE_DS); + state[pad_pos] ^= u64::from(PAD) << 56; +} + +/// Tests vectors are generated by the `turbo-shake` crate +#[cfg(test)] +mod tests { + use super::{parallel, scalar}; + use crate::consts::{CHUNK_SIZE, ROUNDS}; + use digest::array::typenum::{U136, U168, Unsigned}; + use keccak::{Backend, BackendClosure}; + + const CHUNKS: usize = 32; + const KT128_CV_LEN: usize = 32; + const KT256_CV_LEN: usize = 64; + const KT128_CVS_LEN: usize = KT128_CV_LEN * CHUNKS; + const KT256_CVS_LEN: usize = KT256_CV_LEN * CHUNKS; + + const DATA: &[u8] = &{ + let mut buf = [0u8; CHUNKS * CHUNK_SIZE]; + let mut i = 0; + while i < CHUNKS { + let mut j = 0; + while j < CHUNK_SIZE { + buf[i * CHUNK_SIZE + j] = (i + j) as u8; + j += 1; + } + i += 1; + } + buf + }; + + const KT128_CVS: &[u8; KT128_CVS_LEN] = include_bytes!("../tests/data/kt128_cvs.bin"); + const KT256_CVS: &[u8; KT256_CVS_LEN] = include_bytes!("../tests/data/kt256_cvs.bin"); + + #[test] + fn kt128_cvs() { + keccak::Keccak::new().with_p1600::(|p1600| { + let mut cvs = [0u8; KT128_CVS_LEN]; + let mut data_chunks = DATA.chunks_exact(CHUNK_SIZE); + let mut cvs_chunks = cvs.chunks_exact_mut(KT128_CV_LEN); + + for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut cvs_chunks) { + scalar::(p1600, data_chunk, par_cv); + } + + assert!(data_chunks.remainder().is_empty()); + assert!(cvs_chunks.into_remainder().is_empty()); + assert_eq!(&cvs, KT128_CVS); + }); + } + + #[test] + fn kt256_cvs() { + keccak::Keccak::new().with_p1600::(|p1600| { + let mut cvs = [0u8; KT256_CVS_LEN]; + let mut data_chunks = DATA.chunks_exact(CHUNK_SIZE); + let mut cvs_chunks = cvs.chunks_exact_mut(KT256_CV_LEN); + + for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut cvs_chunks) { + scalar::(p1600, data_chunk, par_cv); + } + + assert!(data_chunks.remainder().is_empty()); + assert!(cvs_chunks.into_remainder().is_empty()); + assert_eq!(&cvs, KT256_CVS); + }); + } + + #[test] + fn kt128_par_cvs() { + struct Closure; + + impl BackendClosure for Closure { + fn call_once(self) { + let par_p1600 = B::get_par_p1600::(); + let p1600 = B::get_p1600::(); + + let mut cvs = [0u8; KT128_CVS_LEN]; + + let par_size = B::ParSize1600::USIZE; + let par_data_size = CHUNK_SIZE * par_size; + let par_cv_size = KT128_CV_LEN * par_size; + let mut data_chunks = DATA.chunks_exact(par_data_size); + let mut par_cvs = cvs.chunks_exact_mut(par_cv_size); + + for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut par_cvs) { + parallel::<_, U168>(par_p1600, data_chunk, par_cv); + } + + let mut data_chunks = data_chunks.remainder().chunks_exact(CHUNK_SIZE); + let mut cvs_chunks = par_cvs.into_remainder().chunks_exact_mut(KT256_CV_LEN); + + for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut cvs_chunks) { + scalar::(p1600, data_chunk, par_cv); + } + + assert!(data_chunks.remainder().is_empty()); + assert!(cvs_chunks.into_remainder().is_empty()); + assert_eq!(&cvs, KT128_CVS); + } + } + + keccak::Keccak::new().with_backend(Closure); + } + + #[test] + fn kt256_par_cvs() { + struct Closure; + + impl BackendClosure for Closure { + fn call_once(self) { + let par_p1600 = B::get_par_p1600::(); + let p1600 = B::get_p1600::(); + + let mut cvs = [0u8; KT256_CVS_LEN]; + + let par_size = B::ParSize1600::USIZE; + let par_data_size = CHUNK_SIZE * par_size; + let par_cv_size = KT256_CV_LEN * par_size; + let mut data_chunks = DATA.chunks_exact(par_data_size); + let mut par_cvs = cvs.chunks_exact_mut(par_cv_size); + + for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut par_cvs) { + parallel::<_, U136>(par_p1600, data_chunk, par_cv); + } + + let mut data_chunks = data_chunks.remainder().chunks_exact(CHUNK_SIZE); + let mut cvs_chunks = par_cvs.into_remainder().chunks_exact_mut(KT256_CV_LEN); + + for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut cvs_chunks) { + scalar::(p1600, data_chunk, par_cv); + } + + assert!(data_chunks.remainder().is_empty()); + assert!(cvs_chunks.into_remainder().is_empty()); + assert_eq!(&cvs, KT256_CVS); + } + } + + keccak::Keccak::new().with_backend(Closure); + } +} diff --git a/k12/src/reader.rs b/k12/src/reader.rs new file mode 100644 index 000000000..229b2e44f --- /dev/null +++ b/k12/src/reader.rs @@ -0,0 +1,63 @@ +use crate::consts::ROUNDS; +use core::fmt; +use digest::{ + XofReader, + block_buffer::{BlockSizes, ReadBuffer}, +}; +use keccak::{Keccak, State1600}; + +/// KangarooTwelve XOF reader generic over rate. +#[derive(Clone)] +pub struct KtReader { + pub(crate) state: State1600, + pub(crate) buffer: ReadBuffer, + pub(crate) keccak: Keccak, +} + +impl XofReader for KtReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) { + let Self { + state, + buffer, + 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 fmt::Debug for KtReader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let debug_str = match Rate::USIZE { + 168 => "Kt128Reader { ... }", + 136 => "Kt256Reader { ... }", + _ => unreachable!(), + }; + f.write_str(debug_str) + } +} + +impl Drop for KtReader { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use digest::zeroize::Zeroize; + self.state.zeroize(); + // self.buffer is zeroized by its `Drop` + } + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for KtReader {} diff --git a/k12/src/turbo_shake.rs b/k12/src/turbo_shake.rs new file mode 100644 index 000000000..c73142531 --- /dev/null +++ b/k12/src/turbo_shake.rs @@ -0,0 +1,61 @@ +use digest::{ + block_api::Eager, + block_buffer::{BlockBuffer, BlockSizes}, +}; +use keccak::{Fn1600, State1600}; + +use crate::{consts::PAD, utils::xor_block}; + +#[derive(Default, Clone)] +pub(crate) struct TurboShake { + state: State1600, + buffer: BlockBuffer, +} + +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) + } + }) + } + + 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 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 state(&self) -> &State1600 { + &self.state + } + + pub(crate) fn reset(&mut self) { + self.state = Default::default(); + self.buffer.reset(); + } +} + +impl Drop for TurboShake { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use digest::zeroize::Zeroize; + self.state.zeroize(); + // `buffer` is zeroized by `Drop` + } + } +} diff --git a/k12/src/update.rs b/k12/src/update.rs new file mode 100644 index 000000000..9031c9eea --- /dev/null +++ b/k12/src/update.rs @@ -0,0 +1,107 @@ +use crate::{ + Kt, + consts::{CHUNK_SIZE, CHUNK_SIZE_U64, ROUNDS, S0_DELIM}, + node_turbo_shake, +}; +use digest::{block_buffer::BlockSizes, typenum::Unsigned}; +use keccak::{Backend, BackendClosure}; + +/// Buffer size used by the update closure. +/// +/// 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) data: &'a [u8], + pub(crate) kt: &'a mut Kt, +} + +impl BackendClosure for Closure<'_, Rate> { + #[inline(always)] + fn call_once(self) { + let Kt { + accum_tshk, + node_tshk, + consumed_len, + .. + } = self.kt; + let mut data = self.data; + + let par_p1600 = B::get_par_p1600::(); + let p1600 = B::get_p1600::(); + let par_size = B::ParSize1600::USIZE; + let cv_len = 200 - Rate::USIZE; + // TODO: this should be [0u8; par_size * cv_len]` + let mut cv_buf = [0u8; BUFFER_LEN]; + + // Handle the S_0 chunk which is absorbed directly by the CV accumulator + if let Some(s0_rem_len) = CHUNK_SIZE_U64.checked_sub(*consumed_len) { + let s0_rem_len = usize::try_from(s0_rem_len) + .expect("the value is smaller or equal to CHUNK_SIZE_U64"); + + let (part_data, rem_data) = data.split_at_checked(s0_rem_len).unwrap_or((data, &[])); + + accum_tshk.absorb(p1600, part_data); + *consumed_len += u64::try_from(part_data.len()).expect("length fits into `u64`"); + if rem_data.is_empty() { + return; + } + debug_assert_eq!(*consumed_len, CHUNK_SIZE_U64); + // Note that `consumed_len` does not account for `S0_DELIM` + accum_tshk.absorb(p1600, &S0_DELIM.to_le_bytes()); + data = rem_data; + } + + let partial_chunk_len = usize::try_from(*consumed_len % CHUNK_SIZE_U64) + .expect("the remainder is always smaller than CHUNK_SIZE"); + *consumed_len += u64::try_from(data.len()).expect("`data.len()` fits into `u64`"); + + // 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 { + node_tshk.absorb(p1600, data); + return; + }; + + node_tshk.absorb(p1600, part_data); + + let cv_dst = &mut cv_buf[..cv_len]; + node_tshk.full_node_finalize(p1600, cv_dst); + accum_tshk.absorb(p1600, cv_dst); + + *node_tshk = Default::default(); + data = rem_data; + } + + if data.is_empty() { + return; + } + + // Handle full 8 KiB chunks using the parallel function if the selected backend supports it + if par_size > 1 { + let cvs_dst = &mut cv_buf[..par_size * cv_len]; + let mut par_data_chunks = data.chunks_exact(par_size * CHUNK_SIZE); + + for par_data_chunk in &mut par_data_chunks { + node_turbo_shake::parallel::<_, Rate>(par_p1600, par_data_chunk, cvs_dst); + accum_tshk.absorb(p1600, cvs_dst); + } + data = par_data_chunks.remainder(); + } + + // Handle full 8 KiB chunks using the scalar function + let cv_dst = &mut cv_buf[..cv_len]; + let mut data_chunks = data.chunks_exact(CHUNK_SIZE); + for data_chunk in &mut data_chunks { + node_turbo_shake::scalar::(p1600, data_chunk, cv_dst); + accum_tshk.absorb(p1600, cv_dst); + } + data = data_chunks.remainder(); + + // Absorb the remaining data + node_tshk.absorb(p1600, data); + } +} diff --git a/k12/src/utils.rs b/k12/src/utils.rs new file mode 100644 index 000000000..d3ba99747 --- /dev/null +++ b/k12/src/utils.rs @@ -0,0 +1,53 @@ +use keccak::State1600; + +pub(crate) fn xor_block(state: &mut State1600, block: &[u8]) { + debug_assert!(size_of_val(block) < size_of_val(state)); + + let mut chunks = block.chunks_exact(size_of::()); + for (s, chunk) in state.iter_mut().zip(&mut chunks) { + *s ^= u64::from_le_bytes(chunk.try_into().unwrap()); + } + + let rem = chunks.remainder(); + debug_assert!( + rem.is_empty(), + "block size should be multiple of `size_of::()" + ); +} + +pub(crate) fn copy_cv(state: &State1600, cv_dst: &mut [u8]) { + let mut chunks = cv_dst.chunks_exact_mut(size_of::()); + for (src, dst) in state.iter().zip(&mut chunks) { + dst.copy_from_slice(&src.to_le_bytes()); + } + assert!(chunks.into_remainder().is_empty()); +} + +pub(crate) fn length_encode(len: u64, f: impl FnOnce(&[u8])) { + let mut buf = [0u8; 9]; + buf[..8].copy_from_slice(&len.to_be_bytes()); + buf[8] = u8::try_from((u64::BITS - len.leading_zeros()).div_ceil(8)) + .expect("the division result can not be bigger than 8"); + let idx = 8 - usize::from(buf[8]); + let enc_len = &buf[idx..]; + f(enc_len); +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + fn length_encode() { + use super::length_encode; + + length_encode(0, |r| assert_eq!(r, hex!("00"))); + length_encode(1, |r| assert_eq!(r, hex!("0101"))); + length_encode(12, |r| assert_eq!(r, hex!("0C01"))); + length_encode((1 << 16) - 1, |r| assert_eq!(r, hex!("ffff02"))); + length_encode(1 << 16, |r| assert_eq!(r, hex!("01000003"))); + length_encode(65538, |r| assert_eq!(r, hex!("01000203"))); + length_encode((1 << 32) - 1, |r| assert_eq!(r, hex!("ffffffff04"))); + length_encode(1 << 32, |r| assert_eq!(r, hex!("010000000005"))); + } +} diff --git a/k12/tests/data/kt128_cvs.bin b/k12/tests/data/kt128_cvs.bin new file mode 100644 index 0000000000000000000000000000000000000000..d214060e48dc159e1134f202253769fa530322b0 GIT binary patch literal 1024 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 literal 0 HcmV?d00001 diff --git a/k12/tests/mod.rs b/k12/tests/mod.rs index 0f26a5da6..acd67cc6a 100644 --- a/k12/tests/mod.rs +++ b/k12/tests/mod.rs @@ -1,10 +1,13 @@ -use core::iter; +use core::iter::repeat_n; use hex_literal::hex; use k12::{ Kt128, Kt256, digest::{ExtendableOutput, Update}, }; +#[cfg(feature = "alloc")] +use digest::CustomizedInit; + macro_rules! digest_and_box { ($name:ident, $hasher:ty) => { fn $name(data: &[u8], n: usize) -> Box<[u8]> { @@ -24,7 +27,7 @@ digest_and_box!(kt256_digest_and_box, Kt256); fn kt128_empty() { assert_eq!( kt128_digest_and_box(b"", 32)[..], - hex!("1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5")[..] + hex!("1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5") ); assert_eq!( @@ -32,12 +35,12 @@ fn kt128_empty() { hex!( "1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5" "4269c056b8c82e48276038b6d292966cc07a3d4645272e31ff38508139eb0a71" - )[..], + ), ); assert_eq!( kt128_digest_and_box(b"", 10032)[10000..], - hex!("e8dc563642f7228c84684c898405d3a834799158c079b12880277a1d28e2ff6d")[..] + hex!("e8dc563642f7228c84684c898405d3a834799158c079b12880277a1d28e2ff6d"), ); } @@ -48,7 +51,7 @@ fn kt256_empty() { hex!( "b23d2e9cea9f4904e02bec06817fc10ce38ce8e93ef4c89e6537076af8646404" "e3e8b68107b8833a5d30490aa33482353fd4adc7148ecb782855003aaebde4a9" - )[..], + ), ); assert_eq!( @@ -58,7 +61,7 @@ fn kt256_empty() { "e3e8b68107b8833a5d30490aa33482353fd4adc7148ecb782855003aaebde4a9" "b0925319d8ea1e121a609821ec19efea89e6d08daee1662b69c840289f188ba8" "60f55760b61f82114c030c97e5178449608ccd2cd2d919fc7829ff69931ac4d0" - )[..], + ), ); assert_eq!( @@ -66,7 +69,7 @@ fn kt256_empty() { hex!( "ad4a1d718cf950506709a4c33396139b4449041fc79a05d68da35f1e453522e0" "56c64fe94958e7085f2964888259b9932752f3ccd855288efee5fcbb8b563069" - )[..], + ), ); } @@ -81,13 +84,11 @@ fn kt128_pat_m() { hex!("844d610933b1b9963cbdeb5ae3b6b05cc7cbd67ceedf883eb678a0a8e0371682"), hex!("3c390782a8a4e89fa6367f72feaaf13255c8d95878481d3cd8ce85f58e880af8"), ]; - for i in 0..5 - /*NOTE: can be up to 7 but is slow*/ - { - let len = 17usize.pow(i); + for (i, exp_res) in expected.into_iter().enumerate() { + let len = 17usize.pow(i as u32); let m: Vec = (0..len).map(|j| (j % 251) as u8).collect(); let result = kt128_digest_and_box(&m, 32); - assert_eq!(result[..], expected[i as usize][..]); + assert_eq!(result[..], exp_res); } } @@ -123,13 +124,11 @@ fn kt256_pat_m() { "31a5578f568f911e09cf44746da84224a5266e96a4a535e871324e4f9c7004da" ), ]; - for i in 0..5 - /*NOTE: can be up to 7 but is slow*/ - { - let len = 17usize.pow(i); + for (i, exp_res) in expected.into_iter().enumerate() { + let len = 17usize.pow(i as u32); let m: Vec = (0..len).map(|j| (j % 251) as u8).collect(); let result = kt256_digest_and_box(&m, 64); - assert_eq!(result[..], expected[i as usize][..]); + assert_eq!(result[..], exp_res); } } @@ -141,14 +140,22 @@ fn kt128_pat_c() { hex!("c389e5009ae57120854c2e8c64670ac01358cf4c1baf89447a724234dc7ced74"), hex!("75d2f86a2e644566726b4fbcfc5657b9dbcf070c7b0dca06450ab291d7443bcf"), ]; - for i in 0..4 { - let m: Vec = iter::repeat_n(0xFF, 2usize.pow(i) - 1).collect(); + for (i, exp_res) in expected.into_iter().enumerate() { + let i = i as u32; + let m: Vec = repeat_n(0xFF, 2usize.pow(i) - 1).collect(); let len = 41usize.pow(i); let c: Vec = (0..len).map(|j| (j % 251) as u8).collect(); - let mut h = Kt128::new(&c); + + let mut h = k12::CustomRefKt128::new_customized(&c); h.update(&m); - let result = h.finalize_boxed(32); - assert_eq!(result[..], expected[i as usize][..]); + assert_eq!(h.finalize_boxed(32)[..], exp_res); + + #[cfg(feature = "alloc")] + { + let mut h = k12::CustomKt128::new_customized(&c); + h.update(&m); + assert_eq!(h.finalize_boxed(32)[..], exp_res); + } } } @@ -172,14 +179,22 @@ fn kt256_pat_c() { "30a88cbf4ac2a91a2432743054fbcc9897670e86ba8cec2fc2ace9c966369724" ), ]; - for i in 0..4 { - let m: Vec = iter::repeat_n(0xFF, 2usize.pow(i) - 1).collect(); + for (i, exp_res) in expected.into_iter().enumerate() { + let i = i as u32; + let m: Vec = repeat_n(0xFF, 2usize.pow(i) - 1).collect(); let len = 41usize.pow(i); let c: Vec = (0..len).map(|j| (j % 251) as u8).collect(); - let mut h = Kt256::new(&c); + + let mut h = k12::CustomRefKt256::new_customized(&c); h.update(&m); - let result = h.finalize_boxed(64); - assert_eq!(result[..], expected[i as usize][..]); + assert_eq!(h.finalize_boxed(64)[..], exp_res); + + #[cfg(feature = "alloc")] + { + let mut h = k12::CustomKt256::new_customized(&c); + h.update(&m); + assert_eq!(h.finalize_boxed(64)[..], exp_res); + } } } diff --git a/turbo-shake/tests/data/kt128_cvs.bin b/turbo-shake/tests/data/kt128_cvs.bin new file mode 100644 index 0000000000000000000000000000000000000000..d214060e48dc159e1134f202253769fa530322b0 GIT binary patch literal 1024 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 literal 0 HcmV?d00001 diff --git a/turbo-shake/tests/k12.rs b/turbo-shake/tests/k12.rs new file mode 100644 index 000000000..816840174 --- /dev/null +++ b/turbo-shake/tests/k12.rs @@ -0,0 +1,58 @@ +//! Test vectors for `k12` chaining values +use digest::{ExtendableOutput, Update}; +use turbo_shake::{TurboShake128, TurboShake256}; + +const CHUNK_SIZE: usize = 1 << 13; +const CHUNK_DS: u8 = 0x0B; +const CHUNKS: usize = 32; + +const KT128_CV_LEN: usize = 32; +const KT256_CV_LEN: usize = 64; + +const KT128_CVS_LEN: usize = KT128_CV_LEN * CHUNKS; +const KT256_CVS_LEN: usize = KT256_CV_LEN * CHUNKS; + +const DATA: &[u8] = &{ + let mut buf = [0u8; CHUNKS * CHUNK_SIZE]; + let mut i = 0; + while i < CHUNKS { + let mut j = 0; + while j < CHUNK_SIZE { + buf[i * CHUNK_SIZE + j] = (i + j) as u8; + j += 1; + } + i += 1; + } + buf +}; + +const KT128_CVS: &[u8; KT128_CVS_LEN] = include_bytes!("data/kt128_cvs.bin"); +const KT256_CVS: &[u8; KT256_CVS_LEN] = include_bytes!("data/kt256_cvs.bin"); + +#[test] +fn turboshake_kt128_cvs() { + let mut cvs = [0u8; KT128_CVS_LEN]; + for (data_chunk, cv_dst) in DATA + .chunks_exact(CHUNK_SIZE) + .zip(cvs.chunks_exact_mut(KT128_CV_LEN)) + { + let mut h = TurboShake128::::default(); + h.update(data_chunk); + h.finalize_xof_into(cv_dst); + } + assert_eq!(&cvs, KT128_CVS); +} + +#[test] +fn turboshake_kt256_cvs() { + let mut cvs = [0u8; KT256_CVS_LEN]; + for (data_chunk, cv_dst) in DATA + .chunks_exact(CHUNK_SIZE) + .zip(cvs.chunks_exact_mut(KT256_CV_LEN)) + { + let mut h = TurboShake256::::default(); + h.update(data_chunk); + h.finalize_xof_into(cv_dst); + } + assert_eq!(&cvs, KT256_CVS); +} From 0d71bbae7e1730144717a156603c415e7bfec3da Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Mon, 20 Apr 2026 22:44:56 +0300 Subject: [PATCH 2/2] fix test Co-authored-by: Tony Arcieri --- k12/src/node_turbo_shake.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k12/src/node_turbo_shake.rs b/k12/src/node_turbo_shake.rs index c01a8bc6d..4b6bfba1e 100644 --- a/k12/src/node_turbo_shake.rs +++ b/k12/src/node_turbo_shake.rs @@ -216,7 +216,7 @@ mod tests { } let mut data_chunks = data_chunks.remainder().chunks_exact(CHUNK_SIZE); - let mut cvs_chunks = par_cvs.into_remainder().chunks_exact_mut(KT256_CV_LEN); + let mut cvs_chunks = par_cvs.into_remainder().chunks_exact_mut(KT128_CV_LEN); for (data_chunk, par_cv) in (&mut data_chunks).zip(&mut cvs_chunks) { scalar::(p1600, data_chunk, par_cv);