diff --git a/.github/workflows/ascon.yml b/.github/workflows/ascon.yml index 8808b58..0322cc3 100644 --- a/.github/workflows/ascon.yml +++ b/.github/workflows/ascon.yml @@ -23,18 +23,6 @@ jobs: with: msrv: 1.60.0 - benches: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: RustCrypto/actions/cargo-cache@master - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2023-02-01 - override: true - - run: cargo build --benches - build: needs: set-msrv runs-on: ubuntu-latest @@ -56,7 +44,6 @@ jobs: target: ${{ matrix.target }} override: true - run: cargo build --no-default-features --target ${{ matrix.target }} - - run: cargo build --no-default-features --target ${{ matrix.target }} --features aead minimal-versions: uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master diff --git a/Cargo.lock b/Cargo.lock index 1c45782..cd14b80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,65 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aead" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" -dependencies = [ - "crypto-common", - "generic-array", -] - [[package]] name = "ascon" -version = "0.2.0" -dependencies = [ - "aead", - "hex-literal", - "subtle", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hex-literal" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +version = "0.3.0-pre" diff --git a/ascon/Cargo.toml b/ascon/Cargo.toml index 2b5c4db..cfdb27a 100644 --- a/ascon/Cargo.toml +++ b/ascon/Cargo.toml @@ -1,34 +1,22 @@ [package] name = "ascon" -version = "0.2.0" -description = """ -Pure Rust implementation of Ascon, a family of authenticated encryption and -hashing algorithms designed to be lightweight and easy to implement -""" -authors = ["quininer kel ", "RustCrypto Developers"] +version = "0.3.0-pre" +description = "Pure rust implementation of the Ascon permutation" +authors = [ + "Sebastian Ramacher ", + "RustCrypto Developers", +] license = "Apache-2.0 OR MIT" documentation = "https://docs.rs/ascon" repository = "https://github.com/RustCrypto/sponges/tree/master/ascon" -keywords = ["aead", "crypto", "sponge"] +keywords = ["Ascon", "crypto", "permutation"] categories = ["cryptography", "no-std"] readme = "README.md" edition = "2021" rust-version = "1.60" -[dependencies] -aead = { version = "0.5", optional = true, default-features = false } -subtle = { version = "2", optional = true, default-features = false } - -[dev-dependencies] -hex-literal = "0.3" - [features] -default = ["aead"] -alloc = ["aead?/alloc"] -std = ["aead?/std"] - -aead = ["dep:aead", "alloc", "subtle"] # TODO(tarcieri): remove alloc dependency +no_unroll = [] # Do not unroll loops for binary size reduction [package.metadata.docs.rs] -all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/ascon/README.md b/ascon/README.md index 6b4543d..e1e8eca 100644 --- a/ascon/README.md +++ b/ascon/README.md @@ -1,4 +1,4 @@ -# [RustCrypto]: Ascon +# [RustCrypto]: Ascon permutation [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] @@ -7,8 +7,9 @@ ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] -Pure Rust implementation of [Ascon], a family of authenticated encryption and -hashing algorithms designed to be lightweight and easy to implement. +Pure Rust implementation of the permutation of [Ascon], a family of +authenticated encryption and hashing algorithms designed to be lightweight and +easy to implement. [Documentation][docs-link] @@ -18,7 +19,7 @@ Ascon is a family of lightweight algorithms built on a core permutation algorithm. These algorithms include: - [x] Authenticated Encryption with Associated Data (AEAD) -- [ ] Hash functions (HASH) and extendible-output functions (XOF) +- [x] Hash functions (HASH) 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 diff --git a/ascon/benches/bench.rs b/ascon/benches/bench.rs deleted file mode 100644 index b12caf5..0000000 --- a/ascon/benches/bench.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![feature(test)] - -extern crate test; - -use ascon::aead::{AeadInPlace, AsconAead, KeyInit}; -use hex_literal::hex; -use test::Bencher; - -const KEY: [u8; 16] = [4; 16]; -const NONCE: [u8; 16] = [8; 16]; -const AAD: [u8; 16] = [3; 16]; -const PLAINTEXT: [u8; 1025] = [99; 1025]; -const CIPHERTEXT: [u8; 1025] = hex!("d5cfb4650e0a82b9e86455de588cfd48c4bf240301efbf1981a57f00c5ec31eb9127d5da85fab0bf3c927b9d875cd1f71e0945f0281a0774e890bae1f98d488580c63e19972d73b6df19773503b0d8f0317727b8ab7681e15237eec369e8ea2915695d6f03f44541ea28f737574d5c7277361636a0a246fbc8c4b6764ef642b9a15211e11f5059fd08d173e4a9fff4cc55389d04a392f0a12dbccce67439e967bb8f39887b078cd68eaa8fd5a3c3b7299536c50a35b7871ab612417fdd1313527cbc82591d20417f2ef3dc37dc1298b32677daac9e79cf28307ceb886aea42694322ac5e978405fff9df6b9da86c83661fa165c957b830dc85350bb41ae6a6f9ee7c713d47b6277b54c64d0ceefd41945f23cfe4f0ba0ea39ba7c829c308ab53591817adeda57c4d3858fff07f57ec4100905e8568e855f61d558f7cffc3dc47b4e2ab9b90053caef52bede5871c5839b33f48e8822dbca59e96532ded31e809d901de72d65d5112ac067f7135670445bdec7e9f41287190dd091f906d6d98b6d15aa3fa475a2b4f99bc0e12e9c057140308a73572dcabad2e2140103028c075e9da74d92177aaea9d1899dace0ee8f4a9edfa9c4abc39a4b6239a2ea3841af1f90af884d10ef43b48924cba8474cb0162c8204acab4647cb680e9b64fb77e4d2195f63dd45e6c21b637db5c15101a72259e5af940ce9e5c0fe0b4affefef059e2982c32dfe50c0aefee368f15867ab2ff205fa855a895a0379b5678f432e25fd884771873acf6ee72fe4cf02c13843239b3114ff9e9bfebc44618bffc731b272cf08e627e47d759f1ae63191892d493a19fb54b96dd2132e416612d4e4a3bb536d0470c151c998a9ce5a44883ac395efa23e201734ef6a9e7ac1dade6a48fff8445e39a38157b5263cf9e52abf8cb604be9f1ed7ebe85e10933ab1f3e0546c17bbf7561233d7f95b32f8086fdbb06948af5a0ffd06dd47e89c3f4a419cc5b34300a76a542aeeae716563204f68f880cac077855297150fd217d6185116d542b64ce1319712760bf61fa1b6b52c8e9feb609bf6caa07b41ed80c7423cbe5390ec9851a2629d9d307971515c0a1696b0d4d52deabab089b945326b1e62a6ec5eb2cf2c58a5af0404962923a0c8605b6d55fd899765b193db8f5a849ecab478e187476779cf5f08d38a812b14ddff6143870ce5d9afc3e2eda91624067654940856421f5a8e0f6e3eefe888bd11c9364a29e2821374a1550e888359230ba863424746c793708e0ba23be967fa527ee5989eaf48541928e594ea846d67d5a6a56aa886be7b693d662cd675c786687c0d870bf19ab7564fb0b87bc6bba157ae04f482fca52858f8ff600141efa24f2f29cb0b8e222d88d9bfd9e60818474e55cc2b11e70125a956934c75cad4207f83b372c501ca7864bf8d387d0f81ec1cc872ff180"); -const TAG: [u8; 16] = hex!("17d7dca595adcaaddf537a1e9fd02854"); - -#[bench] -fn ascon_decrypt_bench(b: &mut Bencher) { - let aead = AsconAead::<12, 8>::new(&KEY.into()); - b.bytes = CIPHERTEXT.len() as u64; - b.iter(|| { - let mut buffer = CIPHERTEXT; - aead.decrypt_in_place_detached(&NONCE.into(), &AAD, &mut buffer, &TAG.into()) - .unwrap(); - }); -} - -#[bench] -fn ascon_encrypt_bench(b: &mut Bencher) { - let aead = AsconAead::<12, 8>::new(&KEY.into()); - b.bytes = PLAINTEXT.len() as u64; - b.iter(|| { - let mut buffer = PLAINTEXT; - aead.encrypt_in_place_detached(&NONCE.into(), &AAD, &mut buffer) - .unwrap(); - }); -} diff --git a/ascon/src/aead.rs b/ascon/src/aead.rs deleted file mode 100644 index 2a59a1e..0000000 --- a/ascon/src/aead.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! Authenticated Encryption with Associated Data. - -pub use ::aead::{self, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser}; - -use crate::{Ascon, KEY_SIZE, RATE, S_SIZE}; -use ::aead::consts::{U0, U16}; - -// TODO(tarcieri): remove hard dependency on alloc -use alloc::vec; - -/// Ascon AEAD key. -pub type Key = ::aead::generic_array::GenericArray; - -/// Ascon AEAD nonce. -pub type Nonce = ::aead::generic_array::GenericArray; - -/// Ascon AEAD authentication tag. -pub type Tag = ::aead::generic_array::GenericArray; - -/// Ascon AEAD encryption. -#[derive(Clone)] -pub struct AsconAead { - key: Key, -} - -impl KeySizeUser for AsconAead { - type KeySize = U16; -} - -impl KeyInit for AsconAead { - fn new(key_bytes: &Key) -> Self { - Self { key: *key_bytes } - } -} - -impl AeadCore for AsconAead { - type NonceSize = U16; - type TagSize = U16; - type CiphertextOverhead = U0; -} - -impl AeadInPlace for AsconAead { - fn encrypt_in_place_detached( - &self, - nonce: &Nonce, - aad: &[u8], - buffer: &mut [u8], - ) -> Result { - let s = aad.len() / RATE + 1; - let t = buffer.len() / RATE + 1; - let l = buffer.len() % RATE; - - let mut aa = vec![0; s * RATE]; - let mut mm = vec![0; t * RATE]; - - // pad aad - aa[..aad.len()].copy_from_slice(aad); - aa[aad.len()] = 0x80; - - // pad message - mm[..buffer.len()].copy_from_slice(buffer); - mm[buffer.len()] = 0x80; - - // init - let mut ss = Ascon::::new(self.key.as_ref(), nonce.as_ref()); - - // aad - if !aad.is_empty() { - process_aad(&mut ss, &aa, s); - } - - ss.state[S_SIZE - 1] ^= 1; - - // plaintext - for i in 0..(t - 1) { - for j in 0..RATE { - ss.state[j] ^= mm[i * RATE + j]; - } - buffer[(i * RATE)..(i * RATE + RATE)].copy_from_slice(&ss.state[..RATE]); - ss.permutation(12 - B, B); - } - - for j in 0..RATE { - ss.state[j] ^= mm[(t - 1) * RATE + j]; - } - - for j in 0..l { - buffer[(t - 1) * RATE + j] = ss.state[j]; - } - - // tag - let mut tag = Tag::default(); - tag.copy_from_slice(&ss.finalize()[S_SIZE - KEY_SIZE..]); - - Ok(tag) - } - - fn decrypt_in_place_detached( - &self, - nonce: &Nonce, - aad: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> Result<(), Error> { - let s = aad.len() / RATE + 1; - let t = buffer.len() / RATE + 1; - let l = buffer.len() % RATE; - - let mut aa = vec![0; s * RATE]; - let mut mm = vec![0; t * RATE]; - - // pad aad - aa[..aad.len()].copy_from_slice(aad); - aa[aad.len()] = 0x80; - - // init - let mut ss = Ascon::::new(self.key.as_ref(), nonce.as_ref()); - - // aad - if !aad.is_empty() { - process_aad(&mut ss, &aa, s); - } - - ss.state[S_SIZE - 1] ^= 1; - - // ciphertext - for i in 0..(t - 1) { - for j in 0..RATE { - mm[i * RATE + j] = ss.state[j] ^ buffer[i * RATE + j]; - } - ss.state[..RATE].copy_from_slice(&buffer[(i * RATE)..(i * RATE + RATE)]); - ss.permutation(12 - B, B); - } - - for j in 0..l { - mm[(t - 1) * RATE + j] = ss.state[j] ^ buffer[(t - 1) * RATE + j]; - } - - for j in 0..l { - ss.state[j] = buffer[(t - 1) * RATE + j]; - } - - ss.state[l] ^= 0x80; - - // finalization - let expected_tag = ss.finalize(); - - use subtle::ConstantTimeEq; - if expected_tag[S_SIZE - KEY_SIZE..] - .ct_eq(tag.as_slice()) - .into() - { - buffer.copy_from_slice(&mm[..buffer.len()]); - Ok(()) - } else { - Err(Error) - } - } -} - -fn process_aad(ss: &mut Ascon, aa: &[u8], s: usize) { - for i in 0..s { - for j in 0..RATE { - ss.state[j] ^= aa[i * RATE + j]; - } - - ss.permutation(12 - B, B); - } -} - -#[cfg(test)] -mod tests { - use super::{AeadInPlace, AsconAead, KeyInit}; - use hex_literal::hex; - - #[test] - fn round_trip() { - let key = [0; 16]; - let nonce = [0; 16]; - let aad = [0; 16]; - let message = [0; 64]; - - let aead = AsconAead::<12, 8>::new(&key.into()); - let mut buffer = message; - - let tag = aead - .encrypt_in_place_detached(&nonce.into(), &aad, &mut buffer) - .unwrap(); - aead.decrypt_in_place_detached(&nonce.into(), &aad, &mut buffer, &tag) - .unwrap(); - - assert_eq!(message, buffer); - } - - #[test] - fn test_vectors() { - const KEY: [u8; 16] = [0; 16]; - const NONCE: [u8; 16] = [0; 16]; - const AAD: &[u8; 5] = b"ASCON"; - const PLAINTEXT: &[u8; 5] = b"ascon"; - const CIPHERTEXT: &[u8; 5] = &hex!("4c8c428949"); - const TAG: &[u8; 16] = &hex!("65fd17b6d30cd876a05a8efcecad993a"); - - let aead = AsconAead::<12, 8>::new(&KEY.into()); - let mut buffer = *PLAINTEXT; - - let tag = aead - .encrypt_in_place_detached(&NONCE.into(), AAD, &mut buffer) - .unwrap(); - - assert_eq!(CIPHERTEXT, &buffer); - assert_eq!(TAG, tag.as_slice()); - - aead.decrypt_in_place_detached(&NONCE.into(), AAD, &mut buffer, &tag) - .unwrap(); - - assert_eq!(PLAINTEXT, &buffer); - } -} diff --git a/ascon/src/lib.rs b/ascon/src/lib.rs index 708d34c..6eb9a3a 100644 --- a/ascon/src/lib.rs +++ b/ascon/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2021-2022 Sebastian Ramacher +// SPDX-License-Identifier: Apache-2.0 OR MIT + #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] @@ -6,161 +9,405 @@ html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" )] #![forbid(unsafe_code)] -#![warn( - clippy::mod_module_files, - clippy::unwrap_used, - missing_docs, - rust_2018_idioms, - unused_lifetimes, - unused_qualifications -)] +#![warn(missing_docs)] -#[cfg(feature = "alloc")] -extern crate alloc; +use core::mem::size_of; -#[cfg(feature = "aead")] -pub mod aead; - -#[cfg(feature = "aead")] -pub use crate::aead::AsconAead; - -use core::convert::TryInto; - -/// Size of an Ascon key in bytes. -const KEY_SIZE: usize = 16; - -/// Size of an Ascon nonce in bytes. -const NONCE_SIZE: usize = 16; - -/// State size in bits. -const S_BITS: usize = 320; - -/// State size. -const S_SIZE: usize = S_BITS / 8; - -/// Rate: Sᵣ. -const RATE: usize = 128 / 8; - -/// Ascon permutation key. -pub type Key = [u8; KEY_SIZE]; - -/// Ascon nonce. -pub type Nonce = [u8; NONCE_SIZE]; - -/// Ascon permutation state. -type State = [u8; S_SIZE]; - -/// Ascon(a,b) permutation. -pub struct Ascon { - key: Key, - state: State, +/// Produce mask for padding. +#[inline(always)] +pub const fn pad(n: usize) -> u64 { + 0x80_u64 << (56 - 8 * n) } -impl Ascon { - /// Initialize Ascon permutation. - pub fn new(key: &Key, nonce: &Nonce) -> Self { - let mut state = [0; S_SIZE]; - state[0] = KEY_SIZE as u8 * 8; - state[1] = RATE as u8 * 8; - state[2] = A as u8; - state[3] = B as u8; +/// Compute round constant +#[inline(always)] +const fn round_constant(round: u64) -> u64 { + ((0xfu64 - round) << 4) | round +} - let pos = S_SIZE - KEY_SIZE - NONCE_SIZE; - let (k, n) = state[pos..].split_at_mut(KEY_SIZE); - k.copy_from_slice(key); - n.copy_from_slice(nonce); +/// The state of Ascon's permutation. +/// +/// The permutation operates on a state of 320 bits represented as 5 64 bit words. +#[derive(Clone, Copy, Debug, Default)] +pub struct State { + x: [u64; 5], +} - permutation(&mut state, 12 - A, A); +/// Ascon's round function +const fn round(x: [u64; 5], c: u64) -> [u64; 5] { + // S-box layer + let x0 = x[0] ^ x[4]; + let x2 = x[2] ^ x[1] ^ c; // with round constant + let x4 = x[4] ^ x[3]; - for (i, &b) in key.iter().enumerate() { - state[pos + KEY_SIZE + i] ^= b; + let tx0 = x0 ^ (!x[1] & x2); + let tx1 = x[1] ^ (!x2 & x[3]); + let tx2 = x2 ^ (!x[3] & x4); + let tx3 = x[3] ^ (!x4 & x0); + let tx4 = x4 ^ (!x0 & x[1]); + let tx1 = tx1 ^ tx0; + let tx3 = tx3 ^ tx2; + let tx0 = tx0 ^ tx4; + + // linear layer + let x0 = tx0 ^ tx0.rotate_right(9); + let x1 = tx1 ^ tx1.rotate_right(22); + let x2 = tx2 ^ tx2.rotate_right(5); + let x3 = tx3 ^ tx3.rotate_right(7); + let x4 = tx4 ^ tx4.rotate_right(34); + [ + tx0 ^ x0.rotate_right(19), + tx1 ^ x1.rotate_right(39), + !(tx2 ^ x2.rotate_right(1)), + tx3 ^ x3.rotate_right(10), + tx4 ^ x4.rotate_right(7), + ] +} + +impl State { + /// Instantiate new state from the given values. + pub fn new(x0: u64, x1: u64, x2: u64, x3: u64, x4: u64) -> Self { + State { + x: [x0, x1, x2, x3, x4], } - - Self { key: *key, state } } - /// Perform Ascon permutation on internal state. - pub fn permutation(&mut self, start: usize, rounds: usize) { - permutation(&mut self.state, start, rounds); + #[cfg(not(feature = "no_unroll"))] + /// 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, + ); } - /// Finalize Ascon permutation. + #[cfg(feature = "no_unroll")] + /// 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(feature = "no_unroll"))] + /// 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(feature = "no_unroll")] + /// 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); + } + + #[cfg(not(feature = "no_unroll"))] + /// 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(feature = "no_unroll")] + /// 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. + 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.into_iter()) + { + dst.copy_from_slice(&u64::to_be_bytes(src)); + } + bytes + } +} + +impl core::ops::Index for State { + type Output = u64; + #[inline(always)] - #[must_use] - pub fn finalize(self) -> [u8; S_SIZE] { - let mut s = self.state; - - for (i, &b) in self.key.iter().enumerate() { - s[RATE + i] ^= b; - } - - permutation(&mut s, 12 - A, A); - - for (i, &b) in self.key.iter().enumerate() { - s[S_SIZE - KEY_SIZE + i] ^= b; - } - - s + fn index(&self, index: usize) -> &Self::Output { + &self.x[index] } } -/// Ascon permutation. -// TODO(tarcieri): change `s` to `&mut [u8; 40]` -fn permutation(s: &mut [u8], start: usize, rounds: usize) { - let mut x = [0; 5]; - let mut t = [0; 5]; +impl core::ops::IndexMut for State { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.x[index] + } +} - assert_eq!(s.len(), 8 * x.len()); +impl TryFrom<&[u64]> for State { + type Error = (); - // TODO(tarcieri): use `array_chunks` to eliminate `unwrap` - #[allow(clippy::unwrap_used)] - s.chunks_exact(8) - .map(|c| c.try_into().unwrap()) - .map(u64::from_be_bytes) - .zip(x.iter_mut()) - .for_each(|(inp, out)| *out = inp); + fn try_from(value: &[u64]) -> Result { + match value.len() { + 5 => Ok(Self::new(value[0], value[1], value[2], value[3], value[4])), + _ => Err(()), + } + } +} - for i in start as u64..(start + rounds) as u64 { - x[2] ^= ((0xfu64 - i) << 4) | i; +impl From<&[u64; 5]> for State { + fn from(value: &[u64; 5]) -> Self { + Self { x: *value } + } +} - x[0] ^= x[4]; - x[4] ^= x[3]; - x[2] ^= x[1]; - t[0] = x[0]; - t[1] = x[1]; - t[2] = x[2]; - t[3] = x[3]; - t[4] = x[4]; - t[0] = !t[0]; - t[1] = !t[1]; - t[2] = !t[2]; - t[3] = !t[3]; - t[4] = !t[4]; - t[0] &= x[1]; - t[1] &= x[2]; - t[2] &= x[3]; - t[3] &= x[4]; - t[4] &= x[0]; - x[0] ^= t[1]; - x[1] ^= t[2]; - x[2] ^= t[3]; - x[3] ^= t[4]; - x[4] ^= t[0]; - x[1] ^= x[0]; - x[0] ^= x[4]; - x[3] ^= x[2]; - x[2] = !x[2]; +impl TryFrom<&[u8]> for State { + type Error = (); - x[0] ^= x[0].rotate_right(19) ^ x[0].rotate_right(28); - x[1] ^= x[1].rotate_right(61) ^ x[1].rotate_right(39); - x[2] ^= x[2].rotate_right(1) ^ x[2].rotate_right(6); - x[3] ^= x[3].rotate_right(10) ^ x[3].rotate_right(17); - x[4] ^= x[4].rotate_right(7) ^ x[4].rotate_right(41); + fn try_from(value: &[u8]) -> Result { + if value.len() != core::mem::size_of::() * 5 { + return Err(()); + } + + let mut state = Self::default(); + for (src, dst) in value + .chunks_exact(core::mem::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(); + for (src, dst) in value + .chunks_exact(core::mem::size_of::()) + .zip(state.x.iter_mut()) + { + *dst = u64::from_be_bytes(src.try_into().unwrap()); + } + state + } +} + +impl AsRef<[u64]> for State { + fn as_ref(&self) -> &[u64] { + &self.x + } +} + +#[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); } - x.into_iter() - .map(u64::to_be_bytes) - // TODO: replace with `array_chunks_mut` on stabilization - .zip(s.chunks_exact_mut(8)) - .for_each(|(inp, out)| out.copy_from_slice(&inp)) + #[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; + + 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(); + + let state2 = State::try_from(bytes.as_slice()); + assert_eq!(state2.expect("try_from bytes").x, state.x); + + let state2 = State::from(&bytes); + assert_eq!(state2.x, state.x); + } } diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 0000000..1530430 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "benches" +version = "0.0.0" +description = "Criteration benchmarks of the sponges crates" +authors = [ + "Sebastian Ramacher ", + "RustCrypto Developers", +] +edition = "2021" +rust-version = "1.60" +publish = false + +[workspace] + +[dev-dependencies] +ascon = { path = "../ascon" } +criterion = "0.4" +rand = { version = "0.8", default-features = false, features = [ + "std_rng", + "getrandom", +] } +# workaround for underspecified dependencies from criterion +# see also https://github.com/RustCrypto/formats/pull/720 +regex = { version = "1.5.1", default-features = false } +serde = { version = "1.0.144", default-features = false } + +[features] +no_unroll = ["ascon?/no_unroll"] + +[[bench]] +name = "ascon" +path = "src/ascon.rs" +harness = false diff --git a/benches/src/ascon.rs b/benches/src/ascon.rs new file mode 100644 index 0000000..03c1cce --- /dev/null +++ b/benches/src/ascon.rs @@ -0,0 +1,46 @@ +// Copyright 2022 Sebastian Ramacher +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use ascon::State; +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::{rngs::StdRng, RngCore, SeedableRng}; + +fn criterion_bench_permutation(c: &mut Criterion) { + let mut rng = StdRng::from_entropy(); + let mut state = State::new( + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + ); + + 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.finish(); +} + +criterion_group!(bench_permutation, criterion_bench_permutation); +criterion_main!(bench_permutation);