diff --git a/Cargo.lock b/Cargo.lock index 7e439e61c..46b548ff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,7 @@ version = "0.2.1" dependencies = [ "digest", "hex-literal", + "sha3 0.10.8", ] [[package]] @@ -148,9 +149,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.142" +version = "0.2.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "edc207893e85c5d6be840e969b496b53d94cec8be2d501b214f50daa97fa8024" [[package]] name = "md-5" @@ -249,6 +250,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shabal" version = "0.4.1" diff --git a/k12/Cargo.toml b/k12/Cargo.toml index ada20f487..65e7496d0 100644 --- a/k12/Cargo.toml +++ b/k12/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "k12" version = "0.2.1" -description = "Experimental pure Rust implementation of the KangarooTwelve hash function" +description = "Pure Rust implementation of the KangarooTwelve hash function" authors = ["Diggory Hardy "] license = "Apache-2.0 OR MIT" readme = "README.md" @@ -12,7 +12,8 @@ keywords = ["crypto", "hash", "digest"] categories = ["cryptography", "no-std"] [dependencies] -digest = { version = "0.10.3", features = ["alloc"] } +digest = { version = "0.10.3", default-features = false, features = ["core-api"] } +sha3 = "0.10.8" [dev-dependencies] digest = { version = "0.10.3", features = ["alloc", "dev"] } diff --git a/k12/src/lanes.rs b/k12/src/lanes.rs deleted file mode 100644 index 8214fb4e0..000000000 --- a/k12/src/lanes.rs +++ /dev/null @@ -1,133 +0,0 @@ -#![allow(clippy::unreadable_literal)] - -macro_rules! REPEAT4 { - ($e: expr) => { - $e; - $e; - $e; - $e; - }; -} - -macro_rules! REPEAT5 { - ($e: expr) => { - $e; - $e; - $e; - $e; - $e; - }; -} - -macro_rules! REPEAT6 { - ($e: expr) => { - $e; - $e; - $e; - $e; - $e; - $e; - }; -} - -macro_rules! REPEAT24 { - ($e: expr, $s: expr) => { - REPEAT6!({ - $e; - $s; - }); - REPEAT6!({ - $e; - $s; - }); - REPEAT6!({ - $e; - $s; - }); - REPEAT5!({ - $e; - $s; - }); - $e; - }; -} - -macro_rules! FOR5 { - ($v: expr, $s: expr, $e: expr) => { - $v = 0; - REPEAT4!({ - $e; - $v += $s; - }); - $e; - }; -} - -pub const RC: [u64; 12] = [ - 0x000000008000808b, - 0x800000000000008b, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800a, - 0x800000008000000a, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, -]; - -// (0..24).map(|t| ((t+1)*(t+2)/2) % 64) -pub const RHO: [u32; 24] = [ - 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, -]; -pub const PI: [usize; 24] = [ - 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, -]; - -pub fn keccak(lanes: &mut [u64; 25]) { - let mut c = [0u64; 5]; - let (mut x, mut y): (usize, usize); - - #[allow(clippy::needless_range_loop)] - for round in 0..12 { - // θ - FOR5!(x, 1, { - c[x] = lanes[x] ^ lanes[x + 5] ^ lanes[x + 10] ^ lanes[x + 15] ^ lanes[x + 20]; - }); - - FOR5!(x, 1, { - FOR5!(y, 5, { - lanes[x + y] ^= c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1); - }); - }); - - // ρ and π - let mut a = lanes[1]; - x = 0; - REPEAT24!( - { - c[0] = lanes[PI[x]]; - lanes[PI[x]] = a.rotate_left(RHO[x]); - }, - { - a = c[0]; - x += 1; - } - ); - - // χ - FOR5!(y, 5, { - FOR5!(x, 1, { - c[x] = lanes[x + y]; - }); - FOR5!(x, 1, { - lanes[x + y] = c[x] ^ ((!c[(x + 1) % 5]) & c[(x + 2) % 5]); - }); - }); - - // ι - lanes[0] ^= RC[round]; - } -} diff --git a/k12/src/lib.rs b/k12/src/lib.rs index a85ee25a2..a5f593c4a 100644 --- a/k12/src/lib.rs +++ b/k12/src/lib.rs @@ -1,12 +1,7 @@ -//! Experimental pure Rust implementation of the KangarooTwelve -//! cryptographic hash algorithm, based on the reference implementation: +//! Pure Rust implementation of the KangarooTwelve cryptographic hash +//! algorithm, based on the reference implementation: //! -//! -//! -//! Some optimisations copied from: - -// Based off this translation originally by Diggory Hardy: -// +//! #![no_std] #![doc( @@ -16,242 +11,209 @@ #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] -// TODO(tarcieri): eliminate alloc requirement -#[macro_use] -extern crate alloc; - pub use digest; -#[macro_use] -mod lanes; - -// TODO(tarcieri): eliminate usage of `Vec` -use alloc::vec::Vec; -use core::{cmp::min, convert::TryInto, mem}; -use digest::{ExtendableOutput, ExtendableOutputReset, HashMarker, Reset, Update, XofReader}; - -/// The KangarooTwelve extendable-output function (XOF). -#[derive(Debug, Default)] -pub struct KangarooTwelve { - /// Input to be processed - // TODO(tarcieri): don't store input in a `Vec` - buffer: Vec, - - /// Customization string to apply - // TODO(tarcieri): don't store customization in a `Vec` - customization: Vec, +use core::fmt; +use digest::block_buffer::Eager; +use digest::consts::{U128, U168}; +use digest::core_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper, ExtendableOutputCore, + UpdateCore, XofReaderCore, XofReaderCoreWrapper, +}; +use digest::{ExtendableOutputReset, HashMarker, Reset, Update, XofReader}; + +use sha3::{TurboShake128, TurboShake128Core, TurboShake128ReaderCore}; + +const CHUNK_SIZE: usize = 8192; +const CHAINING_VALUE_SIZE: usize = 32; +const LENGTH_ENCODE_SIZE: usize = 255; + +/// Core [`KangarooTwelve`] hasher state. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct KangarooTwelveCore<'cs> { + customization: &'cs [u8], + buffer: [u8; CHUNK_SIZE], + bufpos: usize, + final_tshk: TurboShake128, + chain_tshk: TurboShake128, + chain_length: usize, } -impl KangarooTwelve { - /// Create a new [`KangarooTwelve`] instance. - pub fn new() -> Self { - Self::default() - } - - /// Create a new [`KangarooTwelve`] instance with the given customization. - pub fn new_with_customization(customization: impl AsRef<[u8]>) -> Self { +impl<'cs> KangarooTwelveCore<'cs> { + /// Creates a new KangarooTwelve instance with the given customization. + pub fn new(customization: &'cs [u8]) -> Self { Self { - buffer: Vec::new(), - customization: customization.as_ref().into(), + customization, + buffer: [0u8; CHUNK_SIZE], + bufpos: 0usize, + final_tshk: TurboShake128::from_core(::new(0x06)), + chain_tshk: TurboShake128::from_core(::new(0x0B)), + chain_length: 0usize, } } } -impl HashMarker for KangarooTwelve {} +impl HashMarker for KangarooTwelveCore<'_> {} -impl Update for KangarooTwelve { - fn update(&mut self, bytes: &[u8]) { - self.buffer.extend_from_slice(bytes); - } +impl BlockSizeUser for KangarooTwelveCore<'_> { + type BlockSize = U128; } -impl ExtendableOutput for KangarooTwelve { - type Reader = Reader; - - fn finalize_xof(self) -> Self::Reader { - Reader { - buffer: self.buffer, - customization: self.customization, - finished: false, - } - } +impl BufferKindUser for KangarooTwelveCore<'_> { + type BufferKind = Eager; } -impl ExtendableOutputReset for KangarooTwelve { - fn finalize_xof_reset(&mut self) -> Self::Reader { - let mut buffer = vec![]; - let mut customization = vec![]; +impl UpdateCore for KangarooTwelveCore<'_> { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + for block in blocks { + self.buffer[self.bufpos..self.bufpos + 128].clone_from_slice(block); + self.bufpos += 128; - mem::swap(&mut self.buffer, &mut buffer); - mem::swap(&mut self.customization, &mut customization); + if self.bufpos != CHUNK_SIZE { + continue; + } - Reader { - buffer, - customization, - finished: false, + if self.chain_length == 0 { + self.final_tshk.update(&self.buffer); + self.final_tshk + .update(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + } else { + let mut result = [0u8; CHAINING_VALUE_SIZE]; + self.chain_tshk.update(&self.buffer); + self.chain_tshk.finalize_xof_reset_into(&mut result); + self.final_tshk.update(&result); + } + + self.chain_length += 1; + self.buffer = [0u8; CHUNK_SIZE]; + self.bufpos = 0; } } } -impl Reset for KangarooTwelve { - fn reset(&mut self) { - self.buffer.clear(); - } -} +impl ExtendableOutputCore for KangarooTwelveCore<'_> { + type ReaderCore = KangarooTwelveReaderCore; -/// Extensible output reader. -/// -/// NOTE: this presently only supports one invocation and will *panic* if -/// [`XofReader::read`] is invoked on it multiple times. -#[derive(Debug, Default)] -pub struct Reader { - /// Input to be processed - // TODO(tarcieri): don't store input in a `Vec` - buffer: Vec, - - /// Customization string to apply - // TODO(tarcieri): don't store customization in a `Vec` - customization: Vec, - - /// Has the XOF output already been consumed? - // TODO(tarcieri): allow `XofReader::result` to be called multiple times - finished: bool, -} + #[inline] + fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore { + let mut lenbuf = [0u8; LENGTH_ENCODE_SIZE]; -// TODO(tarcieri): factor more of this logic into the `KangarooTwelve` type -impl XofReader for Reader { - /// Get the resulting output of the function. - /// - /// Panics if called multiple times on the same instance (TODO: don't panic!) - fn read(&mut self, output: &mut [u8]) { - assert!( - !self.finished, - "not yet implemented: multiple XofReader::read invocations unsupported" + // 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), ); - let b = 8192; - let c = 256; - - let mut slice = Vec::new(); // S - slice.extend_from_slice(&self.buffer); - slice.extend_from_slice(&self.customization); - slice.extend_from_slice(&right_encode(self.customization.len())[..]); - - // === Cut the input string into chunks of b bytes === - let n = (slice.len() + b - 1) / b; - let mut slices = Vec::with_capacity(n); // Si - for i in 0..n { - let ub = min((i + 1) * b, slice.len()); - slices.push(&slice[i * b..ub]); + // 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 didnot exceed a single chaining value + let tshk = TurboShake128::from_core(::new(0x07)) + .chain(&self.buffer[..self.bufpos]) + .finalize_xof_reset(); + return KangarooTwelveReaderCore { tshk }; + } + // Calculate last chaining value + let mut result = [0u8; 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); + // Pad final node calculation + self.final_tshk + .update(length_encode(self.chain_length, &mut lenbuf)); + self.final_tshk.update(&[0xff, 0xff]); + + KangarooTwelveReaderCore { + tshk: self.final_tshk.finalize_xof_reset(), } - - // TODO(tarcieri): get rid of intermediate output buffer - let tmp_buffer = if n == 1 { - // === Process the tree with only a final node === - f(slices[0], 0x07, output.len()) - } else { - // === Process the tree with kangaroo hopping === - // TODO: in parallel - let mut intermediate = Vec::with_capacity(n - 1); // CVi - for i in 0..n - 1 { - intermediate.push(f(slices[i + 1], 0x0B, c / 8)); - } - - let mut node_star = Vec::new(); - node_star.extend_from_slice(slices[0]); - node_star.extend_from_slice(&[3, 0, 0, 0, 0, 0, 0, 0]); - - #[allow(clippy::needless_range_loop)] - for i in 0..n - 1 { - node_star.extend_from_slice(&intermediate[i][..]); - } - - node_star.extend_from_slice(&right_encode(n - 1)); - node_star.extend_from_slice(b"\xFF\xFF"); - - f(&node_star[..], 0x06, output.len()) - }; - - output.copy_from_slice(&tmp_buffer); - self.finished = true; } } -fn f(input: &[u8], suffix: u8, mut output_len: usize) -> Vec { - let mut state = [0u8; 200]; - let max_block_size = 1344 / 8; // r, also known as rate in bytes - - // === Absorb all the input blocks === - // We unroll first loop, which allows simple copy - let mut block_size = min(input.len(), max_block_size); - state[0..block_size].copy_from_slice(&input[0..block_size]); - - let mut offset = block_size; - while offset < input.len() { - keccak(&mut state); - block_size = min(input.len() - offset, max_block_size); - for i in 0..block_size { - // TODO: is this sufficiently optimisable or better to convert to u64 first? - state[i] ^= input[i + offset]; +impl Default for KangarooTwelveCore<'_> { + #[inline] + fn default() -> Self { + Self { + customization: &[], + buffer: [0u8; CHUNK_SIZE], + bufpos: 0usize, + final_tshk: TurboShake128::from_core(::new(0x06)), + chain_tshk: TurboShake128::from_core(::new(0x0B)), + chain_length: 0usize, } - offset += block_size; - } - if block_size == max_block_size { - // TODO: condition is nearly always false; tests pass without this. - // Why is it here? - keccak(&mut state); - block_size = 0; } +} - // === Do the padding and switch to the squeezing phase === - state[block_size] ^= suffix; - if ((suffix & 0x80) != 0) && (block_size == (max_block_size - 1)) { - // TODO: condition is almost always false — in fact tests pass without - // this block! So why is it here? - keccak(&mut state); +impl Reset for KangarooTwelveCore<'_> { + #[inline] + fn reset(&mut self) { + *self = Self::new(self.customization); } - state[max_block_size - 1] ^= 0x80; - keccak(&mut state); - - // === Squeeze out all the output blocks === - let mut output = Vec::with_capacity(output_len); - while output_len > 0 { - block_size = min(output_len, max_block_size); - output.extend_from_slice(&state[0..block_size]); - output_len -= block_size; - if output_len > 0 { - keccak(&mut state); - } +} + +impl AlgorithmName for KangarooTwelveCore<'_> { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!(KangarooTwelve)) } - output } -fn keccak(state: &mut [u8; 200]) { - let mut lanes = [0u64; 25]; - let mut y; - for x in 0..5 { - FOR5!(y, 5, { - let pos = 8 * (x + y); - lanes[x + y] = u64::from_le_bytes(state[pos..(pos + 8)].try_into().unwrap()); - }); +impl fmt::Debug for KangarooTwelveCore<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(concat!(stringify!(KangarooTwelveCore), " { ... }")) } - lanes::keccak(&mut lanes); - for x in 0..5 { - FOR5!(y, 5, { - let i = 8 * (x + y); - state[i..i + 8].copy_from_slice(&lanes[x + y].to_le_bytes()); - }); +} + +/// Core [`KangarooTwelve`] reader state. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct KangarooTwelveReaderCore { + tshk: XofReaderCoreWrapper, +} + +impl BlockSizeUser for KangarooTwelveReaderCore { + type BlockSize = U168; // TurboSHAKE128 block size +} + +impl XofReaderCore for KangarooTwelveReaderCore { + #[inline] + fn read_block(&mut self) -> Block { + let mut block = Block::::default(); + self.tshk.read(&mut block); + block } } -fn right_encode(mut x: usize) -> Vec { - let mut slice = Vec::new(); - while x > 0 { - slice.push((x % 256) as u8); - x /= 256; +/// [`KangarooTwelve`] hasher state. +pub type KangarooTwelve<'cs> = CoreWrapper>; + +/// [`KangarooTwelve`] reader state. +pub type KangarooTwelveReader = XofReaderCoreWrapper; + +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; } - slice.reverse(); - let len = slice.len(); - slice.push(len as u8); - slice + 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/tests/mod.rs b/k12/tests/mod.rs index bd27c4464..92456e046 100644 --- a/k12/tests/mod.rs +++ b/k12/tests/mod.rs @@ -2,11 +2,11 @@ use core::iter; use hex_literal::hex; use k12::{ digest::{ExtendableOutput, Update}, - KangarooTwelve, + KangarooTwelve, KangarooTwelveCore, }; fn digest_and_box(data: &[u8], n: usize) -> Box<[u8]> { - let mut h = KangarooTwelve::new(); + let mut h = KangarooTwelve::default(); h.update(data); h.finalize_boxed(n) } @@ -67,7 +67,7 @@ fn pat_c() { let m: Vec = iter::repeat(0xFF).take(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 = KangarooTwelve::new_with_customization(c); + let mut h = KangarooTwelve::from_core(KangarooTwelveCore::new(&c)); h.update(&m); let result = h.finalize_boxed(32); assert_eq!(result[..], expected[i as usize][..]);