Skip to content

Commit

Permalink
sha3: add TurboSHAKE (#458)
Browse files Browse the repository at this point in the history
  • Loading branch information
aewag committed Mar 17, 2023
1 parent 852feb9 commit 9b218cf
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 6 deletions.
22 changes: 22 additions & 0 deletions sha3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const SHA3: u8 = 0x06;
const SHAKE: u8 = 0x1f;
const CSHAKE: u8 = 0x4;

// Round counts
const TURBO_SHAKE_ROUND_COUNT: usize = 12;

impl_sha3!(Keccak224Core, Keccak224, U28, U144, KECCAK, "Keccak-224");
impl_sha3!(Keccak256Core, Keccak256, U32, U136, KECCAK, "Keccak-256");
impl_sha3!(Keccak384Core, Keccak384, U48, U104, KECCAK, "Keccak-384");
Expand Down Expand Up @@ -165,6 +168,25 @@ impl_shake!(
"2.16.840.1.101.3.4.2.11",
);

impl_turbo_shake!(
TurboShake128Core,
TurboShake128,
TurboShake128ReaderCore,
TurboShake128Reader,
U168,
"TurboSHAKE128",
"",
);
impl_turbo_shake!(
TurboShake256Core,
TurboShake256,
TurboShake256ReaderCore,
TurboShake256Reader,
U136,
"TurboSHAKE256",
"",
);

impl_cshake!(
CShake128Core,
CShake128,
Expand Down
133 changes: 131 additions & 2 deletions sha3/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ macro_rules! impl_shake {
fn read_block(&mut self) -> Block<Self> {
let mut block = Block::<Self>::default();
self.state.as_bytes(&mut block);
self.state.apply_f();
self.state.permute();
block
}
}
Expand Down Expand Up @@ -228,6 +228,135 @@ macro_rules! impl_shake {
};
}

macro_rules! impl_turbo_shake {
(
$name:ident, $full_name:ident, $reader:ident, $reader_full:ident,
$rate:ident, $alg_name:expr $(,)?
) => {
#[doc = "Core "]
#[doc = $alg_name]
#[doc = " hasher state."]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct $name {
domain_separation: u8,
state: Sha3State,
}

impl $name {
/// Creates a new TurboSHAKE instance with the given domain separation.
/// Note that the domain separation needs to be a byte with a value in
/// the range [0x01, . . . , 0x7F]
pub fn new(domain_separation: u8) -> Self {
assert!((0x01..=0x7F).contains(&domain_separation));
Self {
domain_separation,
state: Sha3State::new(TURBO_SHAKE_ROUND_COUNT),
}
}
}

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.domain_separation;
let n = block.len();
block[n - 1] |= 0x80;

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

impl Reset for $name {
#[inline]
fn reset(&mut self) {
*self = Self::new(self.domain_separation);
}
}

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.permute();
block
}
}

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

#[doc = $alg_name]
#[doc = " reader state."]
pub type $reader_full = XofReaderCoreWrapper<$reader>;
};
(
$name:ident, $full_name:ident, $reader:ident, $reader_full:ident,
$rate:ident, $alg_name:expr, $oid:literal $(,)?
) => {
impl_turbo_shake!($name, $full_name, $reader, $reader_full, $rate, $alg_name);

#[cfg(feature = "oid")]
#[cfg_attr(docsrs, doc(cfg(feature = "oid")))]
impl AssociatedOid for $name {
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap($oid);
}
};
}

macro_rules! impl_cshake {
(
$name:ident, $full_name:ident, $reader:ident, $reader_full:ident,
Expand Down Expand Up @@ -384,7 +513,7 @@ macro_rules! impl_cshake {
fn read_block(&mut self) -> Block<Self> {
let mut block = Block::<Self>::default();
self.state.as_bytes(&mut block);
self.state.apply_f();
self.state.permute();
block
}
}
Expand Down
26 changes: 22 additions & 4 deletions sha3/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
use core::convert::TryInto;

const PLEN: usize = 25;
const DEFAULT_ROUND_COUNT: usize = 24;

#[derive(Clone, Default)]
#[derive(Clone)]
pub(crate) struct Sha3State {
pub state: [u64; PLEN],
round_count: usize,
}

impl Default for Sha3State {
fn default() -> Self {
Self {
state: [0u64; PLEN],
round_count: DEFAULT_ROUND_COUNT,
}
}
}

impl Sha3State {
pub(crate) fn new(round_count: usize) -> Self {
Self {
state: [0u64; PLEN],
round_count,
}
}

#[inline(always)]
pub(crate) fn absorb_block(&mut self, block: &[u8]) {
debug_assert_eq!(block.len() % 8, 0);
Expand All @@ -16,7 +34,7 @@ impl Sha3State {
*s ^= u64::from_le_bytes(b.try_into().unwrap());
}

keccak::f1600(&mut self.state);
keccak::keccak_p(&mut self.state, self.round_count);
}

#[inline(always)]
Expand All @@ -27,7 +45,7 @@ impl Sha3State {
}

#[inline(always)]
pub(crate) fn apply_f(&mut self) {
keccak::f1600(&mut self.state);
pub(crate) fn permute(&mut self) {
keccak::keccak_p(&mut self.state, self.round_count);
}
}
Binary file added sha3/tests/data/turboshake128.blb
Binary file not shown.
Binary file added sha3/tests/data/turboshake256.blb
Binary file not shown.
108 changes: 108 additions & 0 deletions sha3/tests/turboshake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use core::{convert::TryInto, fmt::Debug};
use digest::ExtendableOutput;

pub(crate) fn turbo_shake_test<D, F>(
input: &[u8],
output: &[u8],
truncate_output: usize,
new: F,
) -> Option<&'static str>
where
D: ExtendableOutput + Debug + Clone,
F: Fn() -> D,
{
let mut hasher = new();
let mut buf = [0u8; 16 * 1024];
let buf = &mut buf[..truncate_output + 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[truncate_output..] != 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[truncate_output..] != output {
return Some("message in chunks");
}
buf.iter_mut().for_each(|b| *b = 0);
}

None
}

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

for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() {
let [domain_separation, input, input_pattern_length, output, truncate_output] =
row.unwrap();

let input = if (input_pattern_length.len() == 0) {
input.to_vec()
} else if (input.len() == 0) {
let pattern_length =
u64::from_be_bytes(input_pattern_length.try_into().unwrap());
let mut input = Vec::<u8>::new();
for value in 0..pattern_length {
input.push((value % 0xFB).try_into().unwrap());
}
input
} else {
panic!(
"\
failed to read tests data\n\
input:\t{:02X?}\n\
input_pattern_length:\t{:02X?}\n",
input, input_pattern_length,
);
};

if let Some(desc) = $test_func(
&input,
output,
u64::from_be_bytes(truncate_output.try_into().unwrap())
.try_into()
.unwrap(),
|| <$hasher>::from_core(<$hasher_core>::new(domain_separation[0])),
) {
panic!(
"\n\
Failed test №{}: {}\n\
input:\t{:02X?}\n\
output:\t{:02X?}\n",
i, desc, &input, output,
);
}
}
}
};
}

new_turbo_shake_test!(
turboshake128,
"turboshake128",
sha3::TurboShake128,
sha3::TurboShake128Core,
turbo_shake_test,
);
new_turbo_shake_test!(
turboshake256,
"turboshake256",
sha3::TurboShake256,
sha3::TurboShake256Core,
turbo_shake_test,
);

0 comments on commit 9b218cf

Please sign in to comment.