diff --git a/.github/workflows/keccak.yml b/.github/workflows/keccak.yml index 1552640..1d49ae5 100644 --- a/.github/workflows/keccak.yml +++ b/.github/workflows/keccak.yml @@ -42,8 +42,9 @@ jobs: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features parallel - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="soft-compact"' + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="soft"' run: cargo build --target ${{ matrix.target }} minimal-versions: @@ -80,25 +81,36 @@ jobs: targets: ${{ matrix.target }} - run: cargo check --target ${{ matrix.target }} - run: cargo test --target ${{ matrix.target }} - - run: cargo test --release --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features parallel + - run: cargo test --release --target ${{ matrix.target }} --features parallel - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="armv8_asm"' + RUSTFLAGS: '-Dwarnings --cfg keccak_soft_compact' run: cargo test --release --target ${{ matrix.target }} - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="soft-compact"' + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="soft"' + run: cargo test --release --target ${{ matrix.target }} + - if: contains(matrix.target, 'aarch64') + env: + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="aarch64_sha3"' run: cargo test --release --target ${{ matrix.target }} test-simd: runs-on: ubuntu-latest - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd"' steps: - uses: actions/checkout@v4 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: toolchain: nightly - - run: cargo test + - env: + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd128"' + run: cargo test --features parallel + - env: + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd256"' + run: cargo test --features parallel + - env: + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd512"' + run: cargo test --features parallel test-miri: runs-on: ubuntu-latest @@ -117,15 +129,22 @@ jobs: components: miri - run: cargo miri setup - run: cargo miri test --target ${{ matrix.target }} + - run: cargo miri test --target ${{ matrix.target }} --features parallel + - env: + RUSTFLAGS: '-Dwarnings --cfg keccak_soft_compact' + run: cargo miri test --release --target ${{ matrix.target }} --features parallel + - env: + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd128"' + run: cargo miri test --release --target ${{ matrix.target }} --features parallel - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="armv8_asm"' - run: cargo miri test --release --target ${{ matrix.target }} + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd256"' + run: cargo miri test --release --target ${{ matrix.target }} --features parallel - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd"' - run: cargo miri test --release --target ${{ matrix.target }} + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="simd512"' + run: cargo miri test --release --target ${{ matrix.target }} --features parallel - env: - RUSTFLAGS: '-Dwarnings --cfg keccak_backend="soft-compact"' - run: cargo miri test --release --target ${{ matrix.target }} + RUSTFLAGS: '-Dwarnings --cfg keccak_backend="soft"' + run: cargo miri test --release --target ${{ matrix.target }} --features parallel aarch64-sha3: needs: set-msrv @@ -154,8 +173,8 @@ jobs: mv /tmp/cross ~/.cargo/bin shell: bash - env: - RUSTFLAGS: '-C target-feature=+sha3 --cfg keccak_backend="armv8_asm"' + RUSTFLAGS: '-C target-feature=+sha3 --cfg keccak_backend="aarch64_sha3"' run: | cd keccak # Cross doesn't enable `sha3` by default, but QEMU supports it. - cross test --target aarch64-unknown-linux-gnu --no-default-features + cross test --target aarch64-unknown-linux-gnu --features parallel diff --git a/Cargo.lock b/Cargo.lock index 4c1736f..14af5bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,12 @@ dependencies = [ name = "bash-f" version = "0.1.0" +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "cpufeatures" version = "0.3.0" @@ -22,18 +28,35 @@ dependencies = [ "libc", ] +[[package]] +name = "hybrid-array" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" +dependencies = [ + "typenum", +] + [[package]] name = "keccak" version = "0.2.0-rc.2" dependencies = [ + "cfg-if", "cpufeatures", + "hybrid-array", ] [[package]] name = "libc" -version = "0.2.177" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "zeroize" diff --git a/keccak/Cargo.toml b/keccak/Cargo.toml index 014d313..52ca012 100644 --- a/keccak/Cargo.toml +++ b/keccak/Cargo.toml @@ -16,9 +16,16 @@ readme = "README.md" edition = "2024" rust-version = "1.85" +[dependencies] +cfg-if = "1" +hybrid-array = { version = "0.4", optional = true } + [target.'cfg(target_arch = "aarch64")'.dependencies] cpufeatures = "0.3" +[features] +parallel = ["dep:hybrid-array"] + [lints.rust] missing_debug_implementations = "warn" missing_docs = "warn" @@ -29,7 +36,10 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" -check-cfg = ['cfg(keccak_backend, values("simd", "soft-compact"))'] +check-cfg = [ + 'cfg(keccak_soft_compact)', + 'cfg(keccak_backend, values("aarch64_sha3", "simd128", "simd256", "simd512", "soft"))', +] [lints.clippy] borrow_as_ptr = "warn" diff --git a/keccak/README.md b/keccak/README.md index 0eb6020..9b77050 100644 --- a/keccak/README.md +++ b/keccak/README.md @@ -7,10 +7,8 @@ ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] -Pure Rust implementation of the [Keccak Sponge Function][keccak] including the keccak-f -and keccak-p variants. - -[Documentation][docs-link] +Pure Rust implementation of the [Keccak Sponge Function][keccak] including the `keccak-f` +and `keccak-p` variants. ## About @@ -20,17 +18,48 @@ cryptographic functions are built. For the SHA-3 family including the SHAKE XOFs, see the [`sha3`] crate, which is built on this crate. -## Minimum Supported Rust Version - -Rust **1.85** or higher. - -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. - -## SemVer Policy - -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above +## Examples + +```rust +// Test vector from KeccakCodePackage +let mut state = [0u64; 25]; + +keccak::Keccak::new().with_f1600(|f1600| { + f1600(&mut state); + assert_eq!(state, [ + 0xF1258F7940E1DDE7, 0x84D5CCF933C0478A, 0xD598261EA65AA9EE, 0xBD1547306F80494D, + 0x8B284E056253D057, 0xFF97A42D7F8E6FD4, 0x90FEE5A0A44647C4, 0x8C5BDA0CD6192E76, + 0xAD30A6F71B19059C, 0x30935AB7D08FFC64, 0xEB5AA93F2317D635, 0xA9A6E6260D712103, + 0x81A57C16DBCF555F, 0x43B831CD0347C826, 0x01F22F1A11A5569F, 0x05E5635A21D9AE61, + 0x64BEFEF28CC970F2, 0x613670957BC46611, 0xB87C5A554FD00ECB, 0x8C3EE88A1CCF32C8, + 0x940C7922AE3A2614, 0x1841F924A2C509E4, 0x16F53526E70465C2, 0x75F644E97F30A13B, + 0xEAF1FF7B5CECA249, + ]); + + f1600(&mut state); + assert_eq!(state, [ + 0x2D5C954DF96ECB3C, 0x6A332CD07057B56D, 0x093D8D1270D76B6C, 0x8A20D9B25569D094, + 0x4F9C4F99E5E7F156, 0xF957B9A2DA65FB38, 0x85773DAE1275AF0D, 0xFAF4F247C3D810F7, + 0x1F1B9EE6F79A8759, 0xE4FECC0FEE98B425, 0x68CE61B6B9CE68A1, 0xDEEA66C4BA8F974F, + 0x33C43D836EAFB1F5, 0xE00654042719DBD9, 0x7CF8A9F009831265, 0xFD5449A6BF174743, + 0x97DDAD33D8994B40, 0x48EAD5FC5D0BE774, 0xE3B8C8EE55B7B03C, 0x91A0226E649E42E9, + 0x900E3129E7BADD7B, 0x202A9EC5FAA3CCE8, 0x5B3402464E1C3DB6, 0x609F4E62A44C1059, + 0x20D06CD26A8FBF5C, + ]); +}); +``` + +## Configuration flags + +You can modify crate using the following configuration flags: + +- `keccak_backend`: select the specified backend. Supported values: + `aarch64_sha3`, `simd128`, `simd256`, `simd512`, `soft`. +- `keccak_soft_compact`: do not unroll loops in the software backend. + Reduces performance, but results in a more compact binary code. + +The flags can be enabled using `RUSTFLAGS` environment variable +(e.g. `RUSTFLAGS="--cfg aes_compact"`) or by modifying `.cargo/config`. ## License diff --git a/keccak/benches/mod.rs b/keccak/benches/mod.rs index df73767..ff8f470 100644 --- a/keccak/benches/mod.rs +++ b/keccak/benches/mod.rs @@ -1,45 +1,22 @@ //! keccak benchmarks #![feature(test)] -#![cfg_attr(keccak_backend = "simd", feature(portable_simd))] - -extern crate keccak; extern crate test; -use keccak::{f200, f400, f800, f1600, p1600}; +use core::hint::black_box; +use keccak::Keccak; macro_rules! impl_bench { ($name:ident, $fn:ident, $type:expr) => { #[bench] fn $name(b: &mut test::Bencher) { - let mut data = [$type; 25]; - b.iter(|| $fn(&mut data)); + let mut data = black_box([0; 25]); + Keccak::new().$fn(|f| b.iter(|| black_box(f(&mut data)))) } }; } -impl_bench!(b_f200, f200, 0u8); -impl_bench!(b_f400, f400, 0u16); -impl_bench!(b_f800, f800, 0u32); -impl_bench!(b_f1600, f1600, 0u64); - -#[bench] -fn b_p1600_24(b: &mut test::Bencher) { - let mut data = [0u64; 25]; - b.iter(|| p1600(&mut data, 24)); -} - -#[bench] -fn b_p1600_16(b: &mut test::Bencher) { - let mut data = [0u64; 25]; - b.iter(|| p1600(&mut data, 16)); -} - -#[cfg(keccak_backend = "simd")] -mod simd { - use keccak::simd::{f1600x2, f1600x4, f1600x8, u64x2, u64x4, u64x8}; - - impl_bench!(b_f1600x2, f1600x2, u64x2::splat(0)); - impl_bench!(b_f1600x4, f1600x4, u64x4::splat(0)); - impl_bench!(b_f1600x8, f1600x8, u64x8::splat(0)); -} +impl_bench!(keccak_f200, with_f200, 0u8); +impl_bench!(keccak_f400, with_f400, 0u16); +impl_bench!(keccak_f800, with_f800, 0u32); +impl_bench!(keccak_f1600, with_f1600, 0u64); diff --git a/keccak/src/backends.rs b/keccak/src/backends.rs new file mode 100644 index 0000000..02e050b --- /dev/null +++ b/keccak/src/backends.rs @@ -0,0 +1,60 @@ +//! Keccak backend implementations. +use crate::consts::F1600_ROUNDS; +use crate::types::*; +#[cfg(feature = "parallel")] +use hybrid_array::ArraySize; + +#[cfg(target_arch = "aarch64")] +pub(crate) mod aarch64_sha3; +#[cfg(any( + keccak_backend = "simd128", + keccak_backend = "simd256", + keccak_backend = "simd512", +))] +pub(crate) mod simd; +pub(crate) mod soft; + +/// Trait used to define a closure which operates over Keccak backends. +pub trait BackendClosure { + /// Execute closure with the provided backend. + fn call_once(self); +} + +/// Trait implemented by a Keccak backend. +pub trait Backend { + /// Parallelism width supported by the backend for [`State1600`]. + #[cfg(feature = "parallel")] + type ParSize1600: ArraySize; + + /// Get scalar `p1600` function with the specified number of rounds. + /// + /// # Panics + /// If `ROUNDS` is bigger than [`F1600_ROUNDS`]. + #[must_use] + fn get_p1600() -> Fn1600; + + /// Get parallel `p1600` function with the specified number of rounds. + /// + /// # Panics + /// If `ROUNDS` is bigger than [`F1600_ROUNDS`]. + #[cfg(feature = "parallel")] + #[inline] + #[must_use] + fn get_par_p1600() -> ParFn1600 { + |par_state| par_state.iter_mut().for_each(Self::get_p1600::()) + } + + /// Get scalar `f1600` function. + #[must_use] + fn get_f1600() -> Fn1600 { + Self::get_p1600::() + } + + /// Get parallel `f1600` function. + #[cfg(feature = "parallel")] + #[inline] + #[must_use] + fn get_par_f1600() -> ParFn1600 { + Self::get_par_p1600::() + } +} diff --git a/keccak/src/armv8.rs b/keccak/src/backends/aarch64_sha3.rs similarity index 67% rename from keccak/src/armv8.rs rename to keccak/src/backends/aarch64_sha3.rs index e5c4ba6..062fc9b 100644 --- a/keccak/src/armv8.rs +++ b/keccak/src/backends/aarch64_sha3.rs @@ -3,8 +3,35 @@ // TODO(tarcieri): remove when MSRV 1.87 #![allow(unsafe_op_in_unsafe_fn)] -use crate::{PLEN, RC}; +use crate::consts::{PLEN, RC}; +use crate::types::Fn1600; +#[cfg(feature = "parallel")] +use crate::types::ParFn1600; + use core::{arch::aarch64::*, array}; +#[cfg(feature = "parallel")] +use hybrid_array::{Array, typenum::U2}; + +/// AArch64 backend implemented using the SHA3 extension. +pub(crate) struct Backend; + +impl super::Backend for Backend { + #[cfg(feature = "parallel")] + type ParSize1600 = U2; + + #[inline] + fn get_p1600() -> Fn1600 { + // SAFETY: the backend is used only after required target feature checks + |state| unsafe { p1600_armv8_sha3(state, ROUNDS) } + } + + #[cfg(feature = "parallel")] + #[inline] + fn get_par_p1600() -> ParFn1600 { + // SAFETY: the backend is used only after required target feature checks + |Array(state)| unsafe { p1600_armv8_sha3_times2(state, ROUNDS) } + } +} /// Keccak-p1600 on ARMv8.4-A with `FEAT_SHA3`. /// @@ -12,7 +39,7 @@ use core::{arch::aarch64::*, array}; /// Adapted from the Keccak-f1600 implementation in the XKCP/K12. /// see #[target_feature(enable = "sha3")] -pub unsafe fn p1600_armv8_sha3(state: &mut [u64; PLEN], round_count: usize) { +unsafe fn p1600_armv8_sha3(state: &mut [u64; PLEN], round_count: usize) { let mut s = [*state, Default::default()]; // SAFETY: both functions have the same safety invariants, namely they require the `sha3` // target feature is available, and the caller is responsible for ensuring support @@ -27,11 +54,10 @@ pub unsafe fn p1600_armv8_sha3(state: &mut [u64; PLEN], round_count: usize) { /// /// #[target_feature(enable = "sha3")] -pub unsafe fn p1600_armv8_sha3_times2(state: &mut [[u64; PLEN]; 2], round_count: usize) { +unsafe fn p1600_armv8_sha3_times2(state: &mut [[u64; PLEN]; 2], round_count: usize) { assert!( - matches!(round_count, 1..=24), - "invalid round count (must be 1-24): {}", - round_count + round_count <= 24, + "invalid round count greater than 24: {round_count}", ); let mut s: [uint64x2_t; PLEN] = @@ -142,74 +168,3 @@ unsafe fn chi_iota(t: &[uint64x2_t; 25], rc: u64) -> [uint64x2_t; 25] { v19, v20, v21, v22, v23, v24, ] } - -#[cfg(all(test, target_feature = "sha3"))] -mod tests { - use super::*; - - #[test] - fn test_keccak_f1600() { - // Test vectors are copied from XKCP (eXtended Keccak Code Package) - // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-1600-IntermediateValues.txt - let state_first = [ - 0xF1258F7940E1DDE7, - 0x84D5CCF933C0478A, - 0xD598261EA65AA9EE, - 0xBD1547306F80494D, - 0x8B284E056253D057, - 0xFF97A42D7F8E6FD4, - 0x90FEE5A0A44647C4, - 0x8C5BDA0CD6192E76, - 0xAD30A6F71B19059C, - 0x30935AB7D08FFC64, - 0xEB5AA93F2317D635, - 0xA9A6E6260D712103, - 0x81A57C16DBCF555F, - 0x43B831CD0347C826, - 0x01F22F1A11A5569F, - 0x05E5635A21D9AE61, - 0x64BEFEF28CC970F2, - 0x613670957BC46611, - 0xB87C5A554FD00ECB, - 0x8C3EE88A1CCF32C8, - 0x940C7922AE3A2614, - 0x1841F924A2C509E4, - 0x16F53526E70465C2, - 0x75F644E97F30A13B, - 0xEAF1FF7B5CECA249, - ]; - let state_second = [ - 0x2D5C954DF96ECB3C, - 0x6A332CD07057B56D, - 0x093D8D1270D76B6C, - 0x8A20D9B25569D094, - 0x4F9C4F99E5E7F156, - 0xF957B9A2DA65FB38, - 0x85773DAE1275AF0D, - 0xFAF4F247C3D810F7, - 0x1F1B9EE6F79A8759, - 0xE4FECC0FEE98B425, - 0x68CE61B6B9CE68A1, - 0xDEEA66C4BA8F974F, - 0x33C43D836EAFB1F5, - 0xE00654042719DBD9, - 0x7CF8A9F009831265, - 0xFD5449A6BF174743, - 0x97DDAD33D8994B40, - 0x48EAD5FC5D0BE774, - 0xE3B8C8EE55B7B03C, - 0x91A0226E649E42E9, - 0x900E3129E7BADD7B, - 0x202A9EC5FAA3CCE8, - 0x5B3402464E1C3DB6, - 0x609F4E62A44C1059, - 0x20D06CD26A8FBF5C, - ]; - - let mut state = [0u64; 25]; - unsafe { p1600_armv8_sha3(&mut state, 24) }; - assert_eq!(state, state_first); - unsafe { p1600_armv8_sha3(&mut state, 24) }; - assert_eq!(state, state_second); - } -} diff --git a/keccak/src/backends/simd.rs b/keccak/src/backends/simd.rs new file mode 100644 index 0000000..23ec35d --- /dev/null +++ b/keccak/src/backends/simd.rs @@ -0,0 +1,59 @@ +/// Backend implementation using the portable SIMD API. +use super::soft::{LaneSize, keccak_p}; +use crate::types::{Fn1600, ParFn1600}; +use core::array; +use hybrid_array::{Array, typenum}; + +#[cfg(keccak_backend = "simd128")] +use core::simd::u64x2 as u64xN; +#[cfg(keccak_backend = "simd256")] +use core::simd::u64x4 as u64xN; +#[cfg(keccak_backend = "simd512")] +use core::simd::u64x8 as u64xN; + +impl LaneSize for u64xN { + const KECCAK_F_ROUND_COUNT: usize = crate::consts::F1600_ROUNDS; + + fn truncate_rc(rc: u64) -> Self { + Self::splat(rc) + } + + fn rotate_left(self, n: u32) -> Self { + self << Self::splat(n.into()) | self >> Self::splat((64 - n).into()) + } +} + +/// Portable SIMD backend +pub(crate) struct Backend; + +impl super::Backend for Backend { + #[cfg(all(feature = "parallel", keccak_backend = "simd128"))] + type ParSize1600 = typenum::U2; + #[cfg(all(feature = "parallel", keccak_backend = "simd256"))] + type ParSize1600 = typenum::U4; + #[cfg(all(feature = "parallel", keccak_backend = "simd512"))] + type ParSize1600 = typenum::U8; + + #[inline] + fn get_p1600() -> Fn1600 { + keccak_p:: + } + + #[cfg(feature = "parallel")] + #[inline] + fn get_par_p1600() -> ParFn1600 { + |Array(state)| { + let mut simd_state = array::from_fn(|i| { + let t = array::from_fn(|j| state[j][i]); + u64xN::from_array(t) + }); + keccak_p::<_, ROUNDS>(&mut simd_state); + for i in 0..simd_state.len() { + let s = simd_state[i].to_array(); + for j in 0..s.len() { + state[j][i] = s[j]; + } + } + } + } +} diff --git a/keccak/src/backends/soft.rs b/keccak/src/backends/soft.rs new file mode 100644 index 0000000..11b6fde --- /dev/null +++ b/keccak/src/backends/soft.rs @@ -0,0 +1,177 @@ +use crate::{consts::*, types::Fn1600}; +use core::ops::{BitAnd, BitAndAssign, BitXor, BitXorAssign, Not}; +#[cfg(feature = "parallel")] +use hybrid_array::typenum::U1; + +/// Keccak is a permutation over an array of lanes which comprise the sponge +/// construction. +pub trait LaneSize: + Copy + + Clone + + Default + + PartialEq + + BitAndAssign + + BitAnd + + BitXorAssign + + BitXor + + Not +{ + /// Number of rounds of the Keccak-f permutation. + const KECCAK_F_ROUND_COUNT: usize; + + /// Truncate function. + fn truncate_rc(rc: u64) -> Self; + + /// Rotate left function. + #[must_use] + fn rotate_left(self, n: u32) -> Self; +} + +macro_rules! impl_lanesize { + ($type:ty, $round:expr) => { + impl LaneSize for $type { + const KECCAK_F_ROUND_COUNT: usize = $round; + + #[allow(clippy::cast_possible_truncation, trivial_numeric_casts)] + fn truncate_rc(rc: u64) -> Self { + rc as Self + } + + fn rotate_left(self, n: u32) -> Self { + self.rotate_left(n) + } + } + }; +} + +impl_lanesize!(u8, F200_ROUNDS); +impl_lanesize!(u16, F400_ROUNDS); +impl_lanesize!(u32, F800_ROUNDS); +impl_lanesize!(u64, F1600_ROUNDS); + +#[rustfmt::skip] +macro_rules! unroll5 { + ($var: ident, $body: block) => { + #[cfg(not(keccak_soft_compact))] + { + { const $var: usize = 0; $body; } + { const $var: usize = 1; $body; } + { const $var: usize = 2; $body; } + { const $var: usize = 3; $body; } + { const $var: usize = 4; $body; } + } + #[cfg(keccak_soft_compact)] + { + for $var in 0..5 $body + } + }; +} + +#[rustfmt::skip] +macro_rules! unroll24 { + ($var: ident, $body: block) => { + #[cfg(not(keccak_soft_compact))] + { + { const $var: usize = 0; $body; } + { const $var: usize = 1; $body; } + { const $var: usize = 2; $body; } + { const $var: usize = 3; $body; } + { const $var: usize = 4; $body; } + { const $var: usize = 5; $body; } + { const $var: usize = 6; $body; } + { const $var: usize = 7; $body; } + { const $var: usize = 8; $body; } + { const $var: usize = 9; $body; } + { const $var: usize = 10; $body; } + { const $var: usize = 11; $body; } + { const $var: usize = 12; $body; } + { const $var: usize = 13; $body; } + { const $var: usize = 14; $body; } + { const $var: usize = 15; $body; } + { const $var: usize = 16; $body; } + { const $var: usize = 17; $body; } + { const $var: usize = 18; $body; } + { const $var: usize = 19; $body; } + { const $var: usize = 20; $body; } + { const $var: usize = 21; $body; } + { const $var: usize = 22; $body; } + { const $var: usize = 23; $body; } + } + #[cfg(keccak_soft_compact)] + { + for $var in 0..24 $body + } + }; +} + +/// Generic Keccak-p sponge function. +/// +/// # Panics +/// If the `ROUNDS` is greater than `L::KECCAK_F_ROUND_COUNT`. +#[allow(non_upper_case_globals, unused_assignments)] +pub(crate) fn keccak_p(state: &mut [L; PLEN]) { + // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=25 + // "the rounds of KECCAK-p[b, nr] match the last rounds of KECCAK-f[b]" + let round_consts = RC[..L::KECCAK_F_ROUND_COUNT] + .last_chunk::() + .expect("Number of rounds greater than `KECCAK_F_ROUND_COUNT` is not supported!") + .map(L::truncate_rc); + + // Not unrolling this loop results in a much smaller function, plus + // it positively influences performance due to the smaller load on I-cache + for rc in round_consts { + let mut array = [L::default(); 5]; + + // Theta + unroll5!(x, { + unroll5!(y, { + array[x] ^= state[5 * y + x]; + }); + }); + + unroll5!(x, { + let t1 = array[(x + 4) % 5]; + let t2 = array[(x + 1) % 5].rotate_left(1); + unroll5!(y, { + state[5 * y + x] ^= t1 ^ t2; + }); + }); + + // Rho and pi + let mut last = state[1]; + unroll24!(x, { + array[0] = state[PI[x]]; + state[PI[x]] = last.rotate_left(RHO[x]); + last = array[0]; + }); + + // Chi + unroll5!(y_step, { + let y = 5 * y_step; + + array.copy_from_slice(&state[y..][..5]); + + unroll5!(x, { + let t1 = !array[(x + 1) % 5]; + let t2 = array[(x + 2) % 5]; + state[y + x] = array[x] ^ (t1 & t2); + }); + }); + + // Iota + state[0] ^= rc; + } +} + +/// Default backend based on software implementation. +pub(crate) struct Backend; + +impl super::Backend for Backend { + #[cfg(feature = "parallel")] + type ParSize1600 = U1; + + #[inline] + fn get_p1600() -> Fn1600 { + keccak_p:: + } +} diff --git a/keccak/src/consts.rs b/keccak/src/consts.rs new file mode 100644 index 0000000..8f26b69 --- /dev/null +++ b/keccak/src/consts.rs @@ -0,0 +1,48 @@ +//! Constants used by the Keccak functions. + +/// Length of permuted state. +pub const PLEN: usize = 25; + +/// Number of rounds used by the `f200` function. +pub const F200_ROUNDS: usize = 18; +/// Number of rounds used by the `f400` function. +pub const F400_ROUNDS: usize = 20; +/// Number of rounds used by the `f800` function. +pub const F800_ROUNDS: usize = 22; +/// Number of rounds used by the `f1600` function. +pub const F1600_ROUNDS: usize = 24; + +pub(crate) 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(crate) 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(crate) const RC: [u64; 24] = [ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808a, + 0x8000000080008000, + 0x000000000000808b, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008a, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000a, + 0x000000008000808b, + 0x800000000000008b, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800a, + 0x800000008000000a, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +]; diff --git a/keccak/src/lib.rs b/keccak/src/lib.rs index e760227..349979e 100644 --- a/keccak/src/lib.rs +++ b/keccak/src/lib.rs @@ -1,561 +1,158 @@ #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] -#![cfg_attr(keccak_backend = "simd", feature(portable_simd))] +#![cfg_attr( + any( + keccak_backend = "simd128", + keccak_backend = "simd256", + keccak_backend = "simd512", + ), + feature(portable_simd) +)] #![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" )] -#![allow(non_upper_case_globals)] - -//! ## Usage -//! -//! To disable loop unrolling (e.g. for constrained targets) use `no_unroll` feature. -//! -//! ``` -//! // Test vectors are from KeccakCodePackage -//! let mut data = [0u64; 25]; -//! -//! keccak::f1600(&mut data); -//! assert_eq!(data, [ -//! 0xF1258F7940E1DDE7, 0x84D5CCF933C0478A, 0xD598261EA65AA9EE, 0xBD1547306F80494D, -//! 0x8B284E056253D057, 0xFF97A42D7F8E6FD4, 0x90FEE5A0A44647C4, 0x8C5BDA0CD6192E76, -//! 0xAD30A6F71B19059C, 0x30935AB7D08FFC64, 0xEB5AA93F2317D635, 0xA9A6E6260D712103, -//! 0x81A57C16DBCF555F, 0x43B831CD0347C826, 0x01F22F1A11A5569F, 0x05E5635A21D9AE61, -//! 0x64BEFEF28CC970F2, 0x613670957BC46611, 0xB87C5A554FD00ECB, 0x8C3EE88A1CCF32C8, -//! 0x940C7922AE3A2614, 0x1841F924A2C509E4, 0x16F53526E70465C2, 0x75F644E97F30A13B, -//! 0xEAF1FF7B5CECA249, -//! ]); -//! -//! keccak::f1600(&mut data); -//! assert_eq!(data, [ -//! 0x2D5C954DF96ECB3C, 0x6A332CD07057B56D, 0x093D8D1270D76B6C, 0x8A20D9B25569D094, -//! 0x4F9C4F99E5E7F156, 0xF957B9A2DA65FB38, 0x85773DAE1275AF0D, 0xFAF4F247C3D810F7, -//! 0x1F1B9EE6F79A8759, 0xE4FECC0FEE98B425, 0x68CE61B6B9CE68A1, 0xDEEA66C4BA8F974F, -//! 0x33C43D836EAFB1F5, 0xE00654042719DBD9, 0x7CF8A9F009831265, 0xFD5449A6BF174743, -//! 0x97DDAD33D8994B40, 0x48EAD5FC5D0BE774, 0xE3B8C8EE55B7B03C, 0x91A0226E649E42E9, -//! 0x900E3129E7BADD7B, 0x202A9EC5FAA3CCE8, 0x5B3402464E1C3DB6, 0x609F4E62A44C1059, -//! 0x20D06CD26A8FBF5C, -//! ]); -//! ``` - -use core::{ - fmt::Debug, - mem::size_of, - ops::{BitAnd, BitAndAssign, BitXor, BitXorAssign, Not}, -}; - -#[rustfmt::skip] -mod unroll; - -#[cfg(target_arch = "aarch64")] -mod armv8; #[cfg(target_arch = "aarch64")] cpufeatures::new!(armv8_sha3_intrinsics, "sha3"); -const PLEN: usize = 25; +pub mod backends; +pub mod consts; +pub mod types; -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, -]; - -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, -]; - -const RC: [u64; 24] = [ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808a, - 0x8000000080008000, - 0x000000000000808b, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008a, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000a, - 0x000000008000808b, - 0x800000000000008b, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800a, - 0x800000008000000a, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, -]; - -/// Keccak is a permutation over an array of lanes which comprise the sponge -/// construction. -pub trait LaneSize: - Copy - + Clone - + Debug - + Default - + PartialEq - + BitAndAssign - + BitAnd - + BitXorAssign - + BitXor - + Not -{ - /// Number of rounds of the Keccak-f permutation. - const KECCAK_F_ROUND_COUNT: usize; - - /// Truncate function. - fn truncate_rc(rc: u64) -> Self; - - /// Rotate left function. - #[must_use] - fn rotate_left(self, n: u32) -> Self; -} - -macro_rules! impl_lanesize { - ($type:ty, $round:expr, $truncate:expr) => { - impl LaneSize for $type { - const KECCAK_F_ROUND_COUNT: usize = $round; - - fn truncate_rc(rc: u64) -> Self { - $truncate(rc) - } - - fn rotate_left(self, n: u32) -> Self { - self.rotate_left(n) - } - } - }; -} - -impl_lanesize!(u8, 18, |rc: u64| { rc.to_le_bytes()[0] }); -impl_lanesize!(u16, 20, |rc: u64| { - let tmp = rc.to_le_bytes(); - #[allow(clippy::unwrap_used)] - Self::from_le_bytes(tmp[..size_of::()].try_into().unwrap()) -}); -impl_lanesize!(u32, 22, |rc: u64| { - let tmp = rc.to_le_bytes(); - #[allow(clippy::unwrap_used)] - Self::from_le_bytes(tmp[..size_of::()].try_into().unwrap()) -}); -impl_lanesize!(u64, 24, |rc: u64| { rc }); - -macro_rules! impl_keccak { - ($pname:ident, $fname:ident, $type:ty) => { - /// Keccak-p sponge function - pub fn $pname(state: &mut [$type; PLEN], round_count: usize) { - keccak_p(state, round_count); - } +pub use backends::*; +pub use consts::*; +pub use types::*; - /// Keccak-f sponge function - pub fn $fname(state: &mut [$type; PLEN]) { - keccak_p(state, <$type>::KECCAK_F_ROUND_COUNT); - } - }; -} - -impl_keccak!(p200, f200, u8); -impl_keccak!(p400, f400, u16); -impl_keccak!(p800, f800, u32); -impl_keccak!(p1600, f1600, u64); - -/// Keccak permutation with `b=1600` state (i.e. for Keccak-p1600/Keccak-f1600) with optional CPU -/// feature detection support. -#[derive(Clone, Debug)] -pub struct KeccakP1600 { - state: [u64; PLEN], +/// Struct which handles switching between available backends. +#[derive(Debug, Copy, Clone)] +pub struct Keccak { #[cfg(target_arch = "aarch64")] - has_intrinsics: armv8_sha3_intrinsics::InitToken, + armv8_sha3: armv8_sha3_intrinsics::InitToken, } -impl KeccakP1600 { - /// Create a new 1600-bit Keccak state from the given input array. +impl Default for Keccak { #[inline] - #[must_use] - pub fn new(state: [u64; PLEN]) -> Self { + fn default() -> Self { Self { - state, #[cfg(target_arch = "aarch64")] - has_intrinsics: armv8_sha3_intrinsics::init(), + armv8_sha3: armv8_sha3_intrinsics::init(), } } +} - /// `Keccak-p[1600, rc]` permutation. - pub fn p1600(&mut self, round_count: usize) { - #[cfg(target_arch = "aarch64")] - if self.has_intrinsics.get() { - // SAFETY: we just performed runtime CPU feature detection above - unsafe { armv8::p1600_armv8_sha3(&mut self.state, round_count) } - return; - } - - p1600(&mut self.state, round_count); +impl Keccak { + /// Create new Keccak backend. + #[inline] + #[must_use] + pub fn new() -> Self { + Self::default() } - /// `Keccak-f[1600]` permutation. - pub fn f1600(&mut self) { + /// Execute the provided backend closure with Keccak backend. + #[inline] + // The auto-detection code will not be reached if `keccak_backend` is set. + #[allow(unreachable_code)] + pub fn with_backend(&self, f: impl BackendClosure) { + cfg_if::cfg_if!( + if #[cfg(any( + keccak_backend = "simd128", + keccak_backend = "simd256", + keccak_backend = "simd512", + ))] { + return f.call_once::() + } else if #[cfg(keccak_backend = "aarch64_sha3")] { + #[cfg(not(target_arch = "aarch64"))] + compile_error!("aarch64_sha3 backend can be used only on AArch64 targets!"); + #[cfg(not(target_feature = "sha3"))] + compile_error!("aarch64_sha3 backend requires sha3 target feature to be enabled!"); + + return f.call_once::() + } else if #[cfg(keccak_backend = "soft")] { + return f.call_once::() + } + ); + #[cfg(target_arch = "aarch64")] - if self.has_intrinsics.get() { - // SAFETY: we just performed runtime CPU feature detection above - unsafe { armv8::p1600_armv8_sha3(&mut self.state, u64::KECCAK_F_ROUND_COUNT) } - return; + if self.armv8_sha3.get() { + #[target_feature(enable = "sha3")] + unsafe fn aarch64_sha3_inner(f: impl BackendClosure) { + f.call_once::(); + } + // SAFETY: we checked target feature availability above + return unsafe { aarch64_sha3_inner(f) }; } - f1600(&mut self.state); + f.call_once::(); } - /// Extract the state array. - #[must_use] - pub fn into_inner(self) -> [u64; PLEN] { - self.state + /// Execute the closure with `f200` function. + #[inline] + pub fn with_f200(&self, f: impl FnOnce(Fn200)) { + self.with_p200::(f); } -} -impl AsRef<[u64; PLEN]> for KeccakP1600 { + /// Execute the closure with `f400` function. #[inline] - fn as_ref(&self) -> &[u64; PLEN] { - &self.state + pub fn with_f400(&self, f: impl FnOnce(Fn400)) { + self.with_p400::(f); } -} -impl AsMut<[u64; PLEN]> for KeccakP1600 { + /// Execute the closure with `f800` function. #[inline] - fn as_mut(&mut self) -> &mut [u64; PLEN] { - &mut self.state + pub fn with_f800(&self, f: impl FnOnce(Fn800)) { + self.with_p800::(f); } -} -impl Default for KeccakP1600 { - fn default() -> Self { - Self::new(Default::default()) + /// Execute the closure with `f1600` function. + #[inline] + pub fn with_f1600(&self, f: impl FnOnce(Fn1600)) { + self.with_p1600::(f); } -} -impl From<[u64; PLEN]> for KeccakP1600 { + /// Execute the closure with `p200` function with the specified number of rounds. + /// + /// # Panics + /// If `ROUNDS` is bigger than [`F200_ROUNDS`]. #[inline] - fn from(state: [u64; PLEN]) -> Self { - Self::new(state) + pub fn with_p200(&self, f: impl FnOnce(Fn200)) { + f(soft::keccak_p::); } -} -impl From<&[u64; PLEN]> for KeccakP1600 { + /// Execute the closure with `p200` function with the specified number of rounds. + /// + /// # Panics + /// If `ROUNDS` is bigger than [`F400_ROUNDS`]. #[inline] - fn from(state: &[u64; PLEN]) -> Self { - Self::new(*state) + pub fn with_p400(&self, f: impl FnOnce(Fn400)) { + f(soft::keccak_p::); } -} -impl From for [u64; PLEN] { + /// Execute the closure with `p800` function with the specified number of rounds. + /// + /// # Panics + /// If `ROUNDS` is bigger than [`F800_ROUNDS`]. #[inline] - fn from(keccak: KeccakP1600) -> Self { - keccak.into_inner() + pub fn with_p800(&self, f: impl FnOnce(Fn800)) { + f(soft::keccak_p::); } -} - -#[cfg(keccak_backend = "simd")] -/// SIMD implementations for Keccak-f1600 sponge function -pub mod simd { - use crate::{LaneSize, PLEN, keccak_p}; - pub use core::simd::{u64x2, u64x4, u64x8}; - macro_rules! impl_lanesize_simd_u64xn { - ($type:ty) => { - impl LaneSize for $type { - const KECCAK_F_ROUND_COUNT: usize = 24; - - fn truncate_rc(rc: u64) -> Self { - Self::splat(rc) - } + /// Execute the closure with `p1600` function with the specified number of rounds. + /// + /// # Panics + /// If `ROUNDS` is bigger than [`F1600_ROUNDS`]. + #[inline] + pub fn with_p1600(&self, f: impl FnOnce(Fn1600)) { + struct Closure(F); - fn rotate_left(self, n: u32) -> Self { - self << Self::splat(n.into()) | self >> Self::splat((64 - n).into()) - } + impl BackendClosure for Closure { + #[inline(always)] + fn call_once(self) { + (self.0)(B::get_p1600::()); } - }; - } - - impl_lanesize_simd_u64xn!(u64x2); - impl_lanesize_simd_u64xn!(u64x4); - impl_lanesize_simd_u64xn!(u64x8); - - impl_keccak!(p1600x2, f1600x2, u64x2); - impl_keccak!(p1600x4, f1600x4, u64x4); - impl_keccak!(p1600x8, f1600x8, u64x8); -} - -/// Generic Keccak-p sponge function. -/// -/// # Panics -/// If the round count is greater than `L::KECCAK_F_ROUND_COUNT`. -#[allow(unused_assignments)] -pub fn keccak_p(state: &mut [L; PLEN], round_count: usize) { - assert!( - round_count <= L::KECCAK_F_ROUND_COUNT, - "A round_count greater than KECCAK_F_ROUND_COUNT is not supported!" - ); - - // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=25 - // "the rounds of KECCAK-p[b, nr] match the last rounds of KECCAK-f[b]" - let round_consts = &RC[(L::KECCAK_F_ROUND_COUNT - round_count)..L::KECCAK_F_ROUND_COUNT]; - - // not unrolling this loop results in a much smaller function, plus - // it positively influences performance due to the smaller load on I-cache - for &rc in round_consts { - let mut array = [L::default(); 5]; - - // Theta - unroll5!(x, { - unroll5!(y, { - array[x] ^= state[5 * y + x]; - }); - }); - - unroll5!(x, { - let t1 = array[(x + 4) % 5]; - let t2 = array[(x + 1) % 5].rotate_left(1); - unroll5!(y, { - state[5 * y + x] ^= t1 ^ t2; - }); - }); - - // Rho and pi - let mut last = state[1]; - unroll24!(x, { - array[0] = state[PI[x]]; - state[PI[x]] = last.rotate_left(RHO[x]); - last = array[0]; - }); - - // Chi - unroll5!(y_step, { - let y = 5 * y_step; - - array.copy_from_slice(&state[y..][..5]); - - unroll5!(x, { - let t1 = !array[(x + 1) % 5]; - let t2 = array[(x + 2) % 5]; - state[y + x] = array[x] ^ (t1 & t2); - }); - }); - - // Iota - state[0] ^= L::truncate_rc(rc); - } -} - -#[cfg(test)] -mod tests { - use crate::{KeccakP1600, LaneSize, PLEN, keccak_p}; - - fn keccak_f(state_first: [L; PLEN], state_second: [L; PLEN]) { - let mut state = [L::default(); PLEN]; - - keccak_p(&mut state, L::KECCAK_F_ROUND_COUNT); - assert_eq!(state, state_first); - - keccak_p(&mut state, L::KECCAK_F_ROUND_COUNT); - assert_eq!(state, state_second); - } - - #[test] - fn keccak_f200() { - // Test vectors are copied from XKCP (eXtended Keccak Code Package) - // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-200-IntermediateValues.txt - let state_first = [ - 0x3C, 0x28, 0x26, 0x84, 0x1C, 0xB3, 0x5C, 0x17, 0x1E, 0xAA, 0xE9, 0xB8, 0x11, 0x13, - 0x4C, 0xEA, 0xA3, 0x85, 0x2C, 0x69, 0xD2, 0xC5, 0xAB, 0xAF, 0xEA, - ]; - let state_second = [ - 0x1B, 0xEF, 0x68, 0x94, 0x92, 0xA8, 0xA5, 0x43, 0xA5, 0x99, 0x9F, 0xDB, 0x83, 0x4E, - 0x31, 0x66, 0xA1, 0x4B, 0xE8, 0x27, 0xD9, 0x50, 0x40, 0x47, 0x9E, - ]; - - keccak_f::(state_first, state_second); - } - - #[test] - fn keccak_f400() { - // Test vectors are copied from XKCP (eXtended Keccak Code Package) - // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-400-IntermediateValues.txt - let state_first = [ - 0x09F5, 0x40AC, 0x0FA9, 0x14F5, 0xE89F, 0xECA0, 0x5BD1, 0x7870, 0xEFF0, 0xBF8F, 0x0337, - 0x6052, 0xDC75, 0x0EC9, 0xE776, 0x5246, 0x59A1, 0x5D81, 0x6D95, 0x6E14, 0x633E, 0x58EE, - 0x71FF, 0x714C, 0xB38E, - ]; - let state_second = [ - 0xE537, 0xD5D6, 0xDBE7, 0xAAF3, 0x9BC7, 0xCA7D, 0x86B2, 0xFDEC, 0x692C, 0x4E5B, 0x67B1, - 0x15AD, 0xA7F7, 0xA66F, 0x67FF, 0x3F8A, 0x2F99, 0xE2C2, 0x656B, 0x5F31, 0x5BA6, 0xCA29, - 0xC224, 0xB85C, 0x097C, - ]; - - keccak_f::(state_first, state_second); - } - - #[test] - fn keccak_f800() { - // Test vectors are copied from XKCP (eXtended Keccak Code Package) - // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-800-IntermediateValues.txt - let state_first = [ - 0xE531D45D, 0xF404C6FB, 0x23A0BF99, 0xF1F8452F, 0x51FFD042, 0xE539F578, 0xF00B80A7, - 0xAF973664, 0xBF5AF34C, 0x227A2424, 0x88172715, 0x9F685884, 0xB15CD054, 0x1BF4FC0E, - 0x6166FA91, 0x1A9E599A, 0xA3970A1F, 0xAB659687, 0xAFAB8D68, 0xE74B1015, 0x34001A98, - 0x4119EFF3, 0x930A0E76, 0x87B28070, 0x11EFE996, - ]; - let state_second = [ - 0x75BF2D0D, 0x9B610E89, 0xC826AF40, 0x64CD84AB, 0xF905BDD6, 0xBC832835, 0x5F8001B9, - 0x15662CCE, 0x8E38C95E, 0x701FE543, 0x1B544380, 0x89ACDEFF, 0x51EDB5DE, 0x0E9702D9, - 0x6C19AA16, 0xA2913EEE, 0x60754E9A, 0x9819063C, 0xF4709254, 0xD09F9084, 0x772DA259, - 0x1DB35DF7, 0x5AA60162, 0x358825D5, 0xB3783BAB, - ]; - - keccak_f::(state_first, state_second); - } - - #[test] - fn keccak_f1600() { - // Test vectors are copied from XKCP (eXtended Keccak Code Package) - // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-1600-IntermediateValues.txt - let state_first = [ - 0xF1258F7940E1DDE7, - 0x84D5CCF933C0478A, - 0xD598261EA65AA9EE, - 0xBD1547306F80494D, - 0x8B284E056253D057, - 0xFF97A42D7F8E6FD4, - 0x90FEE5A0A44647C4, - 0x8C5BDA0CD6192E76, - 0xAD30A6F71B19059C, - 0x30935AB7D08FFC64, - 0xEB5AA93F2317D635, - 0xA9A6E6260D712103, - 0x81A57C16DBCF555F, - 0x43B831CD0347C826, - 0x01F22F1A11A5569F, - 0x05E5635A21D9AE61, - 0x64BEFEF28CC970F2, - 0x613670957BC46611, - 0xB87C5A554FD00ECB, - 0x8C3EE88A1CCF32C8, - 0x940C7922AE3A2614, - 0x1841F924A2C509E4, - 0x16F53526E70465C2, - 0x75F644E97F30A13B, - 0xEAF1FF7B5CECA249, - ]; - let state_second = [ - 0x2D5C954DF96ECB3C, - 0x6A332CD07057B56D, - 0x093D8D1270D76B6C, - 0x8A20D9B25569D094, - 0x4F9C4F99E5E7F156, - 0xF957B9A2DA65FB38, - 0x85773DAE1275AF0D, - 0xFAF4F247C3D810F7, - 0x1F1B9EE6F79A8759, - 0xE4FECC0FEE98B425, - 0x68CE61B6B9CE68A1, - 0xDEEA66C4BA8F974F, - 0x33C43D836EAFB1F5, - 0xE00654042719DBD9, - 0x7CF8A9F009831265, - 0xFD5449A6BF174743, - 0x97DDAD33D8994B40, - 0x48EAD5FC5D0BE774, - 0xE3B8C8EE55B7B03C, - 0x91A0226E649E42E9, - 0x900E3129E7BADD7B, - 0x202A9EC5FAA3CCE8, - 0x5B3402464E1C3DB6, - 0x609F4E62A44C1059, - 0x20D06CD26A8FBF5C, - ]; - - keccak_f::(state_first, state_second); - - let mut keccak = KeccakP1600::new(state_first); - keccak.f1600(); - assert_eq!(keccak.into_inner(), state_second); - } - - #[cfg(keccak_backend = "simd")] - mod simd { - use super::keccak_f; - use core::simd::{u64x2, u64x4, u64x8}; - - macro_rules! impl_keccak_f1600xn { - ($name:ident, $type:ty) => { - #[test] - fn $name() { - // Test vectors are copied from XKCP (eXtended Keccak Code Package) - // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-1600-IntermediateValues.txt - let state_first = [ - <$type>::splat(0xF1258F7940E1DDE7), - <$type>::splat(0x84D5CCF933C0478A), - <$type>::splat(0xD598261EA65AA9EE), - <$type>::splat(0xBD1547306F80494D), - <$type>::splat(0x8B284E056253D057), - <$type>::splat(0xFF97A42D7F8E6FD4), - <$type>::splat(0x90FEE5A0A44647C4), - <$type>::splat(0x8C5BDA0CD6192E76), - <$type>::splat(0xAD30A6F71B19059C), - <$type>::splat(0x30935AB7D08FFC64), - <$type>::splat(0xEB5AA93F2317D635), - <$type>::splat(0xA9A6E6260D712103), - <$type>::splat(0x81A57C16DBCF555F), - <$type>::splat(0x43B831CD0347C826), - <$type>::splat(0x01F22F1A11A5569F), - <$type>::splat(0x05E5635A21D9AE61), - <$type>::splat(0x64BEFEF28CC970F2), - <$type>::splat(0x613670957BC46611), - <$type>::splat(0xB87C5A554FD00ECB), - <$type>::splat(0x8C3EE88A1CCF32C8), - <$type>::splat(0x940C7922AE3A2614), - <$type>::splat(0x1841F924A2C509E4), - <$type>::splat(0x16F53526E70465C2), - <$type>::splat(0x75F644E97F30A13B), - <$type>::splat(0xEAF1FF7B5CECA249), - ]; - let state_second = [ - <$type>::splat(0x2D5C954DF96ECB3C), - <$type>::splat(0x6A332CD07057B56D), - <$type>::splat(0x093D8D1270D76B6C), - <$type>::splat(0x8A20D9B25569D094), - <$type>::splat(0x4F9C4F99E5E7F156), - <$type>::splat(0xF957B9A2DA65FB38), - <$type>::splat(0x85773DAE1275AF0D), - <$type>::splat(0xFAF4F247C3D810F7), - <$type>::splat(0x1F1B9EE6F79A8759), - <$type>::splat(0xE4FECC0FEE98B425), - <$type>::splat(0x68CE61B6B9CE68A1), - <$type>::splat(0xDEEA66C4BA8F974F), - <$type>::splat(0x33C43D836EAFB1F5), - <$type>::splat(0xE00654042719DBD9), - <$type>::splat(0x7CF8A9F009831265), - <$type>::splat(0xFD5449A6BF174743), - <$type>::splat(0x97DDAD33D8994B40), - <$type>::splat(0x48EAD5FC5D0BE774), - <$type>::splat(0xE3B8C8EE55B7B03C), - <$type>::splat(0x91A0226E649E42E9), - <$type>::splat(0x900E3129E7BADD7B), - <$type>::splat(0x202A9EC5FAA3CCE8), - <$type>::splat(0x5B3402464E1C3DB6), - <$type>::splat(0x609F4E62A44C1059), - <$type>::splat(0x20D06CD26A8FBF5C), - ]; - - keccak_f::<$type>(state_first, state_second); - } - }; } - impl_keccak_f1600xn!(keccak_f1600x2, u64x2); - impl_keccak_f1600xn!(keccak_f1600x4, u64x4); - impl_keccak_f1600xn!(keccak_f1600x8, u64x8); + self.with_backend(Closure::(f)); } } diff --git a/keccak/src/types.rs b/keccak/src/types.rs new file mode 100644 index 0000000..6cc6f55 --- /dev/null +++ b/keccak/src/types.rs @@ -0,0 +1,35 @@ +//! Helper type aliases. +use crate::PLEN; + +/// 200-bit Keccak state. +pub type State200 = [u8; PLEN]; +/// 400-bit Keccak state. +pub type State400 = [u16; PLEN]; +/// 800-bit Keccak state. +pub type State800 = [u32; PLEN]; +/// 1600-bit Keccak state. +pub type State1600 = [u64; PLEN]; + +/// A Keccak function which permutates [`State200`]. +pub type Fn200 = fn(&mut State200); +/// A Keccak function which permutates [`State400`]. +pub type Fn400 = fn(&mut State400); +/// A Keccak function which permutates [`State800`]. +pub type Fn800 = fn(&mut State800); +/// A Keccak function which permutates [`State1600`]. +pub type Fn1600 = fn(&mut State1600); + +#[cfg(feature = "parallel")] +mod parallel { + use super::State1600; + use crate::Backend; + use hybrid_array::Array; + + /// 1600xN-bit state processed in parallel by a [`Backend`] implementation. + pub type ParState1600 = Array::ParSize1600>; + /// A Keccak function which permutates [`ParState1600`]. + pub type ParFn1600 = fn(&mut ParState1600); +} + +#[cfg(feature = "parallel")] +pub use parallel::*; diff --git a/keccak/src/unroll.rs b/keccak/src/unroll.rs deleted file mode 100644 index bd0eae6..0000000 --- a/keccak/src/unroll.rs +++ /dev/null @@ -1,62 +0,0 @@ -/// unroll5 -#[cfg(not(keccak_backend = "soft-compact"))] -#[macro_export] -macro_rules! unroll5 { - ($var:ident, $body:block) => { - { const $var: usize = 0; $body; } - { const $var: usize = 1; $body; } - { const $var: usize = 2; $body; } - { const $var: usize = 3; $body; } - { const $var: usize = 4; $body; } - }; -} - -/// unroll5 -#[cfg(keccak_backend = "soft-compact")] -#[macro_export] -macro_rules! unroll5 { - ($var:ident, $body:block) => { - for $var in 0..5 $body - } -} - -/// unroll24 -#[cfg(not(keccak_backend = "soft-compact"))] -#[macro_export] -macro_rules! unroll24 { - ($var: ident, $body: block) => { - { const $var: usize = 0; $body; } - { const $var: usize = 1; $body; } - { const $var: usize = 2; $body; } - { const $var: usize = 3; $body; } - { const $var: usize = 4; $body; } - { const $var: usize = 5; $body; } - { const $var: usize = 6; $body; } - { const $var: usize = 7; $body; } - { const $var: usize = 8; $body; } - { const $var: usize = 9; $body; } - { const $var: usize = 10; $body; } - { const $var: usize = 11; $body; } - { const $var: usize = 12; $body; } - { const $var: usize = 13; $body; } - { const $var: usize = 14; $body; } - { const $var: usize = 15; $body; } - { const $var: usize = 16; $body; } - { const $var: usize = 17; $body; } - { const $var: usize = 18; $body; } - { const $var: usize = 19; $body; } - { const $var: usize = 20; $body; } - { const $var: usize = 21; $body; } - { const $var: usize = 22; $body; } - { const $var: usize = 23; $body; } - }; -} - -/// unroll24 -#[cfg(keccak_backend = "soft-compact")] -#[macro_export] -macro_rules! unroll24 { - ($var:ident, $body:block) => { - for $var in 0..24 $body - } -} diff --git a/keccak/tests/parallel.rs b/keccak/tests/parallel.rs new file mode 100644 index 0000000..387550f --- /dev/null +++ b/keccak/tests/parallel.rs @@ -0,0 +1,39 @@ +//! Tests for the `parallel` crate feature +#![cfg(feature = "parallel")] +use core::array; +use hybrid_array::Array; +use keccak::{Backend, BackendClosure, State1600}; + +const N: usize = 50; + +/// Test that we get the same result for scalar and parallel functions. +fn test_fn() { + let f1600 = B::get_f1600(); + let par_f1600 = B::get_par_f1600(); + + let mut buf: [State1600; N] = array::from_fn(|i| array::from_fn(|_| i as u64)); + let expected: [State1600; N] = buf.map(|mut s| { + f1600(&mut s); + s + }); + + let (chunks, tail) = Array::slice_as_chunks_mut(&mut buf[..]); + + chunks.iter_mut().for_each(par_f1600); + tail.iter_mut().for_each(f1600); + + assert_eq!(buf, expected); +} + +#[test] +fn keccak_par_f1600() { + struct Closure; + + impl BackendClosure for Closure { + fn call_once(self) { + test_fn::(); + } + } + + keccak::Keccak::new().with_backend(Closure); +} diff --git a/keccak/tests/xkcp.rs b/keccak/tests/xkcp.rs new file mode 100644 index 0000000..c40c058 --- /dev/null +++ b/keccak/tests/xkcp.rs @@ -0,0 +1,140 @@ +//! Test vectors are copied from [XKCP] (eXtended Keccak Code Package). +//! +//! [XKCP]: https://github.com/XKCP/XKCP/blob/master/tests/TestVectors +use keccak::{Keccak, PLEN}; + +/// Test vector from KeccakF-200-IntermediateValues.txt +#[test] +fn keccak_f200() { + let before = [ + 0x3C, 0x28, 0x26, 0x84, 0x1C, 0xB3, 0x5C, 0x17, 0x1E, 0xAA, 0xE9, 0xB8, 0x11, 0x13, 0x4C, + 0xEA, 0xA3, 0x85, 0x2C, 0x69, 0xD2, 0xC5, 0xAB, 0xAF, 0xEA, + ]; + let after = [ + 0x1B, 0xEF, 0x68, 0x94, 0x92, 0xA8, 0xA5, 0x43, 0xA5, 0x99, 0x9F, 0xDB, 0x83, 0x4E, 0x31, + 0x66, 0xA1, 0x4B, 0xE8, 0x27, 0xD9, 0x50, 0x40, 0x47, 0x9E, + ]; + + Keccak::new().with_f200(|f200| { + let mut buf = [0; PLEN]; + f200(&mut buf); + assert_eq!(buf, before); + f200(&mut buf); + assert_eq!(buf, after); + }); +} + +/// Test vector from KeccakF-400-IntermediateValues.txt +#[test] +fn keccak_f400() { + let before = [ + 0x09F5, 0x40AC, 0x0FA9, 0x14F5, 0xE89F, 0xECA0, 0x5BD1, 0x7870, 0xEFF0, 0xBF8F, 0x0337, + 0x6052, 0xDC75, 0x0EC9, 0xE776, 0x5246, 0x59A1, 0x5D81, 0x6D95, 0x6E14, 0x633E, 0x58EE, + 0x71FF, 0x714C, 0xB38E, + ]; + let after = [ + 0xE537, 0xD5D6, 0xDBE7, 0xAAF3, 0x9BC7, 0xCA7D, 0x86B2, 0xFDEC, 0x692C, 0x4E5B, 0x67B1, + 0x15AD, 0xA7F7, 0xA66F, 0x67FF, 0x3F8A, 0x2F99, 0xE2C2, 0x656B, 0x5F31, 0x5BA6, 0xCA29, + 0xC224, 0xB85C, 0x097C, + ]; + + Keccak::new().with_f400(|f400| { + let mut buf = [0; PLEN]; + f400(&mut buf); + assert_eq!(buf, before); + f400(&mut buf); + assert_eq!(buf, after); + }); +} + +/// Test vector from KeccakF-800-IntermediateValues.txt +#[test] +fn keccak_f800() { + let before = [ + 0xE531D45D, 0xF404C6FB, 0x23A0BF99, 0xF1F8452F, 0x51FFD042, 0xE539F578, 0xF00B80A7, + 0xAF973664, 0xBF5AF34C, 0x227A2424, 0x88172715, 0x9F685884, 0xB15CD054, 0x1BF4FC0E, + 0x6166FA91, 0x1A9E599A, 0xA3970A1F, 0xAB659687, 0xAFAB8D68, 0xE74B1015, 0x34001A98, + 0x4119EFF3, 0x930A0E76, 0x87B28070, 0x11EFE996, + ]; + let after = [ + 0x75BF2D0D, 0x9B610E89, 0xC826AF40, 0x64CD84AB, 0xF905BDD6, 0xBC832835, 0x5F8001B9, + 0x15662CCE, 0x8E38C95E, 0x701FE543, 0x1B544380, 0x89ACDEFF, 0x51EDB5DE, 0x0E9702D9, + 0x6C19AA16, 0xA2913EEE, 0x60754E9A, 0x9819063C, 0xF4709254, 0xD09F9084, 0x772DA259, + 0x1DB35DF7, 0x5AA60162, 0x358825D5, 0xB3783BAB, + ]; + + Keccak::new().with_f800(|f800| { + let mut buf = [0; PLEN]; + f800(&mut buf); + assert_eq!(buf, before); + f800(&mut buf); + assert_eq!(buf, after); + }); +} + +/// Test vector from KeccakF-1600-IntermediateValues.txt +#[test] +fn keccak_f1600() { + let before = [ + 0xF1258F7940E1DDE7, + 0x84D5CCF933C0478A, + 0xD598261EA65AA9EE, + 0xBD1547306F80494D, + 0x8B284E056253D057, + 0xFF97A42D7F8E6FD4, + 0x90FEE5A0A44647C4, + 0x8C5BDA0CD6192E76, + 0xAD30A6F71B19059C, + 0x30935AB7D08FFC64, + 0xEB5AA93F2317D635, + 0xA9A6E6260D712103, + 0x81A57C16DBCF555F, + 0x43B831CD0347C826, + 0x01F22F1A11A5569F, + 0x05E5635A21D9AE61, + 0x64BEFEF28CC970F2, + 0x613670957BC46611, + 0xB87C5A554FD00ECB, + 0x8C3EE88A1CCF32C8, + 0x940C7922AE3A2614, + 0x1841F924A2C509E4, + 0x16F53526E70465C2, + 0x75F644E97F30A13B, + 0xEAF1FF7B5CECA249, + ]; + + let after = [ + 0x2D5C954DF96ECB3C, + 0x6A332CD07057B56D, + 0x093D8D1270D76B6C, + 0x8A20D9B25569D094, + 0x4F9C4F99E5E7F156, + 0xF957B9A2DA65FB38, + 0x85773DAE1275AF0D, + 0xFAF4F247C3D810F7, + 0x1F1B9EE6F79A8759, + 0xE4FECC0FEE98B425, + 0x68CE61B6B9CE68A1, + 0xDEEA66C4BA8F974F, + 0x33C43D836EAFB1F5, + 0xE00654042719DBD9, + 0x7CF8A9F009831265, + 0xFD5449A6BF174743, + 0x97DDAD33D8994B40, + 0x48EAD5FC5D0BE774, + 0xE3B8C8EE55B7B03C, + 0x91A0226E649E42E9, + 0x900E3129E7BADD7B, + 0x202A9EC5FAA3CCE8, + 0x5B3402464E1C3DB6, + 0x609F4E62A44C1059, + 0x20D06CD26A8FBF5C, + ]; + Keccak::new().with_f1600(|f1600| { + let mut buf = [0; PLEN]; + f1600(&mut buf); + assert_eq!(buf, before); + f1600(&mut buf); + assert_eq!(buf, after); + }); +}