diff --git a/Cargo.lock b/Cargo.lock index e61f987d9..5ae14c452 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,12 +221,31 @@ dependencies = [ "hex-literal", ] +[[package]] +name = "multimixer-128" +version = "0.1.0" +dependencies = [ + "digest", + "hex-literal", + "rand_chacha", +] + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index e94b026bf..644cf566a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "md2", "md4", "md5", + "multimixer-128", "ripemd", "sha1", "sha1-checked", diff --git a/multimixer-128/Cargo.toml b/multimixer-128/Cargo.toml new file mode 100644 index 000000000..31c131e87 --- /dev/null +++ b/multimixer-128/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "multimixer-128" +version = "0.1.0" +edition = "2021" + +[dependencies] +digest = "=0.11.0-pre.8" +rand_chacha = "0.3.1" + +[dev-dependencies] +digest = { version = "=0.11.0-pre.8", features = ["dev"] } +hex-literal = "0.4.1" diff --git a/multimixer-128/README.md b/multimixer-128/README.md new file mode 100644 index 000000000..ccdc6b88e --- /dev/null +++ b/multimixer-128/README.md @@ -0,0 +1 @@ +An implementation based on the paper [Multimixer-128: Universal Keyed Hashing Based on Integer Multiplication](https://eprint.iacr.org/2023/1357) and [the reference code](https://github.com/Parisaa/Multimixer/tree/main/ReferenceCode). ALso check out this repo: [itzmeanjan/multimixer-128](https://github.com/itzmeanjan/multimixer-128). diff --git a/multimixer-128/benches/mod.rs b/multimixer-128/benches/mod.rs new file mode 100644 index 000000000..c0b4060b2 --- /dev/null +++ b/multimixer-128/benches/mod.rs @@ -0,0 +1,15 @@ +#![feature(test)] +extern crate test; + +use digest::bench_update; +use digest::crypto_common::KeyInit; +use multimixer_128::Multimixer; +use test::Bencher; +//Multimixer::from_core(MultimixerCore::dummy_bencher()); +bench_update!( + Multimixer::new(&[0u8;32].into()); + multimixer_10 10; + multimixer_100 100; + multimixer_1000 1000; + multimixer_10000 10000; +); diff --git a/multimixer-128/src/lib.rs b/multimixer-128/src/lib.rs new file mode 100644 index 000000000..9477ae570 --- /dev/null +++ b/multimixer-128/src/lib.rs @@ -0,0 +1,224 @@ +use core::fmt; + +use digest::crypto_common::{InvalidLength, Key, KeyInit, KeySizeUser}; +pub use digest::{self, Digest}; + +use digest::typenum::Unsigned; +use digest::{ + consts::{U32, U64}, + core_api::{ + AlgorithmName, Block, BlockSizeUser, BufferKindUser, CoreWrapper, FixedOutputCore, + UpdateCore, + }, + HashMarker, OutputSizeUser, +}; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha8Rng; + +const BLOCKSIZE: usize = 32; + +#[derive(Clone)] +pub struct MultimixerCore { + key_blocks: Vec>, + block_sums: [u64; 8usize], + block_index: usize, + rng: Option, +} + +pub type Multimixer = CoreWrapper; + +impl MultimixerCore { + fn compress(&mut self, message_block: &Block) { + let mut x: [u32; 4usize] = [0u32; 4]; + //let mut h = [0u32; 4]; + let mut y = [0u32; 4]; + //let mut k = [0u32; 4]; + let mut a = [0u32; 4]; + let mut b = [0u32; 4]; + let mut p = [0u32; 4]; + let mut q = [0u32; 4]; + + let (h, k) = if let Some(ref mut rng) = self.rng.as_mut() { + let mut h = [0u32; 4]; + let mut k = [0u32; 4]; + + for i in 0..4 { + h[i] = rng.next_u32(); + k[i] = rng.next_u32(); + } + (h, k) + } else { + let mut h = [0u32; 4]; + let mut k = [0u32; 4]; + for i in 0..4 { + h[i] = u32::from_ne_bytes([ + self.key_blocks[self.block_index][i * 4], + self.key_blocks[self.block_index][i * 4 + 1], + self.key_blocks[self.block_index][i * 4 + 2], + self.key_blocks[self.block_index][i * 4 + 3], + ]); + k[i] = u32::from_ne_bytes([ + self.key_blocks[self.block_index][i * 4 + 16], + self.key_blocks[self.block_index][i * 4 + 17], + self.key_blocks[self.block_index][i * 4 + 18], + self.key_blocks[self.block_index][i * 4 + 19], + ]); + } + (h, k) + }; + + for i in 0..4 { + x[i] = u32::from_ne_bytes([ + message_block[i * 4], + message_block[1 + i * 4], + message_block[2 + i * 4], + message_block[3 + i * 4], + ]); + y[i] = u32::from_ne_bytes([ + message_block[16 + i * 4], + message_block[17 + i * 4], + message_block[18 + i * 4], + message_block[19 + i * 4], + ]); + + a[i] = x[i].wrapping_add(h[i]); + b[i] = y[i].wrapping_add(k[i]); + } + + for i in 0..4 { + p[i] = a[i] + .wrapping_add(a[(i + 1) % 4]) + .wrapping_add(a[(i + 2) % 4]); + q[i] = b[(i + 1) % 4] + .wrapping_add(b[(i + 2) % 4]) + .wrapping_add(b[(i + 3) % 4]); + } + + let block_res = [ + a[0] as u64 * b[0] as u64, + a[1] as u64 * b[1] as u64, + a[2] as u64 * b[2] as u64, + a[3] as u64 * b[3] as u64, + p[0] as u64 * q[0] as u64, + p[1] as u64 * q[1] as u64, + p[2] as u64 * q[2] as u64, + p[3] as u64 * q[3] as u64, + ]; + + self.block_sums + .iter_mut() + .zip(block_res.iter()) + .for_each(|(sum, &res)| { + *sum = sum.wrapping_add(res); + }); + + self.block_index += 1; + } + + fn finalize(&self, out: &mut digest::Output) { + for (i, block) in self.block_sums.iter().enumerate() { + let bytes = block.to_ne_bytes(); // Convert u64 to little-endian byte array + for (j, &byte) in bytes.iter().enumerate() { + out[i * 8 + j] = byte; + } + } + } +} + +impl KeySizeUser for MultimixerCore { + type KeySize = U32; + + fn key_size() -> usize { + Self::KeySize::USIZE + } +} + +impl KeyInit for MultimixerCore { + //Uses the key to initialize ChaCha8Rng RNG and fills the key_blocks array + fn new(key: &Key) -> Self { + Self { + block_sums: [0; 8], + key_blocks: Vec::new(), + block_index: 0, + rng: Some(ChaCha8Rng::from_seed( + key.as_slice() + .try_into() + .expect("Key needs to be able to use as seed."), + )), + } + } + + //Uses key instead of RNG, needs to be same size as message. + fn new_from_slice(key: &[u8]) -> Result { + let key_block_size = ::KeySize::USIZE; + if key.len() % key_block_size != 0 { + return Err(InvalidLength); + } + let mut s = Self { + block_sums: [0; 8], + key_blocks: Vec::new(), + block_index: 0, + rng: None, + }; + + for block in key.chunks(key_block_size) { + let array: [u8; BLOCKSIZE] = block + .try_into() + .expect("Key chunk is not of length 32 bytes"); + s.key_blocks.push(array.into()); + } + Ok(s) + } +} + +impl HashMarker for MultimixerCore {} + +impl BlockSizeUser for MultimixerCore { + fn block_size() -> usize { + 32usize + } + + type BlockSize = U32; +} + +impl BufferKindUser for MultimixerCore { + type BufferKind = digest::block_buffer::Eager; +} + +impl OutputSizeUser for MultimixerCore { + type OutputSize = U64; + + fn output_size() -> usize { + 64usize + } +} + +impl UpdateCore for MultimixerCore { + fn update_blocks(&mut self, blocks: &[Block]) { + for block in blocks { + self.compress(block); + } + } +} + +impl FixedOutputCore for MultimixerCore { + fn finalize_fixed_core( + &mut self, + _buffer: &mut digest::core_api::Buffer, + out: &mut digest::Output, + ) { + self.finalize(out); + } +} + +impl AlgorithmName for MultimixerCore { + fn write_alg_name(f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Multimixer-128") + } +} + +impl fmt::Debug for MultimixerCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("MultimixerCore { ... }") + } +} diff --git a/multimixer-128/tests/mod.rs b/multimixer-128/tests/mod.rs new file mode 100644 index 000000000..c1ff5ca77 --- /dev/null +++ b/multimixer-128/tests/mod.rs @@ -0,0 +1,38 @@ +use digest::crypto_common::KeyInit; +use digest::FixedOutput; +use digest::Update; +use hex_literal::hex; + +use multimixer_128::Multimixer; + +#[test] +fn multimixer_10_test() { + let key = &hex!("4420823cfde6f1c26b30f90ec7dd01e4887534a20f0b0d04c36ed80e71e0fd77"); + let mut h = Multimixer::new(key.into()); + let data = [0; 100]; + digest::Update::update(&mut h, &data[..]); +} + +#[test] +fn multimixer_simple_test() { + let key = &hex!("4420823cfde6f1c26b30f90ec7dd01e4887534a20f0b0d04c36ed80e71e0fd77"); + let message = &hex!("b07670eb940bd5335f973daad8619b91ffc911f57cced458bbbf2ce03753c9bd"); + let mut h = Multimixer::new_from_slice(key).unwrap(); + h.update(message); + assert_eq!( + h.finalize_fixed().as_slice(), + &hex!("aca6cbf6480d9b17bb9d13efbb3589596ca1ce7d3ae4edac586d77e22313b5189f6c97c2a910636df227850c398ca01b92ab25c1ccec360e4020eec91331a383")[..] + ); +} + +#[test] +fn multimixer_simple_160_test() { + let key = &hex!("0702f5a3c49364cc514d0f07c64a1dc2824228ec9b07121f42158c3cdd2e610eff428e62e5c7a889857c7d1e59b3db1fb4d366d9238825805a314d1e68db161b2ef0bd32a0144010e241cae40c8a2e80a62b9a11c41d85a04285c23b9b30d97d69a9adc8f63542e50f955066bdc7a631d1b040211699a0d598a3b48ba6043e4ca2a6a723e78ff5e8bac2281c4418fb807dadb9bdce9dedae550e4b807144395e"); + let message = &hex!("d21932883668852228256f58dd0bbcf9917066fc78d9e7bb60f62583d06704c2f927ced914b4ea036199023d9aa190d2d19de79a43e347538104d912bcd7cd90092e2e02c489ed8bbef6acc6e93bf7b54ad44b095885bc4193d38493d78cddabf86efbcdd92e2042694c750d34814ff532cc5f012dda1a6fd8b11834d63c878e5bf5186d2cc73fe596fec93bf5364cc5675583d593fc6dacf83404b1881ce199"); + let mut h = Multimixer::new_from_slice(key).unwrap(); + h.update(message); + assert_eq!( + h.finalize_fixed().as_slice(), + &hex!("fa80bf97fff5b9b014b0691c27907b1f04ac2debd24f964b9ae546d269d6eca934762c68a377114213591c04a762bb331eafe51633c06ee7304fc8dca2c88604")[..] + ); +}