diff --git a/Cargo.lock b/Cargo.lock index 14af5bf..f95c820 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,9 +5,6 @@ version = 4 [[package]] name = "ascon" version = "0.5.0-rc.0" -dependencies = [ - "zeroize", -] [[package]] name = "bash-f" @@ -30,16 +27,16 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "typenum", ] [[package]] name = "keccak" -version = "0.2.0-rc.2" +version = "0.2.0" dependencies = [ "cfg-if", "cpufeatures", @@ -48,18 +45,12 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "zeroize" -version = "1.8.2" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" diff --git a/ascon/Cargo.toml b/ascon/Cargo.toml index 61a307a..4a633df 100644 --- a/ascon/Cargo.toml +++ b/ascon/Cargo.toml @@ -12,9 +12,6 @@ keywords = ["Ascon", "crypto", "permutation"] categories = ["cryptography", "no-std"] description = "Pure Rust implementation of the Ascon permutation" -[dependencies] -zeroize = { version = "1.6", default-features = false, optional = true } - [lints.rust] missing_copy_implementations = "warn" missing_debug_implementations = "warn" diff --git a/ascon/README.md b/ascon/README.md index 6a4d1a6..42c8e1c 100644 --- a/ascon/README.md +++ b/ascon/README.md @@ -7,25 +7,11 @@ ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] -Pure Rust implementation of the permutation of [Ascon], a family of -authenticated encryption and hashing algorithms designed to be lightweight and -easy to implement. +Pure Rust implementation of the [Ascon] permutation, winner of the +[NIST Lightweight Cryptography] competition. -[Documentation][docs-link] - -## About - -Ascon is a family of lightweight algorithms built on a core permutation -algorithm. These algorithms include: - -- [x] [`ascon-aead128`]: Authenticated Encryption with Associated Data -- [x] [`ascon-hash256`]: Hash functions and extendible-output functions (XOF) -- [ ] Pseudo-random functions (PRF) and message authentication codes (MAC) - -Ascon has been selected as [new standard for lightweight cryptography] in the -[NIST Lightweight Cryptography] competition, and has also been selected as the -primary choice for lightweight authenticated encryption in the final -portfolio of the [CAESAR competition]. +This is a low-level crate used for implementation of higher-level agorithms, +e.g. [`ascon-hash256`] and [`ascon-aead128`]. ## Minimum Supported Rust Version @@ -67,7 +53,5 @@ dual licensed as above, without any additional terms or conditions. [`ascon-aead128`]: https://docs.rs/ascon-aead128 [`ascon-hash256`]: https://docs.rs/ascon-hash256 [RustCrypto]: https://github.com/rustcrypto -[Ascon]: https://ascon.iaik.tugraz.at/ -[New standard for lightweight cryptography]: https://www.nist.gov/news-events/news/2023/02/nist-selects-lightweight-cryptography-algorithms-protect-small-devices +[Ascon]: https://ascon.iaik.tugraz.at [NIST Lightweight Cryptography]: https://csrc.nist.gov/projects/lightweight-cryptography/finalists -[CAESAR competition]: https://competitions.cr.yp.to/caesar-submissions.html diff --git a/ascon/src/lib.rs b/ascon/src/lib.rs index 1d14567..005a58d 100644 --- a/ascon/src/lib.rs +++ b/ascon/src/lib.rs @@ -7,37 +7,25 @@ )] #![forbid(unsafe_code)] -#[cfg(feature = "zeroize")] -use zeroize::{Zeroize, ZeroizeOnDrop}; - -/// Produce mask for padding. -#[inline(always)] -#[must_use] -pub const fn pad(n: usize) -> u64 { - 0x80_u64 << (56 - 8 * n) -} - /// Compute round constant #[inline(always)] const fn round_constant(round: u64) -> u64 { - ((0xfu64 - round) << 4) | round + ((0xFu64 - round) << 4) | round } -/// The state of Ascon's permutation. -/// -/// The permutation operates on a state of 320 bits represented as 5 64 bit words. -#[derive(Clone, Debug, Default)] -#[allow(missing_copy_implementations)] -pub struct State { - x: [u64; 5], -} +/// Maximum number of rounds supported by the Ascon permutation. +const MAX_ROUNDS: usize = 12; + +/// Ascon's permutation state +pub type State = [u64; 5]; /// Ascon's round function -const fn round(x: [u64; 5], c: u64) -> [u64; 5] { - let (mut x0, mut x1, mut x3, mut x4) = (x[0], x[1], x[3], x[4]); +#[inline(always)] +const fn round(state: &mut State, c: u64) { + let [mut x0, mut x1, mut x2, mut x3, mut x4] = *state; - // Addition of Constants - let mut x2 = x[2] ^ c; + // Addition of round constant + x2 ^= c; // Substitution Layer. // BGC Optimized Implementations from: @@ -61,370 +49,71 @@ const fn round(x: [u64; 5], c: u64) -> [u64; 5] { x4 = t0 ^ t12; // Linear Diffusion Layer - [ + *state = [ x0 ^ x0.rotate_right(19) ^ x0.rotate_right(28), x1 ^ x1.rotate_right(61) ^ x1.rotate_right(39), x2 ^ x2.rotate_right(1) ^ x2.rotate_right(6), x3 ^ x3.rotate_right(10) ^ x3.rotate_right(17), x4 ^ x4.rotate_right(7) ^ x4.rotate_right(41), - ] + ]; } -impl State { - /// Instantiate new state from the given values. - #[must_use] - pub fn new(x0: u64, x1: u64, x2: u64, x3: u64, x4: u64) -> Self { - State { - x: [x0, x1, x2, x3, x4], - } - } - - #[cfg(not(ascon_backend = "soft-compact"))] - /// Perform permutation with 12 rounds. - pub fn permute_12(&mut self) { - // We could in theory iter().fold() over an array of round constants, - // but the compiler produces better results when optimizing this chain - // of round function calls. - self.x = round( - round( - round( - round( - round( - round( - round( - round( - round(round(round(round(self.x, 0xf0), 0xe1), 0xd2), 0xc3), - 0xb4, - ), - 0xa5, - ), - 0x96, - ), - 0x87, - ), - 0x78, - ), - 0x69, - ), - 0x5a, - ), - 0x4b, - ); - } - - #[cfg(ascon_backend = "soft-compact")] - /// Perform permutation with 12 rounds. - pub fn permute_12(&mut self) { - self.x = [ - 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, - ] - .into_iter() - .fold(self.x, round); - } - - #[cfg(not(ascon_backend = "soft-compact"))] - /// Perform permutation with 8 rounds. - pub fn permute_8(&mut self) { - self.x = round( - round( - round( - round( - round(round(round(round(self.x, 0xb4), 0xa5), 0x96), 0x87), - 0x78, - ), - 0x69, - ), - 0x5a, - ), - 0x4b, - ); - } - - #[cfg(ascon_backend = "soft-compact")] - /// Perform permutation with 8 rounds. - pub fn permute_8(&mut self) { - self.x = [0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b] - .into_iter() - .fold(self.x, round); - } +/// Apply Ascon permutation with the given number of rounds. +/// +/// Results in a compilation error if `ROUNDS` is greater than 12. +#[inline] +pub const fn permute(state: &mut State) { + const { assert!(ROUNDS <= MAX_ROUNDS) }; #[cfg(not(ascon_backend = "soft-compact"))] - /// Perform permutation with 6 rounds. - pub fn permute_6(&mut self) { - self.x = round( - round( - round(round(round(round(self.x, 0x96), 0x87), 0x78), 0x69), - 0x5a, - ), - 0x4b, - ); - } - - #[cfg(ascon_backend = "soft-compact")] - /// Perform permutation with 6 rounds. - pub fn permute_6(&mut self) { - self.x = [0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b] - .into_iter() - .fold(self.x, round); - } - - /// Perform permutation with 1 round - pub fn permute_1(&mut self) { - self.x = round(self.x, 0x4b); - } - - /// Perform a given number (up to 12) of permutations - /// - /// Panics (in debug mode) if `rounds` is larger than 12. - pub fn permute_n(&mut self, rounds: usize) { - debug_assert!(rounds <= 12); - - let start = 12 - rounds; - self.x = (start..12).fold(self.x, |x, round_index| { - round(x, round_constant(round_index as u64)) - }); - } - - /// Convert state to bytes. - #[must_use] - pub fn as_bytes(&self) -> [u8; 40] { - let mut bytes = [0u8; size_of::() * 5]; - for (dst, src) in bytes.chunks_exact_mut(size_of::()).zip(self.x) { - dst.copy_from_slice(&u64::to_be_bytes(src)); - } - bytes - } -} - -impl core::ops::Index for State { - type Output = u64; - - #[inline(always)] - fn index(&self, index: usize) -> &Self::Output { - &self.x[index] - } -} - -impl core::ops::IndexMut for State { - #[inline(always)] - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.x[index] - } -} - -impl TryFrom<&[u64]> for State { - type Error = (); - - fn try_from(value: &[u64]) -> Result { - match value.len() { - 5 => Ok(Self::new(value[0], value[1], value[2], value[3], value[4])), - _ => Err(()), + { + macro_rules! unroll_round { + ($state:ident, $round:literal, $rounds:expr) => { + if $round >= MAX_ROUNDS - $rounds { + let rc = round_constant($round); + round($state, rc); + } + }; } - } -} -impl From<&[u64; 5]> for State { - fn from(value: &[u64; 5]) -> Self { - Self { x: *value } + unroll_round!(state, 0, ROUNDS); + unroll_round!(state, 1, ROUNDS); + unroll_round!(state, 2, ROUNDS); + unroll_round!(state, 3, ROUNDS); + unroll_round!(state, 4, ROUNDS); + unroll_round!(state, 5, ROUNDS); + unroll_round!(state, 6, ROUNDS); + unroll_round!(state, 7, ROUNDS); + unroll_round!(state, 8, ROUNDS); + unroll_round!(state, 9, ROUNDS); + unroll_round!(state, 10, ROUNDS); + unroll_round!(state, 11, ROUNDS); } -} - -impl TryFrom<&[u8]> for State { - type Error = (); - - fn try_from(value: &[u8]) -> Result { - if value.len() != size_of::() * 5 { - return Err(()); - } - - let mut state = Self::default(); - // TODO(tarcieri): use `[T]::as_chunks` when MSRV 1.88 - #[allow(clippy::unwrap_in_result, reason = "MSRV")] - #[allow(clippy::unwrap_used, reason = "MSRV")] - for (src, dst) in value.chunks_exact(size_of::()).zip(state.x.iter_mut()) { - *dst = u64::from_be_bytes(src.try_into().unwrap()); - } - Ok(state) - } -} - -impl From<&[u8; size_of::() * 5]> for State { - fn from(value: &[u8; size_of::() * 5]) -> Self { - let mut state = Self::default(); - - // TODO(tarcieri): use `[T]::as_chunks` when MSRV 1.88 - #[allow(clippy::unwrap_used, reason = "MSRV")] - for (src, dst) in value.chunks_exact(size_of::()).zip(state.x.iter_mut()) { - *dst = u64::from_be_bytes(src.try_into().unwrap()); + #[cfg(ascon_backend = "soft-compact")] + { + let mut i = MAX_ROUNDS - ROUNDS; + while i < MAX_ROUNDS { + round(state, round_constant(i as u64)); + i += 1; } - state } } -impl AsRef<[u64]> for State { - fn as_ref(&self) -> &[u64] { - &self.x - } +/// Apply Ascon permutation with 12 rounds. +#[inline] +pub const fn permute12(state: &mut State) { + permute::<12>(state); } -#[cfg(feature = "zeroize")] -impl Drop for State { - fn drop(&mut self) { - self.x.zeroize(); - } +/// Apply Ascon permutation with 8 rounds. +#[inline] +pub const fn permute8(state: &mut State) { + permute::<8>(state); } -#[cfg(feature = "zeroize")] -impl ZeroizeOnDrop for State {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn pad_0to7() { - assert_eq!(pad(0), 0x8000000000000000); - assert_eq!(pad(1), 0x80000000000000); - assert_eq!(pad(2), 0x800000000000); - assert_eq!(pad(3), 0x8000000000); - assert_eq!(pad(4), 0x80000000); - assert_eq!(pad(5), 0x800000); - assert_eq!(pad(6), 0x8000); - assert_eq!(pad(7), 0x80); - } - - #[test] - fn round_constants() { - assert_eq!(round_constant(0), 0xf0); - assert_eq!(round_constant(1), 0xe1); - assert_eq!(round_constant(2), 0xd2); - assert_eq!(round_constant(3), 0xc3); - assert_eq!(round_constant(4), 0xb4); - assert_eq!(round_constant(5), 0xa5); - assert_eq!(round_constant(6), 0x96); - assert_eq!(round_constant(7), 0x87); - assert_eq!(round_constant(8), 0x78); - assert_eq!(round_constant(9), 0x69); - assert_eq!(round_constant(10), 0x5a); - assert_eq!(round_constant(11), 0x4b); - } - - #[test] - fn one_round() { - let state = round( - [ - 0x0123456789abcdef, - 0x23456789abcdef01, - 0x456789abcdef0123, - 0x6789abcdef012345, - 0x89abcde01234567f, - ], - 0x1f, - ); - assert_eq!( - state, - [ - 0x3c1748c9be2892ce, - 0x5eafb305cd26164f, - 0xf9470254bb3a4213, - 0xf0428daf0c5d3948, - 0x281375af0b294899 - ] - ); - } - - #[test] - fn state_permute_12() { - let mut state = State::new( - 0x0123456789abcdef, - 0xef0123456789abcd, - 0xcdef0123456789ab, - 0xabcdef0123456789, - 0x89abcdef01234567, - ); - state.permute_12(); - assert_eq!(state[0], 0x206416dfc624bb14); - assert_eq!(state[1], 0x1b0c47a601058aab); - assert_eq!(state[2], 0x8934cfc93814cddd); - assert_eq!(state[3], 0xa9738d287a748e4b); - assert_eq!(state[4], 0xddd934f058afc7e1); - } - - #[test] - fn state_permute_6() { - let mut state = State::new( - 0x0123456789abcdef, - 0xef0123456789abcd, - 0xcdef0123456789ab, - 0xabcdef0123456789, - 0x89abcdef01234567, - ); - state.permute_6(); - assert_eq!(state[0], 0xc27b505c635eb07f); - assert_eq!(state[1], 0xd388f5d2a72046fa); - assert_eq!(state[2], 0x9e415c204d7b15e7); - assert_eq!(state[3], 0xce0d71450fe44581); - assert_eq!(state[4], 0xdd7c5fef57befe48); - } - - #[test] - fn state_permute_8() { - let mut state = State::new( - 0x0123456789abcdef, - 0xef0123456789abcd, - 0xcdef0123456789ab, - 0xabcdef0123456789, - 0x89abcdef01234567, - ); - state.permute_8(); - assert_eq!(state[0], 0x67ed228272f46eee); - assert_eq!(state[1], 0x80bc0b097aad7944); - assert_eq!(state[2], 0x2fa599382c6db215); - assert_eq!(state[3], 0x368133fae2f7667a); - assert_eq!(state[4], 0x28cefb195a7c651c); - } - - #[test] - fn state_permute_n() { - let mut state = State::new( - 0x0123456789abcdef, - 0xef0123456789abcd, - 0xcdef0123456789ab, - 0xabcdef0123456789, - 0x89abcdef01234567, - ); - let mut state2 = state.clone(); - - state.permute_6(); - state2.permute_n(6); - assert_eq!(state.x, state2.x); - - state.permute_8(); - state2.permute_n(8); - assert_eq!(state.x, state2.x); - - state.permute_12(); - state2.permute_n(12); - assert_eq!(state.x, state2.x); - } - - #[test] - fn state_convert_bytes() { - let state = State::new( - 0x0123456789abcdef, - 0xef0123456789abcd, - 0xcdef0123456789ab, - 0xabcdef0123456789, - 0x89abcdef01234567, - ); - let bytes = state.as_bytes(); - - // test TryFrom<&[u8]> - let state2 = State::try_from(&bytes[..]); - assert_eq!(state2.expect("try_from bytes").x, state.x); - - let state2 = State::from(&bytes); - assert_eq!(state2.x, state.x); - } +/// Apply Ascon permutation with 6 rounds. +#[inline] +pub const fn permute6(state: &mut State) { + permute::<6>(state); } diff --git a/ascon/tests/mod.rs b/ascon/tests/mod.rs new file mode 100644 index 0000000..4421ac2 --- /dev/null +++ b/ascon/tests/mod.rs @@ -0,0 +1,52 @@ +//! Basic tests + +#[test] +fn ascon_permute6() { + let mut state = [ + 0x0123456789abcdef, + 0xef0123456789abcd, + 0xcdef0123456789ab, + 0xabcdef0123456789, + 0x89abcdef01234567, + ]; + ascon::permute6(&mut state); + assert_eq!(state[0], 0xc27b505c635eb07f); + assert_eq!(state[1], 0xd388f5d2a72046fa); + assert_eq!(state[2], 0x9e415c204d7b15e7); + assert_eq!(state[3], 0xce0d71450fe44581); + assert_eq!(state[4], 0xdd7c5fef57befe48); +} + +#[test] +fn ascon_permute8() { + let mut state = [ + 0x0123456789abcdef, + 0xef0123456789abcd, + 0xcdef0123456789ab, + 0xabcdef0123456789, + 0x89abcdef01234567, + ]; + ascon::permute8(&mut state); + assert_eq!(state[0], 0x67ed228272f46eee); + assert_eq!(state[1], 0x80bc0b097aad7944); + assert_eq!(state[2], 0x2fa599382c6db215); + assert_eq!(state[3], 0x368133fae2f7667a); + assert_eq!(state[4], 0x28cefb195a7c651c); +} + +#[test] +fn state_permute_12() { + let mut state = [ + 0x0123456789abcdef, + 0xef0123456789abcd, + 0xcdef0123456789ab, + 0xabcdef0123456789, + 0x89abcdef01234567, + ]; + ascon::permute12(&mut state); + assert_eq!(state[0], 0x206416dfc624bb14); + assert_eq!(state[1], 0x1b0c47a601058aab); + assert_eq!(state[2], 0x8934cfc93814cddd); + assert_eq!(state[3], 0xa9738d287a748e4b); + assert_eq!(state[4], 0xddd934f058afc7e1); +} diff --git a/benches/src/ascon.rs b/benches/src/ascon.rs index 1a84434..6407086 100644 --- a/benches/src/ascon.rs +++ b/benches/src/ascon.rs @@ -1,21 +1,20 @@ -use ascon::State; use criterion::{criterion_group, criterion_main, Criterion}; fn criterion_bench_permutation(c: &mut Criterion) { - let mut state = core::hint::black_box(State::new( + let mut state = core::hint::black_box([ 0xd0764d4f4476689f, 0x519e4174576f3791, 0xfbe07cfb0c24ed8c, 0xb37d9f600cd835b8, 0xcb231c3874846a73, - )); + ]); let mut c = c.benchmark_group("Permutation"); - c.bench_function("1 round", |b| b.iter(|| state.permute_1())); - c.bench_function("6 rounds", |b| b.iter(|| state.permute_6())); - c.bench_function("8 rounds", |b| b.iter(|| state.permute_8())); - c.bench_function("12 rounds", |b| b.iter(|| state.permute_12())); + c.bench_function("1 round", |b| b.iter(|| ascon::permute::<1>(&mut state))); + c.bench_function("6 rounds", |b| b.iter(|| ascon::permute6(&mut state))); + c.bench_function("8 rounds", |b| b.iter(|| ascon::permute8(&mut state))); + c.bench_function("12 rounds", |b| b.iter(|| ascon::permute12(&mut state))); core::hint::black_box(state);