Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cSHAKE128 and cSHAKE256 implementations #325

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions sha3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@
#[cfg(feature = "std")]
extern crate std;

use block_buffer::block_padding::ZeroPadding;
pub use digest::{self, Digest};

use crate::paddings::{CShake, Shake};
use block_buffer::BlockBuffer;
use digest::consts::{U104, U136, U144, U168, U200, U28, U32, U48, U64, U72};
use digest::generic_array::typenum::Unsigned;
Expand Down Expand Up @@ -161,3 +163,21 @@ shake_impl!(
paddings::Shake,
"SHAKE256 extendable output (XOF) hash function"
);
cshake_impl!(
CShake128,
U168,
"cSHAKE128 extendable output (XOF) hash function with customization"
);
cshake_impl!(
CShake256,
U136,
"cSHAKE256 extendable output (XOF) hash function with customization"
);

#[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..]
}
106 changes: 106 additions & 0 deletions sha3/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,109 @@ macro_rules! shake_impl {
digest::impl_write!($state);
};
}

macro_rules! cshake_impl {
($state:ident, $rate:ident, $doc:expr) => {
#[allow(non_camel_case_types)]
#[derive(Clone)]
#[doc=$doc]
pub struct $state {
init_state: Sha3State,
state: Sha3State,
buffer: BlockBuffer<$rate>,
has_customization: bool,
}

impl $state {
/// Create a new hasher instance using a customization string
/// The customization string is used as domain separation to separate different instances of the same hash function
pub fn new(customization_string: &[u8]) -> Self {
use $crate::left_encode;
let mut buffer = BlockBuffer::default();
let mut state = Sha3State::default();
if customization_string.is_empty() {
return Self {
init_state: state.clone(),
state,
buffer,
has_customization: false,
};
}

let mut array_buf = [0u8; 9];
buffer.input_block(left_encode($rate::to_u64(), &mut array_buf), |b| {
state.absorb_block(b)
});

// This is the encoding of `left_encode(0)`
let empty_function_name = [1, 0];
buffer.input_block(&empty_function_name, |b| state.absorb_block(b));
// buffer.input_block(left_encode((N.len() * 8) as u64, &mut array_buf), |b| {
// state.absorb_block(b)
// });
// buffer.input_block(N, |b| state.absorb_block(b));
buffer.input_block(
left_encode((customization_string.len() * 8) as u64, &mut array_buf),
|b| state.absorb_block(b),
);
buffer.input_block(customization_string, |b| state.absorb_block(b));

state.absorb_block(
buffer
.pad_with::<ZeroPadding>()
.expect("ZeroPadding should never fail"),
);

Self {
init_state: state.clone(),
state,
buffer,
has_customization: true,
}
}
fn absorb(&mut self, input: &[u8]) {
let s = &mut self.state;
self.buffer.input_block(input, |b| s.absorb_block(b));
}

fn apply_padding(&mut self) {
let buf = if self.has_customization {
self.buffer
.pad_with::<CShake>()
.expect("we never use input_lazy")
} else {
self.buffer
.pad_with::<Shake>()
.expect("we never use input_lazy")
};
self.state.absorb_block(buf);
}
}

impl Update for $state {
fn update(&mut self, input: impl AsRef<[u8]>) {
self.absorb(input.as_ref())
}
}

impl ExtendableOutputDirty for $state {
type Reader = Sha3XofReader;

fn finalize_xof_dirty(&mut self) -> Sha3XofReader {
self.apply_padding();
let r = $rate::to_usize();
Sha3XofReader::new(self.state.clone(), r)
}
}

impl Reset for $state {
fn reset(&mut self) {
self.state = self.init_state.clone();
self.buffer.reset();
}
}

opaque_debug::implement!($state);
digest::impl_write!($state);
};
}
1 change: 1 addition & 0 deletions sha3/src/paddings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ macro_rules! impl_padding {
impl_padding!(Keccak, 0x01);
impl_padding!(Sha3, 0x06);
impl_padding!(Shake, 0x1f);
impl_padding!(CShake, 0x04);
Binary file added sha3/tests/data/cshake128.blb
Binary file not shown.
Binary file added sha3/tests/data/cshake256.blb
Binary file not shown.
116 changes: 114 additions & 2 deletions sha3/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![no_std]
// #![no_std]

use digest::dev::{digest_test, xof_test};
use digest::new_test;
use digest::{new_test, ExtendableOutput, XofReader};
use sha3::digest::{Reset, Update};
use sha3::{CShake128, CShake256};
use std::fmt::Debug;

new_test!(keccak_224, "keccak_224", sha3::Keccak224, digest_test);
new_test!(keccak_256, "keccak_256", sha3::Keccak256, digest_test);
Expand All @@ -22,3 +25,112 @@ new_test!(sha3_512, "sha3_512", sha3::Sha3_512, digest_test);

new_test!(shake128, "shake128", sha3::Shake128, xof_test);
new_test!(shake256, "shake256", sha3::Shake256, xof_test);

fn cshake_test_helper<F>(data: &[u8], test: F)
where
F: Fn(&[u8], &[u8], &[u8]) -> Option<&'static str>,
{
use digest::dev::blobby::Blob3Iterator;

for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() {
let customization = row[0];
let input = row[1];
let output = row[2];
if let Some(desc) = test(customization, input, output) {
panic!(
"\n\
Failed test №{}: {}\n\
input:\t{:?}\n\
output:\t{:?}\n",
i, desc, input, output,
);
}
}
}

#[test]
fn test_cshake256() {
cshake_test_helper(
include_bytes!("data/cshake256.blb"),
|customization, input, output| {
xof_test_with_new(input, output, || CShake256::new(customization))
},
)
}

#[test]
fn test_cshake128() {
cshake_test_helper(
include_bytes!("data/cshake128.blb"),
|customization, input, output| {
xof_test_with_new(input, output, || CShake128::new(customization))
},
)
}

pub fn xof_test_with_new<D, F>(input: &[u8], output: &[u8], new: F) -> Option<&'static str>
where
D: Update + ExtendableOutput + Debug + Reset + Clone,
F: Fn() -> D,
{
let mut hasher = new();
let mut buf = [0u8; 1024];
// Test that it works when accepting the message all at once
hasher.update(input);

let mut hasher2 = hasher.clone();
{
let out = &mut buf[..output.len()];
hasher.finalize_xof().read(out);

if out != output {
return Some("whole message");
}
}

// Test if hasher resets correctly
hasher2.reset();
hasher2.update(input);

{
let out = &mut buf[..output.len()];
hasher2.finalize_xof().read(out);

if out != output {
return Some("whole message after reset");
}
}

// Test if hasher accepts message in pieces correctly
let mut hasher = new();
let len = input.len();
let mut left = len;
while left > 0 {
let take = (left + 1) / 2;
hasher.update(&input[len - left..take + len - left]);
left -= take;
}

{
let out = &mut buf[..output.len()];
hasher.finalize_xof().read(out);
if out != output {
return Some("message in pieces");
}
}

// Test reading from reader byte by byte
let mut hasher = new();
hasher.update(input);

let mut reader = hasher.finalize_xof();
let out = &mut buf[..output.len()];
for chunk in out.chunks_mut(1) {
reader.read(chunk);
}

if out != output {
return Some("message in pieces");
}
None
}