Skip to content

Commit

Permalink
Add cSHAKE128 and cSHAKE256 implementations (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvdsn authored May 28, 2022
1 parent 9db7441 commit f9ea367
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 0 deletions.
1 change: 1 addition & 0 deletions sha3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ hex-literal = "0.2.2"
[features]
default = ["std"]
std = ["digest/std"]
reset = [] # Enable reset functionality
31 changes: 31 additions & 0 deletions sha3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ use digest::{
ExtendableOutputCore, FixedOutputCore, OutputSizeUser, Reset, UpdateCore, XofReaderCore,
XofReaderCoreWrapper,
},
generic_array::typenum::Unsigned,
HashMarker, Output,
};

Expand All @@ -89,6 +90,7 @@ use crate::state::Sha3State;
const KECCAK: u8 = 0x01;
const SHA3: u8 = 0x06;
const SHAKE: u8 = 0x1f;
const CSHAKE: u8 = 0x4;

impl_sha3!(Keccak224Core, Keccak224, U28, U144, KECCAK, "Keccak-224");
impl_sha3!(Keccak256Core, Keccak256, U32, U136, KECCAK, "Keccak-256");
Expand Down Expand Up @@ -127,3 +129,32 @@ impl_shake!(
SHAKE,
"SHAKE256",
);

impl_cshake!(
CShake128Core,
CShake128,
CShake128ReaderCore,
CShake128Reader,
U168,
SHAKE,
CSHAKE,
"CSHAKE128",
);
impl_cshake!(
CShake256Core,
CShake256,
CShake256ReaderCore,
CShake256Reader,
U136,
SHAKE,
CSHAKE,
"CSHAKE256",
);

#[inline(always)]
pub(crate) fn left_encode(val: u64, b: &mut [u8; 9]) -> &[u8] {
b[1..].copy_from_slice(&val.to_be_bytes());
let i = b[1..8].iter().take_while(|&&a| a == 0).count();
b[i] = (8 - i) as u8;
&b[i..]
}
171 changes: 171 additions & 0 deletions sha3/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,174 @@ macro_rules! impl_shake {
pub type $reader_full = XofReaderCoreWrapper<$name>;
};
}

macro_rules! impl_cshake {
(
$name:ident, $full_name:ident, $reader:ident, $reader_full:ident,
$rate:ident, $shake_pad:expr, $cshake_pad:expr, $alg_name:expr,
) => {
#[doc = "Core "]
#[doc = $alg_name]
#[doc = " hasher state."]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct $name {
padding: u8,
state: Sha3State,
#[cfg(feature = "reset")]
initial_state: Sha3State,
}

impl $name {
/// Creates a new CSHAKE instance with the given customization.
pub fn new(customization: &[u8]) -> Self {
Self::new_with_function_name(&[], customization)
}

/// 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
/// values defined by NIST. You probably don't need to use this function.
pub fn new_with_function_name(function_name: &[u8], customization: &[u8]) -> Self {
let mut state = Sha3State::default();
if function_name.is_empty() && customization.is_empty() {
return Self {
padding: $shake_pad,
state: state.clone(),
#[cfg(feature = "reset")]
initial_state: state,
};
}

let mut buffer = Buffer::<Self>::default();
let mut b = [0u8; 9];
buffer.digest_blocks(left_encode($rate::to_u64(), &mut b), |blocks| {
for block in blocks {
state.absorb_block(block);
}
});
buffer.digest_blocks(
left_encode((function_name.len() * 8) as u64, &mut b),
|blocks| {
for block in blocks {
state.absorb_block(block);
}
},
);
buffer.digest_blocks(function_name, |blocks| {
for block in blocks {
state.absorb_block(block);
}
});
buffer.digest_blocks(
left_encode((customization.len() * 8) as u64, &mut b),
|blocks| {
for block in blocks {
state.absorb_block(block);
}
},
);
buffer.digest_blocks(customization, |blocks| {
for block in blocks {
state.absorb_block(block);
}
});
state.absorb_block(buffer.pad_with_zeros());

Self {
padding: $cshake_pad,
state: state.clone(),
#[cfg(feature = "reset")]
initial_state: state,
}
}
}

impl HashMarker for $name {}

impl BlockSizeUser for $name {
type BlockSize = $rate;
}

impl BufferKindUser for $name {
type BufferKind = Eager;
}

impl UpdateCore for $name {
#[inline]
fn update_blocks(&mut self, blocks: &[Block<Self>]) {
for block in blocks {
self.state.absorb_block(block)
}
}
}

impl ExtendableOutputCore for $name {
type ReaderCore = $reader;

#[inline]
fn finalize_xof_core(&mut self, buffer: &mut Buffer<Self>) -> Self::ReaderCore {
let pos = buffer.get_pos();
let block = buffer.pad_with_zeros();
block[pos] = self.padding;
let n = block.len();
block[n - 1] |= 0x80;

self.state.absorb_block(block);
$reader {
state: self.state.clone(),
}
}
}

#[cfg(feature = "reset")]
impl Reset for $name {
#[inline]
fn reset(&mut self) {
self.state = self.initial_state.clone();
}
}

impl AlgorithmName for $name {
fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(stringify!($full_name))
}
}

impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(concat!(stringify!($name), " { ... }"))
}
}

#[doc = "Core "]
#[doc = $alg_name]
#[doc = " reader state."]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct $reader {
state: Sha3State,
}

impl BlockSizeUser for $reader {
type BlockSize = $rate;
}

impl XofReaderCore for $reader {
#[inline]
fn read_block(&mut self) -> Block<Self> {
let mut block = Block::<Self>::default();
self.state.as_bytes(&mut block);
self.state.apply_f();
block
}
}

#[doc = $alg_name]
#[doc = " hasher state."]
pub type $full_name = CoreWrapper<$name>;

#[doc = $alg_name]
#[doc = " reader state."]
pub type $reader_full = XofReaderCoreWrapper<$name>;
};
}
145 changes: 145 additions & 0 deletions sha3/tests/cshake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use core::fmt::Debug;
use digest::ExtendableOutput;
#[cfg(feature = "reset")]
use digest::ExtendableOutputReset;

#[cfg(feature = "reset")]
pub(crate) fn cshake_reset_test<D, F>(input: &[u8], output: &[u8], new: F) -> Option<&'static str>
where
D: ExtendableOutputReset + Debug + Clone,
F: Fn() -> D,
{
let mut hasher = new();
let mut buf = [0u8; 1024];
let buf = &mut buf[..output.len()];
// Test that it works when accepting the message all at once
hasher.update(input);
let mut hasher2 = hasher.clone();
hasher.finalize_xof_into(buf);
if buf != output {
return Some("whole message");
}
buf.iter_mut().for_each(|b| *b = 0);

// Test if reset works correctly
hasher2.reset();
hasher2.update(input);
hasher2.finalize_xof_reset_into(buf);
if buf != output {
return Some("whole message after reset");
}
buf.iter_mut().for_each(|b| *b = 0);

// Test that it works when accepting the message in chunks
for n in 1..core::cmp::min(17, input.len()) {
let mut hasher = new();
for chunk in input.chunks(n) {
hasher.update(chunk);
hasher2.update(chunk);
}
hasher.finalize_xof_into(buf);
if buf != output {
return Some("message in chunks");
}
buf.iter_mut().for_each(|b| *b = 0);

hasher2.finalize_xof_reset_into(buf);
if buf != output {
return Some("message in chunks");
}
buf.iter_mut().for_each(|b| *b = 0);
}

None
}

pub(crate) fn cshake_test<D, F>(input: &[u8], output: &[u8], new: F) -> Option<&'static str>
where
D: ExtendableOutput + Debug + Clone,
F: Fn() -> D,
{
let mut hasher = new();
let mut buf = [0u8; 1024];
let buf = &mut buf[..output.len()];
// Test that it works when accepting the message all at once
hasher.update(input);
let mut hasher2 = hasher.clone();
hasher.finalize_xof_into(buf);
if buf != output {
return Some("whole message");
}
buf.iter_mut().for_each(|b| *b = 0);

// Test that it works when accepting the message in chunks
for n in 1..core::cmp::min(17, input.len()) {
let mut hasher = new();
for chunk in input.chunks(n) {
hasher.update(chunk);
hasher2.update(chunk);
}
hasher.finalize_xof_into(buf);
if buf != output {
return Some("message in chunks");
}
buf.iter_mut().for_each(|b| *b = 0);
}

None
}

macro_rules! new_cshake_test {
($name:ident, $test_name:expr, $hasher:ty, $hasher_core:ty, $test_func:ident $(,)?) => {
#[test]
fn $name() {
use digest::dev::blobby::Blob3Iterator;
let data = include_bytes!(concat!("data/", $test_name, ".blb"));

for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() {
let [customization, input, output] = row.unwrap();
if let Some(desc) = $test_func(input, output, || {
<$hasher>::from_core(<$hasher_core>::new(customization))
}) {
panic!(
"\n\
Failed test №{}: {}\n\
input:\t{:?}\n\
output:\t{:?}\n",
i, desc, input, output,
);
}
}
}
};
}

#[cfg(feature = "reset")]
new_cshake_test!(
cshake128_reset,
"cshake128",
sha3::CShake128,
sha3::CShake128Core,
cshake_reset_test
);
#[cfg(feature = "reset")]
new_cshake_test!(
cshake256_reset,
"cshake256",
sha3::CShake256,
sha3::CShake256Core,
cshake_reset_test
);

new_cshake_test!(
cshake128,
"cshake128",
sha3::CShake128,
sha3::CShake128Core,
cshake_test
);
new_cshake_test!(
cshake256,
"cshake256",
sha3::CShake256,
sha3::CShake256Core,
cshake_test
);
Binary file added sha3/tests/data/cshake128.blb
Binary file not shown.
Binary file added sha3/tests/data/cshake256.blb
Binary file not shown.

0 comments on commit f9ea367

Please sign in to comment.