From 3cf3bcbf5952a8a27fd9d8518eb733130158621c 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: Sat, 2 May 2026 20:10:26 +0300 Subject: [PATCH 1/4] Use `sponge-cursor` --- Cargo.lock | 10 +++ cshake/Cargo.toml | 5 +- cshake/src/lib.rs | 162 ++++++++++++++++------------------------------ 3 files changed, 67 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 022295ef..eecbd623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,7 @@ dependencies = [ "digest", "hex-literal", "keccak", + "sponge-cursor", ] [[package]] @@ -355,6 +356,15 @@ dependencies = [ "hex-literal", ] +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "git+https://github.com/RustCrypto/utils?branch=add-sponge-cursor#8ff587ce452a552ce7730338972b78a098079ed9" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "streebog" version = "0.11.0" diff --git a/cshake/Cargo.toml b/cshake/Cargo.toml index 2dedf5fe..c74098f0 100644 --- a/cshake/Cargo.toml +++ b/cshake/Cargo.toml @@ -13,8 +13,9 @@ 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 = { git = "https://github.com/RustCrypto/utils", branch = "add-sponge-cursor" } [dev-dependencies] digest = { version = "0.11", features = ["dev"] } @@ -23,7 +24,7 @@ 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..becaf645 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, + array::ArraySize, block_api::{AlgorithmName, BlockSizeUser}, - block_buffer::{BlockSizes, EagerBuffer, LazyBuffer, ReadBuffer}, 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); - } -} From 5f4fc77f9a9b24a63c73e62b404457ca84528432 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: Sat, 2 May 2026 20:22:57 +0300 Subject: [PATCH 2/4] fix imports --- cshake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cshake/src/lib.rs b/cshake/src/lib.rs index becaf645..042b6d54 100644 --- a/cshake/src/lib.rs +++ b/cshake/src/lib.rs @@ -15,7 +15,7 @@ use core::{fmt, marker::PhantomData}; use digest::{ CollisionResistance, CustomizedInit, ExtendableOutput, HashMarker, Update, XofReader, array::ArraySize, - block_api::{AlgorithmName, BlockSizeUser}, + common::{AlgorithmName, BlockSizeUser}, consts::{U16, U32, U136, U168}, }; use keccak::{Keccak, State1600}; From bcd6ad024e72ed26c8fb471994eb5f100a9a02f0 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: Sun, 3 May 2026 18:39:51 +0300 Subject: [PATCH 3/4] update turboshake --- Cargo.lock | 1 + Cargo.toml | 1 + cshake/Cargo.toml | 4 +- turboshake/Cargo.toml | 7 ++- turboshake/src/lib.rs | 130 ++++++++++++++---------------------------- 5 files changed, 51 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eecbd623..9cf58077 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -408,6 +408,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/cshake/Cargo.toml b/cshake/Cargo.toml index c74098f0..c2893a74 100644 --- a/cshake/Cargo.toml +++ b/cshake/Cargo.toml @@ -15,10 +15,10 @@ description = "Implementation of the cSHAKE family of extendable-output function [dependencies] digest = { version = "0.11", default-features = false } keccak = "0.2" -sponge-cursor = { git = "https://github.com/RustCrypto/utils", branch = "add-sponge-cursor" } +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] 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"); -} From aa2f94ee1de1f9da86660f5b0656bab0164fe549 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: Sun, 3 May 2026 19:10:35 +0300 Subject: [PATCH 4/4] migrate ascon-xof128 --- Cargo.lock | 3 +- ascon-xof128/Cargo.toml | 7 +- ascon-xof128/src/cxof.rs | 54 +++++++--------- ascon-xof128/src/reader.rs | 29 +++++++-- ascon-xof128/src/xof.rs | 61 ++++++++---------- .../data/ascon_cxof128_serialization.bin | Bin 48 -> 41 bytes .../tests/data/ascon_xof128_serialization.bin | Bin 48 -> 41 bytes ascon-xof128/tests/mod.rs | 12 ++-- 8 files changed, 83 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cf58077..77ace3d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "ascon", "digest", "hex-literal", + "sponge-cursor", ] [[package]] @@ -359,7 +360,7 @@ dependencies = [ [[package]] name = "sponge-cursor" version = "0.1.0" -source = "git+https://github.com/RustCrypto/utils?branch=add-sponge-cursor#8ff587ce452a552ce7730338972b78a098079ed9" +source = "git+https://github.com/RustCrypto/utils?branch=add-sponge-cursor#d5900bf4ff1953757ad2a0a63d427c3e28ef6b26" dependencies = [ "hybrid-array", "zeroize", 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 166cb41fc58df34cfb3ed35f8117830a54c4e1be..20a0b53c95d3a307648660b8506a51451e3fed20 100644 GIT binary patch literal 41 xcmd<6G>dqv?!);@sruvpT7kS}%x+s8Me8CSDZDiK%|36VUz)Ig{%iy2NJQ&&>|2^K9%`kIgG-EwjHRFz3SMy1Z6JRsev>5<&m~ literal 48 zcmdlqv+P688s&@$diPJ()Spf*kzQ|=pW)`1($aKyyNaZpghX@6=ai}U7@z