Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
#![cfg(any(feature = "cipher", feature = "rng"))]
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(chacha20_backend = "soft")] {
pub(crate) mod soft;
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
cfg_if! {
if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
pub(crate) mod avx512;
// AVX-2 backend needed for RNG if enabled
#[cfg(feature = "rng")]
pub(crate) mod avx2;
} else if #[cfg(chacha20_backend = "avx2")] {
pub(crate) mod avx2;
} else if #[cfg(chacha20_backend = "sse2")] {
pub(crate) mod sse2;
} else {
pub(crate) mod soft;
#[cfg(chacha20_avx512)]
pub(crate) mod avx512;
pub(crate) mod avx2;
pub(crate) mod sse2;
}
}
} else if #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] {
pub(crate) mod neon;
} else {
pub(crate) mod soft;
}
}
+354
View File
@@ -0,0 +1,354 @@
//! AVX2 backend.
#![allow(unsafe_op_in_unsafe_fn, reason = "needs triage")]
#![allow(clippy::cast_possible_truncation, reason = "needs triage")]
#![allow(clippy::cast_possible_wrap, reason = "needs triage")]
#![allow(clippy::cast_sign_loss, reason = "needs triage")]
#![allow(clippy::undocumented_unsafe_blocks, reason = "TODO")]
use crate::{Rounds, Variant};
use core::marker::PhantomData;
#[cfg(feature = "rng")]
use crate::ChaChaCore;
#[cfg(feature = "cipher")]
use crate::{STATE_WORDS, chacha::Block};
#[cfg(feature = "cipher")]
use cipher::{
BlockSizeUser, ParBlocks, ParBlocksSizeUser, StreamCipherBackend, StreamCipherClosure,
consts::{U4, U64},
};
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
/// Number of blocks processed in parallel.
const PAR_BLOCKS: usize = 4;
/// Number of `__m256i` to store parallel blocks.
const N: usize = PAR_BLOCKS / 2;
#[inline]
#[target_feature(enable = "avx2")]
#[cfg(feature = "cipher")]
#[cfg_attr(chacha20_backend = "avx512", expect(unused))]
pub(crate) unsafe fn inner<R, F, V>(state: &mut [u32; STATE_WORDS], f: F)
where
R: Rounds,
F: StreamCipherClosure<BlockSize = U64>,
V: Variant,
{
let state_ptr = state.as_ptr().cast::<__m128i>();
let v = [
_mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(0))),
_mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(1))),
_mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(2))),
];
let mut c = _mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(3)));
c = match size_of::<V::Counter>() {
4 => _mm256_add_epi32(c, _mm256_set_epi32(0, 0, 0, 1, 0, 0, 0, 0)),
8 => _mm256_add_epi64(c, _mm256_set_epi64x(0, 1, 0, 0)),
_ => unreachable!(),
};
let mut ctr = [c; N];
for i in 0..N {
ctr[i] = c;
c = match size_of::<V::Counter>() {
4 => _mm256_add_epi32(c, _mm256_set_epi32(0, 0, 0, 2, 0, 0, 0, 2)),
8 => _mm256_add_epi64(c, _mm256_set_epi64x(0, 2, 0, 2)),
_ => unreachable!(),
};
}
let mut backend = Backend::<R, V> {
v,
ctr,
_pd: PhantomData,
};
f.call(&mut backend);
state[12] = _mm256_extract_epi32(backend.ctr[0], 0) as u32;
match size_of::<V::Counter>() {
4 => {}
8 => state[13] = _mm256_extract_epi32(backend.ctr[0], 1) as u32,
_ => unreachable!(),
}
}
#[inline]
#[target_feature(enable = "avx2")]
#[cfg(feature = "rng")]
pub(crate) unsafe fn rng_inner<R, V>(core: &mut ChaChaCore<R, V>, buffer: &mut [u32; 64])
where
R: Rounds,
V: Variant,
{
let state_ptr = core.state.as_ptr().cast::<__m128i>();
let v = [
_mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(0))),
_mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(1))),
_mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(2))),
];
let mut c = _mm256_broadcastsi128_si256(_mm_loadu_si128(state_ptr.add(3)));
c = _mm256_add_epi64(c, _mm256_set_epi64x(0, 1, 0, 0));
let mut ctr = [c; N];
for i in 0..N {
ctr[i] = c;
c = _mm256_add_epi64(c, _mm256_set_epi64x(0, 2, 0, 2));
}
let mut backend = Backend::<R, V> {
v,
ctr,
_pd: PhantomData,
};
backend.rng_gen_par_ks_blocks(buffer);
core.state[12] = _mm256_extract_epi32(backend.ctr[0], 0) as u32;
core.state[13] = _mm256_extract_epi32(backend.ctr[0], 1) as u32;
}
struct Backend<R: Rounds, V: Variant> {
v: [__m256i; 3],
ctr: [__m256i; N],
_pd: PhantomData<(R, V)>,
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> BlockSizeUser for Backend<R, V> {
type BlockSize = U64;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> ParBlocksSizeUser for Backend<R, V> {
type ParBlocksSize = U4;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherBackend for Backend<R, V> {
#[inline(always)]
fn gen_ks_block(&mut self, block: &mut Block) {
unsafe {
let res = rounds::<R>(&self.v, &self.ctr);
for c in self.ctr.iter_mut() {
*c = match size_of::<V::Counter>() {
4 => _mm256_add_epi32(*c, _mm256_set_epi32(0, 0, 0, 1, 0, 0, 0, 1)),
8 => _mm256_add_epi64(*c, _mm256_set_epi64x(0, 1, 0, 1)),
_ => unreachable!(),
};
}
let res0: [__m128i; 8] = core::mem::transmute(res[0]);
let block_ptr = block.as_mut_ptr().cast::<__m128i>();
for i in 0..4 {
_mm_storeu_si128(block_ptr.add(i), res0[2 * i]);
}
}
}
#[inline(always)]
fn gen_par_ks_blocks(&mut self, blocks: &mut ParBlocks<Self>) {
unsafe {
let vs = rounds::<R>(&self.v, &self.ctr);
let pb = PAR_BLOCKS as i32;
for c in self.ctr.iter_mut() {
*c = match size_of::<V::Counter>() {
4 => _mm256_add_epi32(*c, _mm256_set_epi32(0, 0, 0, pb, 0, 0, 0, pb)),
8 => {
_mm256_add_epi64(*c, _mm256_set_epi64x(0, i64::from(pb), 0, i64::from(pb)))
}
_ => unreachable!(),
}
}
let mut block_ptr = blocks.as_mut_ptr().cast::<__m128i>();
for v in vs {
let t: [__m128i; 8] = core::mem::transmute(v);
for i in 0..4 {
_mm_storeu_si128(block_ptr.add(i), t[2 * i]);
_mm_storeu_si128(block_ptr.add(4 + i), t[2 * i + 1]);
}
block_ptr = block_ptr.add(8);
}
}
}
}
#[cfg(feature = "rng")]
impl<R: Rounds, V: Variant> Backend<R, V> {
#[inline(always)]
fn rng_gen_par_ks_blocks(&mut self, blocks: &mut [u32; 64]) {
unsafe {
let vs = rounds::<R>(&self.v, &self.ctr);
let pb = PAR_BLOCKS as i32;
for c in self.ctr.iter_mut() {
*c = _mm256_add_epi64(*c, _mm256_set_epi64x(0, i64::from(pb), 0, i64::from(pb)));
}
let mut block_ptr = blocks.as_mut_ptr().cast::<__m128i>();
for v in vs {
let t: [__m128i; 8] = core::mem::transmute(v);
for i in 0..4 {
_mm_storeu_si128(block_ptr.add(i), t[2 * i]);
_mm_storeu_si128(block_ptr.add(4 + i), t[2 * i + 1]);
}
block_ptr = block_ptr.add(8);
}
}
}
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn rounds<R: Rounds>(v: &[__m256i; 3], c: &[__m256i; N]) -> [[__m256i; 4]; N] {
let mut vs: [[__m256i; 4]; N] = [[_mm256_setzero_si256(); 4]; N];
for i in 0..N {
vs[i] = [v[0], v[1], v[2], c[i]];
}
for _ in 0..R::COUNT {
double_quarter_round(&mut vs);
}
for i in 0..N {
for j in 0..3 {
vs[i][j] = _mm256_add_epi32(vs[i][j], v[j]);
}
vs[i][3] = _mm256_add_epi32(vs[i][3], c[i]);
}
vs
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn double_quarter_round(v: &mut [[__m256i; 4]; N]) {
add_xor_rot(v);
rows_to_cols(v);
add_xor_rot(v);
cols_to_rows(v);
}
/// The goal of this function is to transform the state words from:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c0, c1, c2, c3] [ 8, 9, 10, 11]
/// [d0, d1, d2, d3] [12, 13, 14, 15]
/// ```
///
/// to:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b1, b2, b3, b0] == [ 5, 6, 7, 4]
/// [c2, c3, c0, c1] [10, 11, 8, 9]
/// [d3, d0, d1, d2] [15, 12, 13, 14]
/// ```
///
/// so that we can apply [`add_xor_rot`] to the resulting columns, and have it compute the
/// "diagonal rounds" (as defined in RFC 7539) in parallel. In practice, this shuffle is
/// non-optimal: the last state word to be altered in `add_xor_rot` is `b`, so the shuffle
/// blocks on the result of `b` being calculated.
///
/// We can optimize this by observing that the four quarter rounds in `add_xor_rot` are
/// data-independent: they only access a single column of the state, and thus the order of
/// the columns does not matter. We therefore instead shuffle the other three state words,
/// to obtain the following equivalent layout:
/// ```text
/// [a3, a0, a1, a2] [ 3, 0, 1, 2]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c1, c2, c3, c0] [ 9, 10, 11, 8]
/// [d2, d3, d0, d1] [14, 15, 12, 13]
/// ```
///
/// See https://github.com/sneves/blake2-avx2/pull/4 for additional details. The earliest
/// known occurrence of this optimization is in floodyberry's SSE4 ChaCha code from 2014:
/// - https://github.com/floodyberry/chacha-opt/blob/0ab65cb99f5016633b652edebaf3691ceb4ff753/chacha_blocks_ssse3-64.S#L639-L643
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn rows_to_cols(vs: &mut [[__m256i; 4]; N]) {
// c >>>= 32; d >>>= 64; a >>>= 96;
for [a, _, c, d] in vs {
*c = _mm256_shuffle_epi32(*c, 0b_00_11_10_01); // _MM_SHUFFLE(0, 3, 2, 1)
*d = _mm256_shuffle_epi32(*d, 0b_01_00_11_10); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm256_shuffle_epi32(*a, 0b_10_01_00_11); // _MM_SHUFFLE(2, 1, 0, 3)
}
}
/// The goal of this function is to transform the state words from:
/// ```text
/// [a3, a0, a1, a2] [ 3, 0, 1, 2]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c1, c2, c3, c0] [ 9, 10, 11, 8]
/// [d2, d3, d0, d1] [14, 15, 12, 13]
/// ```
///
/// to:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c0, c1, c2, c3] [ 8, 9, 10, 11]
/// [d0, d1, d2, d3] [12, 13, 14, 15]
/// ```
///
/// reversing the transformation of [`rows_to_cols`].
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn cols_to_rows(vs: &mut [[__m256i; 4]; N]) {
// c <<<= 32; d <<<= 64; a <<<= 96;
for [a, _, c, d] in vs {
*c = _mm256_shuffle_epi32(*c, 0b_10_01_00_11); // _MM_SHUFFLE(2, 1, 0, 3)
*d = _mm256_shuffle_epi32(*d, 0b_01_00_11_10); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm256_shuffle_epi32(*a, 0b_00_11_10_01); // _MM_SHUFFLE(0, 3, 2, 1)
}
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn add_xor_rot(vs: &mut [[__m256i; 4]; N]) {
let rol16_mask = _mm256_set_epi64x(
0x0d0c_0f0e_0908_0b0a,
0x0504_0706_0100_0302,
0x0d0c_0f0e_0908_0b0a,
0x0504_0706_0100_0302,
);
let rol8_mask = _mm256_set_epi64x(
0x0e0d_0c0f_0a09_080b,
0x0605_0407_0201_0003,
0x0e0d_0c0f_0a09_080b,
0x0605_0407_0201_0003,
);
// a += b; d ^= a; d <<<= (16, 16, 16, 16);
for [a, b, _, d] in vs.iter_mut() {
*a = _mm256_add_epi32(*a, *b);
*d = _mm256_xor_si256(*d, *a);
*d = _mm256_shuffle_epi8(*d, rol16_mask);
}
// c += d; b ^= c; b <<<= (12, 12, 12, 12);
for [_, b, c, d] in vs.iter_mut() {
*c = _mm256_add_epi32(*c, *d);
*b = _mm256_xor_si256(*b, *c);
*b = _mm256_xor_si256(_mm256_slli_epi32(*b, 12), _mm256_srli_epi32(*b, 20));
}
// a += b; d ^= a; d <<<= (8, 8, 8, 8);
for [a, b, _, d] in vs.iter_mut() {
*a = _mm256_add_epi32(*a, *b);
*d = _mm256_xor_si256(*d, *a);
*d = _mm256_shuffle_epi8(*d, rol8_mask);
}
// c += d; b ^= c; b <<<= (7, 7, 7, 7);
for [_, b, c, d] in vs.iter_mut() {
*c = _mm256_add_epi32(*c, *d);
*b = _mm256_xor_si256(*b, *c);
*b = _mm256_xor_si256(_mm256_slli_epi32(*b, 7), _mm256_srli_epi32(*b, 25));
}
}
+625
View File
@@ -0,0 +1,625 @@
#![allow(unsafe_op_in_unsafe_fn)]
use crate::{Rounds, Variant};
use core::marker::PhantomData;
#[cfg(feature = "cipher")]
use crate::{STATE_WORDS, chacha::Block};
#[cfg(feature = "cipher")]
use cipher::{
BlockSizeUser, ParBlocks, ParBlocksSizeUser, StreamCipherBackend, StreamCipherClosure,
consts::{U16, U64},
};
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
/// Maximum number of blocks processed in parallel.
/// We also support 8 and 4 in gen_tail_blocks.
const MAX_PAR_BLOCKS: usize = 16;
/// Divisor to compute `N`, the number of __m512i needed
/// to represent a number of parallel blocks.
const BLOCKS_PER_VECTOR: usize = 4;
const MAX_N: usize = MAX_PAR_BLOCKS / BLOCKS_PER_VECTOR;
#[inline]
#[target_feature(enable = "avx512f")]
#[cfg(feature = "cipher")]
pub(crate) unsafe fn inner<R, F, V>(state: &mut [u32; STATE_WORDS], f: F)
where
R: Rounds,
F: StreamCipherClosure<BlockSize = U64>,
V: Variant,
{
let simd_state = state.as_mut_ptr().cast::<i32>();
let mut backend = Backend::<R, V> {
state: [
_mm_loadu_epi32(simd_state),
_mm_loadu_epi32(simd_state.add(4)),
_mm_loadu_epi32(simd_state.add(8)),
],
ctr: _mm_loadu_epi32(simd_state.add(12)),
_pd: PhantomData,
};
f.call(&mut backend);
// Update counter in the persistent state
state[12] = _mm_extract_epi32::<0>(backend.ctr) as u32;
if size_of::<V::Counter>() == 8 {
state[13] = _mm_extract_epi32::<1>(backend.ctr) as u32;
}
}
struct Backend<R: Rounds, V: Variant> {
state: [__m128i; 3],
ctr: __m128i,
_pd: PhantomData<(R, V)>,
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> Backend<R, V> {
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn increment_ctr(&mut self, amount: usize) {
match size_of::<V::Counter>() {
4 => {
self.ctr = _mm_add_epi32(self.ctr, _mm_set_epi32(0, 0, 0, amount as i32));
}
8 => {
self.ctr = _mm_add_epi64(self.ctr, _mm_set_epi64x(0, amount as i64));
}
_ => unreachable!(),
}
}
/// Generates blocks using the 512-bit-wide dispatch
/// with up to `N` vectors processed in parallel, producing
/// `N * BLOCKS_PER_VECTOR` blocks.
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn gen_blocks_fullwidth<const N: usize>(&mut self, blocks: &mut [Block]) {
let par_blocks = N * BLOCKS_PER_VECTOR;
assert!(blocks.len() <= par_blocks);
let mut ctrs = [_mm512_broadcast_i32x4(self.ctr); N];
for i in 0..ctrs.len() {
match size_of::<V::Counter>() {
4 => {
ctrs[i] = _mm512_add_epi32(
ctrs[i],
_mm512_set_epi32(
0,
0,
0,
(i * BLOCKS_PER_VECTOR + 3) as i32,
0,
0,
0,
(i * BLOCKS_PER_VECTOR + 2) as i32,
0,
0,
0,
(i * BLOCKS_PER_VECTOR + 1) as i32,
0,
0,
0,
(i * BLOCKS_PER_VECTOR) as i32,
),
);
}
8 => {
ctrs[i] = _mm512_add_epi64(
ctrs[i],
_mm512_set_epi64(
0,
(i * BLOCKS_PER_VECTOR + 3) as i64,
0,
(i * BLOCKS_PER_VECTOR + 2) as i64,
0,
(i * BLOCKS_PER_VECTOR + 1) as i64,
0,
(i * BLOCKS_PER_VECTOR) as i64,
),
);
}
_ => unreachable!(),
}
}
self.increment_ctr(blocks.len());
let result = rounds::<N, R>(&self.state.map(|v| _mm512_broadcast_i32x4(v)), &ctrs);
for i in 0..N {
let result_vectors = result[i];
// We have our data in SIMD vectors in the following layout
// (using a, b, c, and d to indicate the resp. 4 rows of each block,
// and Bn to denote the nth block):
// result_vectors[0]:
// B0a0 B0a1 B0a2 B0a3
// B1a0 B1a1 B1a2 B1a3
// ...
// B3a0 B3a1 B3a2 B3a2
//
// result_vectors[1]:
// B0b0 B0b1 B0b2 B0b3
// B1b0 B1b1 B1b2 B1b3
// ...
// B3b0 B3b1 B3b2 B3b2
//
// and so on for result_vectors[2] (storing c values) and result_vectors[3] (storing d values).
//
// To store to memory, we need to transpose to the following format:
// transposed[0]:
// B0a0 B0a1 B0a2 B0a3
// B0b0 B0b1 B0b2 B0b3
// B0c0 B0c1 B0c2 B0c3
// B0d0 B0d1 B0d2 B0d3
//
// and so on, such that each 512-bit SIMD vector
// contains a single contiguous block.
//
// We achieve this transposition using the following
// sequence of shuffles.
let temp_abab_block01 = _mm512_permutex2var_epi64(
result_vectors[0],
_mm512_setr_epi64(0, 1, 8, 9, 2, 3, 10, 11),
result_vectors[1],
);
let temp_abab_block23 = _mm512_permutex2var_epi64(
result_vectors[0],
_mm512_setr_epi64(4, 5, 12, 13, 6, 7, 14, 15),
result_vectors[1],
);
let temp_cdcd_block01 = _mm512_permutex2var_epi64(
result_vectors[2],
_mm512_setr_epi64(0, 1, 8, 9, 2, 3, 10, 11),
result_vectors[3],
);
let temp_cdcd_block23 = _mm512_permutex2var_epi64(
result_vectors[2],
_mm512_setr_epi64(4, 5, 12, 13, 6, 7, 14, 15),
result_vectors[3],
);
let block0 =
_mm512_shuffle_i32x4::<0b01_00_01_00>(temp_abab_block01, temp_cdcd_block01);
let block1 =
_mm512_shuffle_i32x4::<0b11_10_11_10>(temp_abab_block01, temp_cdcd_block01);
let block2 =
_mm512_shuffle_i32x4::<0b01_00_01_00>(temp_abab_block23, temp_cdcd_block23);
let block3 =
_mm512_shuffle_i32x4::<0b11_10_11_10>(temp_abab_block23, temp_cdcd_block23);
for (j, src_block) in [block0, block1, block2, block3].into_iter().enumerate() {
let dst_index = i * BLOCKS_PER_VECTOR + j;
if dst_index < blocks.len() {
_mm512_storeu_si512((&raw mut blocks[dst_index]).cast(), src_block);
}
}
}
}
/// Generates up to 2 blocks using 256-bit vectors.
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn gen_blocks_halfwidth(&mut self, blocks: &mut [Block]) {
assert!(blocks.len() <= 2);
let mut ctr = _mm256_broadcast_i32x4(self.ctr);
match size_of::<V::Counter>() {
4 => {
ctr = _mm256_add_epi32(ctr, _mm256_set_epi32(0, 0, 0, 1, 0, 0, 0, 0));
}
8 => {
ctr = _mm256_add_epi64(ctr, _mm256_set_epi64x(0, 1, 0, 0));
}
_ => unreachable!(),
}
self.increment_ctr(blocks.len());
let block_vectors = rounds_halfwide::<R>([
_mm256_broadcast_i32x4(self.state[0]),
_mm256_broadcast_i32x4(self.state[1]),
_mm256_broadcast_i32x4(self.state[2]),
ctr,
]);
// Similar transpose operation as
// in gen_blocks_fullwidth.
let block0_ab = _mm256_permutex2var_epi64(
block_vectors[0],
_mm256_setr_epi64x(0, 1, 4, 5),
block_vectors[1],
);
let block0_cd = _mm256_permutex2var_epi64(
block_vectors[2],
_mm256_setr_epi64x(0, 1, 4, 5),
block_vectors[3],
);
let block1_ab = _mm256_permutex2var_epi64(
block_vectors[0],
_mm256_setr_epi64x(2, 3, 6, 7),
block_vectors[1],
);
let block1_cd = _mm256_permutex2var_epi64(
block_vectors[2],
_mm256_setr_epi64x(2, 3, 6, 7),
block_vectors[3],
);
for (i, (block_part_ab, block_part_cd)) in [(block0_ab, block0_cd), (block1_ab, block1_cd)]
.into_iter()
.enumerate()
{
if i < blocks.len() {
let dst = (&raw mut blocks[i]).cast::<i32>();
_mm256_storeu_epi32(dst, block_part_ab);
_mm256_storeu_epi32(
dst.add(size_of::<Block>() / 2 / size_of::<i32>()),
block_part_cd,
);
}
}
}
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> BlockSizeUser for Backend<R, V> {
type BlockSize = U64;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> ParBlocksSizeUser for Backend<R, V> {
type ParBlocksSize = U16;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherBackend for Backend<R, V> {
#[inline]
fn gen_par_ks_blocks(&mut self, blocks: &mut ParBlocks<Self>) {
unsafe { self.gen_blocks_fullwidth::<MAX_N>(blocks) }
}
#[inline(always)]
fn gen_ks_block(&mut self, block: &mut Block) {
// Fallback for generating a single block using quarter-width vectors
// (128).
unsafe {
let state = [self.state[0], self.state[1], self.state[2], self.ctr];
self.increment_ctr(1);
let result = rounds_quarterwide::<R>(state);
for row in 0..4 {
let dst = block.as_mut_ptr().cast::<i32>().add(row * 4);
_mm_storeu_epi32(dst, result[row]);
}
}
}
#[inline]
fn gen_tail_blocks(&mut self, blocks: &mut [cipher::Block<Self>]) {
assert!(blocks.len() < MAX_PAR_BLOCKS);
if blocks.is_empty() {
return;
}
// Fallback for generating a number of blocks less than
// MAX_PAR_BLOCKS.
unsafe {
if blocks.len() == 1 {
self.gen_ks_block(&mut blocks[0]);
} else if blocks.len() == 2 {
self.gen_blocks_halfwidth(blocks);
} else if blocks.len() <= 4 {
self.gen_blocks_fullwidth::<1>(blocks);
} else if blocks.len() <= 8 {
self.gen_blocks_fullwidth::<2>(blocks);
} else {
self.gen_blocks_fullwidth::<MAX_N>(blocks);
}
}
}
}
#[inline]
#[target_feature(enable = "avx512f")]
unsafe fn rounds<const N: usize, R: Rounds>(
v: &[__m512i; 3],
c: &[__m512i; N],
) -> [[__m512i; 4]; N] {
let mut vs: [[__m512i; 4]; N] = [[_mm512_setzero_si512(); 4]; N];
for i in 0..N {
vs[i] = [v[0], v[1], v[2], c[i]];
}
for _ in 0..R::COUNT {
double_quarter_round(&mut vs);
}
for i in 0..N {
for j in 0..3 {
vs[i][j] = _mm512_add_epi32(vs[i][j], v[j]);
}
vs[i][3] = _mm512_add_epi32(vs[i][3], c[i]);
}
vs
}
#[inline]
#[target_feature(enable = "avx512f")]
unsafe fn double_quarter_round<const N: usize>(v: &mut [[__m512i; 4]; N]) {
add_xor_rot(v);
rows_to_cols(v);
add_xor_rot(v);
cols_to_rows(v);
}
/// The goal of this function is to transform the state words from:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c0, c1, c2, c3] [ 8, 9, 10, 11]
/// [d0, d1, d2, d3] [12, 13, 14, 15]
/// ```
///
/// to:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b1, b2, b3, b0] == [ 5, 6, 7, 4]
/// [c2, c3, c0, c1] [10, 11, 8, 9]
/// [d3, d0, d1, d2] [15, 12, 13, 14]
/// ```
///
/// so that we can apply [`add_xor_rot`] to the resulting columns, and have it compute the
/// "diagonal rounds" (as defined in RFC 7539) in parallel. In practice, this shuffle is
/// non-optimal: the last state word to be altered in `add_xor_rot` is `b`, so the shuffle
/// blocks on the result of `b` being calculated.
///
/// We can optimize this by observing that the four quarter rounds in `add_xor_rot` are
/// data-independent: they only access a single column of the state, and thus the order of
/// the columns does not matter. We therefore instead shuffle the other three state words,
/// to obtain the following equivalent layout:
/// ```text
/// [a3, a0, a1, a2] [ 3, 0, 1, 2]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c1, c2, c3, c0] [ 9, 10, 11, 8]
/// [d2, d3, d0, d1] [14, 15, 12, 13]
/// ```
///
/// See https://github.com/sneves/blake2-avx2/pull/4 for additional details. The earliest
/// known occurrence of this optimization is in floodyberry's SSE4 ChaCha code from 2014:
/// - https://github.com/floodyberry/chacha-opt/blob/0ab65cb99f5016633b652edebaf3691ceb4ff753/chacha_blocks_ssse3-64.S#L639-L643
#[inline]
#[target_feature(enable = "avx512f")]
unsafe fn rows_to_cols<const N: usize>(vs: &mut [[__m512i; 4]; N]) {
// c >>>= 32; d >>>= 64; a >>>= 96;
for [a, _, c, d] in vs {
*c = _mm512_shuffle_epi32::<0b_00_11_10_01>(*c); // _MM_SHUFFLE(0, 3, 2, 1)
*d = _mm512_shuffle_epi32::<0b_01_00_11_10>(*d); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm512_shuffle_epi32::<0b_10_01_00_11>(*a); // _MM_SHUFFLE(2, 1, 0, 3)
}
}
/// The goal of this function is to transform the state words from:
/// ```text
/// [a3, a0, a1, a2] [ 3, 0, 1, 2]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c1, c2, c3, c0] [ 9, 10, 11, 8]
/// [d2, d3, d0, d1] [14, 15, 12, 13]
/// ```
///
/// to:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c0, c1, c2, c3] [ 8, 9, 10, 11]
/// [d0, d1, d2, d3] [12, 13, 14, 15]
/// ```
///
/// reversing the transformation of [`rows_to_cols`].
#[inline]
#[target_feature(enable = "avx512f")]
unsafe fn cols_to_rows<const N: usize>(vs: &mut [[__m512i; 4]; N]) {
// c <<<= 32; d <<<= 64; a <<<= 96;
for [a, _, c, d] in vs {
*c = _mm512_shuffle_epi32::<0b_10_01_00_11>(*c); // _MM_SHUFFLE(2, 1, 0, 3)
*d = _mm512_shuffle_epi32::<0b_01_00_11_10>(*d); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm512_shuffle_epi32::<0b_00_11_10_01>(*a); // _MM_SHUFFLE(0, 3, 2, 1)
}
}
#[inline]
#[target_feature(enable = "avx512f")]
unsafe fn add_xor_rot<const N: usize>(vs: &mut [[__m512i; 4]; N]) {
// a += b; d ^= a; d <<<= (16, 16, 16, 16);
for [a, b, _, d] in vs.iter_mut() {
*a = _mm512_add_epi32(*a, *b);
*d = _mm512_xor_si512(*d, *a);
*d = _mm512_rol_epi32::<16>(*d);
}
// c += d; b ^= c; b <<<= (12, 12, 12, 12);
for [_, b, c, d] in vs.iter_mut() {
*c = _mm512_add_epi32(*c, *d);
*b = _mm512_xor_si512(*b, *c);
*b = _mm512_rol_epi32::<12>(*b);
}
// a += b; d ^= a; d <<<= (8, 8, 8, 8);
for [a, b, _, d] in vs.iter_mut() {
*a = _mm512_add_epi32(*a, *b);
*d = _mm512_xor_si512(*d, *a);
*d = _mm512_rol_epi32::<8>(*d);
}
// c += d; b ^= c; b <<<= (7, 7, 7, 7);
for [_, b, c, d] in vs.iter_mut() {
*c = _mm512_add_epi32(*c, *d);
*b = _mm512_xor_si512(*b, *c);
*b = _mm512_rol_epi32::<7>(*b);
}
}
// Below is another implementation of the round application
// that uses 256-bit vectors instead of 512-bit (but, unlike
// the avx2 module, can use new AVX-512 instructions like rotates).
// It is used for tail processing of shorter outputs,
// since 256-bit instructions can be faster and lower latency
// than 512-bit instructions on certain microarchitectures (e.g. Zen 4).
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn rounds_halfwide<R: Rounds>(v_in: [__m256i; 4]) -> [__m256i; 4] {
let mut v = v_in;
for _ in 0..R::COUNT {
double_quarter_round_halfwide(&mut v);
}
for (a, b) in v.iter_mut().zip(v_in) {
*a = _mm256_add_epi32(*a, b);
}
v
}
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn double_quarter_round_halfwide(v: &mut [__m256i; 4]) {
add_xor_rot_halfwide(v);
rows_to_cols_halfwide(v);
add_xor_rot_halfwide(v);
cols_to_rows_halfwide(v);
}
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn rows_to_cols_halfwide(v: &mut [__m256i; 4]) {
// c >>>= 32; d >>>= 64; a >>>= 96;
let [a, _, c, d] = v;
*c = _mm256_shuffle_epi32::<0b_00_11_10_01>(*c); // _MM_SHUFFLE(0, 3, 2, 1)
*d = _mm256_shuffle_epi32::<0b_01_00_11_10>(*d); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm256_shuffle_epi32::<0b_10_01_00_11>(*a); // _MM_SHUFFLE(2, 1, 0, 3)
}
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn cols_to_rows_halfwide(v: &mut [__m256i; 4]) {
// c <<<= 32; d <<<= 64; a <<<= 96;
let [a, _, c, d] = v;
*c = _mm256_shuffle_epi32::<0b_10_01_00_11>(*c); // _MM_SHUFFLE(2, 1, 0, 3)
*d = _mm256_shuffle_epi32::<0b_01_00_11_10>(*d); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm256_shuffle_epi32::<0b_00_11_10_01>(*a); // _MM_SHUFFLE(0, 3, 2, 1)
}
#[inline]
#[target_feature(enable = "avx512f", enable = "avx512vl")]
unsafe fn add_xor_rot_halfwide(v: &mut [__m256i; 4]) {
let [a, b, c, d] = v;
// a += b; d ^= a; d <<<= (16, 16, 16, 16);
*a = _mm256_add_epi32(*a, *b);
*d = _mm256_xor_si256(*d, *a);
*d = _mm256_rol_epi32::<16>(*d);
// c += d; b ^= c; b <<<= (12, 12, 12, 12);
*c = _mm256_add_epi32(*c, *d);
*b = _mm256_xor_si256(*b, *c);
*b = _mm256_rol_epi32::<12>(*b);
// a += b; d ^= a; d <<<= (8, 8, 8, 8);
*a = _mm256_add_epi32(*a, *b);
*d = _mm256_xor_si256(*d, *a);
*d = _mm256_rol_epi32::<8>(*d);
// c += d; b ^= c; b <<<= (7, 7, 7, 7);
*c = _mm256_add_epi32(*c, *d);
*b = _mm256_xor_si256(*b, *c);
*b = _mm256_rol_epi32::<7>(*b);
}
// Finally, below is an implementation using 128-bit vectors
// for the case of generating a single block.
#[inline(always)]
unsafe fn rounds_quarterwide<R: Rounds>(v_in: [__m128i; 4]) -> [__m128i; 4] {
let mut v = v_in;
for _ in 0..R::COUNT {
double_quarter_round_quarterwide(&mut v);
}
for (a, b) in v.iter_mut().zip(v_in) {
*a = _mm_add_epi32(*a, b);
}
v
}
#[inline(always)]
unsafe fn double_quarter_round_quarterwide(v: &mut [__m128i; 4]) {
add_xor_rot_quarterwide(v);
rows_to_cols_quarterwide(v);
add_xor_rot_quarterwide(v);
cols_to_rows_quarterwide(v);
}
#[inline(always)]
unsafe fn rows_to_cols_quarterwide(v: &mut [__m128i; 4]) {
// c >>>= 32; d >>>= 64; a >>>= 96;
let [a, _, c, d] = v;
*c = _mm_shuffle_epi32::<0b_00_11_10_01>(*c); // _MM_SHUFFLE(0, 3, 2, 1)
*d = _mm_shuffle_epi32::<0b_01_00_11_10>(*d); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm_shuffle_epi32::<0b_10_01_00_11>(*a); // _MM_SHUFFLE(2, 1, 0, 3)
}
#[inline(always)]
unsafe fn cols_to_rows_quarterwide(v: &mut [__m128i; 4]) {
// c <<<= 32; d <<<= 64; a <<<= 96;
let [a, _, c, d] = v;
*c = _mm_shuffle_epi32::<0b_10_01_00_11>(*c); // _MM_SHUFFLE(2, 1, 0, 3)
*d = _mm_shuffle_epi32::<0b_01_00_11_10>(*d); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm_shuffle_epi32::<0b_00_11_10_01>(*a); // _MM_SHUFFLE(0, 3, 2, 1)
}
#[inline(always)]
unsafe fn add_xor_rot_quarterwide(v: &mut [__m128i; 4]) {
let [a, b, c, d] = v;
// a += b; d ^= a; d <<<= (16, 16, 16, 16);
*a = _mm_add_epi32(*a, *b);
*d = _mm_xor_si128(*d, *a);
*d = _mm_rol_epi32::<16>(*d);
// c += d; b ^= c; b <<<= (12, 12, 12, 12);
*c = _mm_add_epi32(*c, *d);
*b = _mm_xor_si128(*b, *c);
*b = _mm_rol_epi32::<12>(*b);
// a += b; d ^= a; d <<<= (8, 8, 8, 8);
*a = _mm_add_epi32(*a, *b);
*d = _mm_xor_si128(*d, *a);
*d = _mm_rol_epi32::<8>(*d);
// c += d; b ^= c; b <<<= (7, 7, 7, 7);
*c = _mm_add_epi32(*c, *d);
*b = _mm_xor_si128(*b, *c);
*b = _mm_rol_epi32::<7>(*b);
}
+364
View File
@@ -0,0 +1,364 @@
//! NEON-optimized implementation for aarch64 CPUs.
//!
//! Adapted from the Crypto++ `chacha_simd` implementation by Jack Lloyd and
//! Jeffrey Walton (public domain), but uses **8-block** outer batches for
//! bulk paths (see `ChaCha20_512_neon` in OpenSSL).
#![allow(unsafe_op_in_unsafe_fn, reason = "needs triage")]
use crate::{Rounds, STATE_WORDS, Variant};
use core::{arch::aarch64::*, marker::PhantomData};
#[cfg(feature = "rng")]
use crate::ChaChaCore;
#[cfg(feature = "cipher")]
use crate::chacha::Block;
#[cfg(feature = "cipher")]
use cipher::{
BlockSizeUser, ParBlocks, ParBlocksSizeUser, StreamCipherBackend, StreamCipherClosure,
consts::{U8, U64},
};
struct Backend<R: Rounds, V: Variant> {
state: [uint32x4_t; 4],
ctrs: [uint32x4_t; 8],
_pd: PhantomData<(R, V)>,
}
macro_rules! add_counter {
($a:expr, $b:expr, $variant:ty) => {
match size_of::<<$variant>::Counter>() {
4 => vaddq_u32($a, $b),
8 => vreinterpretq_u32_u64(vaddq_u64(
vreinterpretq_u64_u32($a),
vreinterpretq_u64_u32($b),
)),
_ => unreachable!(),
}
};
}
impl<R: Rounds, V: Variant> Backend<R, V> {
#[inline]
unsafe fn new(state: &mut [u32; STATE_WORDS]) -> Self {
let state = [
vld1q_u32(state.as_ptr().offset(0)),
vld1q_u32(state.as_ptr().offset(4)),
vld1q_u32(state.as_ptr().offset(8)),
vld1q_u32(state.as_ptr().offset(12)),
];
let ctrs = [
vld1q_u32([1, 0, 0, 0].as_ptr()),
vld1q_u32([2, 0, 0, 0].as_ptr()),
vld1q_u32([3, 0, 0, 0].as_ptr()),
vld1q_u32([4, 0, 0, 0].as_ptr()),
vld1q_u32([5, 0, 0, 0].as_ptr()),
vld1q_u32([6, 0, 0, 0].as_ptr()),
vld1q_u32([7, 0, 0, 0].as_ptr()),
vld1q_u32([8, 0, 0, 0].as_ptr()),
];
Backend::<R, V> {
state,
ctrs,
_pd: PhantomData,
}
}
}
#[inline]
#[cfg(feature = "cipher")]
#[target_feature(enable = "neon")]
pub(crate) unsafe fn inner<R, F, V>(state: &mut [u32; STATE_WORDS], f: F)
where
R: Rounds,
F: StreamCipherClosure<BlockSize = U64>,
V: Variant,
{
let mut backend = Backend::<R, V>::new(state);
f.call(&mut backend);
match size_of::<V::Counter>() {
4 => state[12] = vgetq_lane_u32(backend.state[3], 0),
8 => vst1q_u64(
state.as_mut_ptr().offset(12).cast::<u64>(),
vreinterpretq_u64_u32(backend.state[3]),
),
_ => unreachable!(),
}
}
/// Sets up backend and blindly writes 4 blocks to dest_ptr.
#[cfg(feature = "rng")]
#[inline]
#[target_feature(enable = "neon")]
pub(crate) unsafe fn rng_inner<R, V>(core: &mut ChaChaCore<R, V>, buffer: &mut [u32; 64])
where
R: Rounds,
V: Variant,
{
let mut backend = Backend::<R, V>::new(&mut core.state);
backend.write_par_ks_blocks(buffer);
vst1q_u64(
core.state.as_mut_ptr().offset(12).cast::<u64>(),
vreinterpretq_u64_u32(backend.state[3]),
);
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> BlockSizeUser for Backend<R, V> {
type BlockSize = U64;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> ParBlocksSizeUser for Backend<R, V> {
/// Match OpenSSL's large-buffer path: 8 blocks
type ParBlocksSize = U8;
}
/// Evaluates to `a = a + b`, where the operands are u32x4s
macro_rules! add_assign_vec {
($a:expr, $b:expr) => {
$a = vaddq_u32($a, $b)
};
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherBackend for Backend<R, V> {
#[inline(always)]
fn gen_ks_block(&mut self, block: &mut Block) {
let state3 = self.state[3];
let mut par = ParBlocks::<Self>::default();
self.gen_par_ks_blocks(&mut par);
*block = par[0];
// SAFETY: we have used conditional compilation to ensure NEON is available
unsafe {
self.state[3] = add_counter!(state3, vld1q_u32([1, 0, 0, 0].as_ptr()), V);
}
}
#[inline(always)]
fn gen_par_ks_blocks(&mut self, dest: &mut ParBlocks<Self>) {
// SAFETY: we have used conditional compilation to ensure NEON is available
unsafe {
let mut blocks = [
[self.state[0], self.state[1], self.state[2], self.state[3]],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[0], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[1], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[2], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[3], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[4], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[5], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[6], V),
],
];
for _ in 0..R::COUNT {
double_quarter_round(&mut blocks);
}
for block in 0..8 {
// add state to block
for state_row in 0..3 {
add_assign_vec!(blocks[block][state_row], self.state[state_row]);
}
if block > 0 {
add_assign_vec!(
blocks[block][3],
add_counter!(self.state[3], self.ctrs[block - 1], V)
);
} else {
add_assign_vec!(blocks[block][3], self.state[3]);
}
// write blocks to dest
for state_row in 0..4 {
vst1q_u8(
dest[block].as_mut_ptr().add(state_row << 4),
vreinterpretq_u8_u32(blocks[block][state_row]),
);
}
}
self.state[3] = add_counter!(self.state[3], self.ctrs[7], V);
}
}
}
macro_rules! rotate_left {
($v:expr, 8) => {{
let maskb = [3u8, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14];
let mask = vld1q_u8(maskb.as_ptr());
$v = vreinterpretq_u32_u8(vqtbl1q_u8(vreinterpretq_u8_u32($v), mask))
}};
($v:expr, 16) => {
$v = vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32($v)))
};
($v:expr, $r:literal) => {
$v = vorrq_u32(vshlq_n_u32($v, $r), vshrq_n_u32($v, 32 - $r))
};
}
macro_rules! extract {
($v:expr, $s:literal) => {
$v = vextq_u32($v, $v, $s)
};
}
impl<R: Rounds, V: Variant> Backend<R, V> {
#[inline(always)]
/// Generates `num_blocks` blocks and blindly writes them to `dest_ptr`
///
/// `num_blocks` must be greater than 0, and less than or equal to 4.
///
/// # Safety
/// `dest_ptr` must have at least `64 * num_blocks` bytes available to be
/// overwritten, or else it could produce undefined behavior
#[cfg(feature = "rng")]
unsafe fn write_par_ks_blocks(&mut self, buffer: &mut [u32; 64]) {
let mut blocks = [
[self.state[0], self.state[1], self.state[2], self.state[3]],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[0], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[1], V),
],
[
self.state[0],
self.state[1],
self.state[2],
add_counter!(self.state[3], self.ctrs[2], V),
],
];
for _ in 0..R::COUNT {
double_quarter_round(&mut blocks);
}
let mut dest_ptr = buffer.as_mut_ptr().cast::<u8>();
for block in 0..4 {
// add state to block
for state_row in 0..3 {
add_assign_vec!(blocks[block][state_row], self.state[state_row]);
}
if block > 0 {
add_assign_vec!(
blocks[block][3],
add_counter!(self.state[3], self.ctrs[block - 1], V)
);
} else {
add_assign_vec!(blocks[block][3], self.state[3]);
}
// write blocks to buffer
for state_row in 0..4 {
#[allow(clippy::cast_sign_loss)]
vst1q_u8(
dest_ptr.offset(state_row << 4),
vreinterpretq_u8_u32(blocks[block][state_row as usize]),
);
}
dest_ptr = dest_ptr.add(64);
}
self.state[3] = add_counter!(self.state[3], self.ctrs[3], V);
}
}
#[inline]
unsafe fn double_quarter_round<const N: usize>(blocks: &mut [[uint32x4_t; 4]; N]) {
add_xor_rot(blocks);
rows_to_cols(blocks);
add_xor_rot(blocks);
cols_to_rows(blocks);
}
#[inline]
unsafe fn add_xor_rot<const N: usize>(blocks: &mut [[uint32x4_t; 4]; N]) {
/// Evaluates to `a = a ^ b`, where the operands are u32x4s
macro_rules! xor_assign_vec {
($a:expr, $b:expr) => {
$a = veorq_u32($a, $b)
};
}
for block in blocks.iter_mut() {
// this part of the code cannot be reduced much more without having
// to deal with some problems regarding `rotate_left` requiring the second
// argument to be a const, and const arrays cannot be indexed by non-consts
add_assign_vec!(block[0], block[1]);
xor_assign_vec!(block[3], block[0]);
rotate_left!(block[3], 16);
add_assign_vec!(block[2], block[3]);
xor_assign_vec!(block[1], block[2]);
rotate_left!(block[1], 12);
add_assign_vec!(block[0], block[1]);
xor_assign_vec!(block[3], block[0]);
rotate_left!(block[3], 8);
add_assign_vec!(block[2], block[3]);
xor_assign_vec!(block[1], block[2]);
rotate_left!(block[1], 7);
}
}
#[inline]
unsafe fn rows_to_cols<const N: usize>(blocks: &mut [[uint32x4_t; 4]; N]) {
for block in blocks.iter_mut() {
extract!(block[1], 1);
extract!(block[2], 2);
extract!(block[3], 3);
}
}
#[inline]
unsafe fn cols_to_rows<const N: usize>(blocks: &mut [[uint32x4_t; 4]; N]) {
for block in blocks.iter_mut() {
extract!(block[1], 3);
extract!(block[2], 2);
extract!(block[3], 1);
}
}
+87
View File
@@ -0,0 +1,87 @@
//! Portable implementation which does not rely on architecture-specific intrinsics.
#![allow(clippy::cast_possible_truncation)]
use crate::{ChaChaCore, Rounds, STATE_WORDS, Variant, quarter_round};
#[cfg(feature = "cipher")]
use crate::chacha::Block;
#[cfg(feature = "cipher")]
use cipher::{
BlockSizeUser, ParBlocksSizeUser, StreamCipherBackend,
consts::{U1, U64},
};
#[cfg(feature = "rng")]
use crate::rng::BLOCK_WORDS;
pub(crate) struct Backend<'a, R: Rounds, V: Variant>(pub(crate) &'a mut ChaChaCore<R, V>);
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> BlockSizeUser for Backend<'_, R, V> {
type BlockSize = U64;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> ParBlocksSizeUser for Backend<'_, R, V> {
type ParBlocksSize = U1;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherBackend for Backend<'_, R, V> {
#[inline(always)]
fn gen_ks_block(&mut self, block: &mut Block) {
let res = run_rounds::<R>(&self.0.state);
let mut ctr = (u64::from(self.0.state[13]) << 32) | u64::from(self.0.state[12]);
ctr = ctr.wrapping_add(1);
self.0.state[12] = ctr as u32;
if size_of::<V::Counter>() == 8 {
self.0.state[13] = (ctr >> 32) as u32;
}
for (chunk, val) in block.chunks_exact_mut(4).zip(res.iter()) {
chunk.copy_from_slice(&val.to_le_bytes());
}
}
}
#[cfg(feature = "rng")]
impl<R: Rounds, V: Variant> Backend<'_, R, V> {
#[inline(always)]
pub(crate) fn gen_ks_blocks(&mut self, buffer: &mut [u32; 64]) {
for block in 0..4 {
let res = run_rounds::<R>(&self.0.state);
let mut ctr = (u64::from(self.0.state[13]) << 32) | u64::from(self.0.state[12]);
ctr = ctr.wrapping_add(1);
self.0.state[12] = ctr as u32;
self.0.state[13] = (ctr >> 32) as u32;
buffer[block * BLOCK_WORDS as usize..(block + 1) * BLOCK_WORDS as usize]
.copy_from_slice(&res);
}
}
}
#[inline(always)]
fn run_rounds<R: Rounds>(state: &[u32; STATE_WORDS]) -> [u32; STATE_WORDS] {
let mut res = *state;
for _ in 0..R::COUNT {
// column rounds
quarter_round(0, 4, 8, 12, &mut res);
quarter_round(1, 5, 9, 13, &mut res);
quarter_round(2, 6, 10, 14, &mut res);
quarter_round(3, 7, 11, 15, &mut res);
// diagonal rounds
quarter_round(0, 5, 10, 15, &mut res);
quarter_round(1, 6, 11, 12, &mut res);
quarter_round(2, 7, 8, 13, &mut res);
quarter_round(3, 4, 9, 14, &mut res);
}
for (s1, s0) in res.iter_mut().zip(state.iter()) {
*s1 = s1.wrapping_add(*s0);
}
res
}
+293
View File
@@ -0,0 +1,293 @@
//! SSE2 backend.
#![allow(unsafe_op_in_unsafe_fn, reason = "needs triage")]
#![allow(clippy::cast_possible_truncation, reason = "needs triage")]
#![allow(clippy::cast_possible_wrap, reason = "needs triage")]
#![allow(clippy::cast_sign_loss, reason = "needs triage")]
#![allow(clippy::undocumented_unsafe_blocks, reason = "TODO")]
use crate::{Rounds, Variant};
#[cfg(feature = "rng")]
use crate::ChaChaCore;
#[cfg(feature = "cipher")]
use crate::{STATE_WORDS, chacha::Block};
#[cfg(feature = "cipher")]
use cipher::{
BlockSizeUser, ParBlocksSizeUser, StreamCipherBackend, StreamCipherClosure,
consts::{U4, U64},
};
use core::marker::PhantomData;
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
const PAR_BLOCKS: usize = 4;
#[inline]
#[target_feature(enable = "sse2")]
#[cfg(feature = "cipher")]
pub(crate) unsafe fn inner<R, F, V>(state: &mut [u32; STATE_WORDS], f: F)
where
R: Rounds,
F: StreamCipherClosure<BlockSize = U64>,
V: Variant,
{
let state_ptr = state.as_ptr().cast::<__m128i>();
let mut backend = Backend::<R, V> {
v: [
_mm_loadu_si128(state_ptr.add(0)),
_mm_loadu_si128(state_ptr.add(1)),
_mm_loadu_si128(state_ptr.add(2)),
_mm_loadu_si128(state_ptr.add(3)),
],
_pd: PhantomData,
};
f.call(&mut backend);
state[12] = _mm_cvtsi128_si32(backend.v[3]) as u32;
if size_of::<V::Counter>() == 8 {
state[13] = _mm_extract_epi32(backend.v[3], 1) as u32;
}
}
struct Backend<R: Rounds, V: Variant> {
v: [__m128i; 4],
_pd: PhantomData<(R, V)>,
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> BlockSizeUser for Backend<R, V> {
type BlockSize = U64;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> ParBlocksSizeUser for Backend<R, V> {
type ParBlocksSize = U4;
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherBackend for Backend<R, V> {
#[inline(always)]
fn gen_ks_block(&mut self, block: &mut Block) {
unsafe {
let res = rounds::<R, V>(&self.v);
self.v[3] = match size_of::<V::Counter>() {
4 => _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, 1)),
8 => _mm_add_epi64(self.v[3], _mm_set_epi64x(0, 1)),
_ => unreachable!(),
};
let block_ptr = block.as_mut_ptr().cast::<__m128i>();
for i in 0..4 {
_mm_storeu_si128(block_ptr.add(i), res[0][i]);
}
}
}
#[inline(always)]
fn gen_par_ks_blocks(&mut self, blocks: &mut cipher::ParBlocks<Self>) {
unsafe {
let res = rounds::<R, V>(&self.v);
self.v[3] = match size_of::<V::Counter>() {
4 => _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, PAR_BLOCKS as i32)),
8 => _mm_add_epi64(self.v[3], _mm_set_epi64x(0, PAR_BLOCKS as i64)),
_ => unreachable!(),
};
let blocks_ptr = blocks.as_mut_ptr().cast::<__m128i>();
for block in 0..PAR_BLOCKS {
for i in 0..4 {
_mm_storeu_si128(blocks_ptr.add(i + block * PAR_BLOCKS), res[block][i]);
}
}
}
}
}
#[inline]
#[target_feature(enable = "sse2")]
#[cfg(feature = "rng")]
pub(crate) unsafe fn rng_inner<R, V>(core: &mut ChaChaCore<R, V>, buffer: &mut [u32; 64])
where
R: Rounds,
V: Variant,
{
let state_ptr = core.state.as_ptr().cast::<__m128i>();
let mut backend = Backend::<R, V> {
v: [
_mm_loadu_si128(state_ptr.add(0)),
_mm_loadu_si128(state_ptr.add(1)),
_mm_loadu_si128(state_ptr.add(2)),
_mm_loadu_si128(state_ptr.add(3)),
],
_pd: PhantomData,
};
backend.gen_ks_blocks(buffer);
core.state[12] = _mm_cvtsi128_si32(backend.v[3]) as u32;
core.state[13] = _mm_extract_epi32(backend.v[3], 1) as u32;
}
#[cfg(feature = "rng")]
impl<R: Rounds, V: Variant> Backend<R, V> {
#[inline(always)]
fn gen_ks_blocks(&mut self, block: &mut [u32; 64]) {
const _: () = assert!(4 * PAR_BLOCKS * size_of::<__m128i>() == size_of::<[u32; 64]>());
unsafe {
let res = rounds::<R, V>(&self.v);
self.v[3] = _mm_add_epi64(self.v[3], _mm_set_epi64x(0, PAR_BLOCKS as i64));
let blocks_ptr = block.as_mut_ptr().cast::<__m128i>();
for block in 0..PAR_BLOCKS {
for i in 0..4 {
_mm_storeu_si128(blocks_ptr.add(i + block * PAR_BLOCKS), res[block][i]);
}
}
}
}
}
#[inline]
#[target_feature(enable = "sse2")]
unsafe fn rounds<R: Rounds, V: Variant>(v: &[__m128i; 4]) -> [[__m128i; 4]; PAR_BLOCKS] {
let mut res = [*v; 4];
for block in 1..PAR_BLOCKS {
res[block][3] = match size_of::<V::Counter>() {
4 => _mm_add_epi32(res[block][3], _mm_set_epi32(0, 0, 0, block as i32)),
8 => _mm_add_epi64(res[block][3], _mm_set_epi64x(0, block as i64)),
_ => unreachable!(),
}
}
for _ in 0..R::COUNT {
double_quarter_round(&mut res);
}
for block in 0..PAR_BLOCKS {
for i in 0..3 {
res[block][i] = _mm_add_epi32(res[block][i], v[i]);
}
let ctr = match size_of::<V::Counter>() {
4 => _mm_add_epi32(v[3], _mm_set_epi32(0, 0, 0, block as i32)),
8 => _mm_add_epi64(v[3], _mm_set_epi64x(0, block as i64)),
_ => unreachable!(),
};
res[block][3] = _mm_add_epi32(res[block][3], ctr);
}
res
}
#[inline]
#[target_feature(enable = "sse2")]
unsafe fn double_quarter_round(v: &mut [[__m128i; 4]; PAR_BLOCKS]) {
add_xor_rot(v);
rows_to_cols(v);
add_xor_rot(v);
cols_to_rows(v);
}
/// The goal of this function is to transform the state words from:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c0, c1, c2, c3] [ 8, 9, 10, 11]
/// [d0, d1, d2, d3] [12, 13, 14, 15]
/// ```
///
/// to:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b1, b2, b3, b0] == [ 5, 6, 7, 4]
/// [c2, c3, c0, c1] [10, 11, 8, 9]
/// [d3, d0, d1, d2] [15, 12, 13, 14]
/// ```
///
/// so that we can apply [`add_xor_rot`] to the resulting columns, and have it compute the
/// "diagonal rounds" (as defined in RFC 7539) in parallel. In practice, this shuffle is
/// non-optimal: the last state word to be altered in `add_xor_rot` is `b`, so the shuffle
/// blocks on the result of `b` being calculated.
///
/// We can optimize this by observing that the four quarter rounds in `add_xor_rot` are
/// data-independent: they only access a single column of the state, and thus the order of
/// the columns does not matter. We therefore instead shuffle the other three state words,
/// to obtain the following equivalent layout:
/// ```text
/// [a3, a0, a1, a2] [ 3, 0, 1, 2]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c1, c2, c3, c0] [ 9, 10, 11, 8]
/// [d2, d3, d0, d1] [14, 15, 12, 13]
/// ```
///
/// See https://github.com/sneves/blake2-avx2/pull/4 for additional details. The earliest
/// known occurrence of this optimization is in floodyberry's SSE4 ChaCha code from 2014:
/// - https://github.com/floodyberry/chacha-opt/blob/0ab65cb99f5016633b652edebaf3691ceb4ff753/chacha_blocks_ssse3-64.S#L639-L643
#[inline]
#[target_feature(enable = "sse2")]
unsafe fn rows_to_cols(blocks: &mut [[__m128i; 4]; PAR_BLOCKS]) {
for [a, _, c, d] in blocks.iter_mut() {
// c >>>= 32; d >>>= 64; a >>>= 96;
*c = _mm_shuffle_epi32(*c, 0b_00_11_10_01); // _MM_SHUFFLE(0, 3, 2, 1)
*d = _mm_shuffle_epi32(*d, 0b_01_00_11_10); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm_shuffle_epi32(*a, 0b_10_01_00_11); // _MM_SHUFFLE(2, 1, 0, 3)
}
}
/// The goal of this function is to transform the state words from:
/// ```text
/// [a3, a0, a1, a2] [ 3, 0, 1, 2]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c1, c2, c3, c0] [ 9, 10, 11, 8]
/// [d2, d3, d0, d1] [14, 15, 12, 13]
/// ```
///
/// to:
/// ```text
/// [a0, a1, a2, a3] [ 0, 1, 2, 3]
/// [b0, b1, b2, b3] == [ 4, 5, 6, 7]
/// [c0, c1, c2, c3] [ 8, 9, 10, 11]
/// [d0, d1, d2, d3] [12, 13, 14, 15]
/// ```
///
/// reversing the transformation of [`rows_to_cols`].
#[inline]
#[target_feature(enable = "sse2")]
unsafe fn cols_to_rows(blocks: &mut [[__m128i; 4]; PAR_BLOCKS]) {
for [a, _, c, d] in blocks.iter_mut() {
// c <<<= 32; d <<<= 64; a <<<= 96;
*c = _mm_shuffle_epi32(*c, 0b_10_01_00_11); // _MM_SHUFFLE(2, 1, 0, 3)
*d = _mm_shuffle_epi32(*d, 0b_01_00_11_10); // _MM_SHUFFLE(1, 0, 3, 2)
*a = _mm_shuffle_epi32(*a, 0b_00_11_10_01); // _MM_SHUFFLE(0, 3, 2, 1)
}
}
#[inline]
#[target_feature(enable = "sse2")]
unsafe fn add_xor_rot(blocks: &mut [[__m128i; 4]; PAR_BLOCKS]) {
for [a, b, c, d] in blocks.iter_mut() {
// a += b; d ^= a; d <<<= (16, 16, 16, 16);
*a = _mm_add_epi32(*a, *b);
*d = _mm_xor_si128(*d, *a);
*d = _mm_xor_si128(_mm_slli_epi32(*d, 16), _mm_srli_epi32(*d, 16));
// c += d; b ^= c; b <<<= (12, 12, 12, 12);
*c = _mm_add_epi32(*c, *d);
*b = _mm_xor_si128(*b, *c);
*b = _mm_xor_si128(_mm_slli_epi32(*b, 12), _mm_srli_epi32(*b, 20));
// a += b; d ^= a; d <<<= (8, 8, 8, 8);
*a = _mm_add_epi32(*a, *b);
*d = _mm_xor_si128(*d, *a);
*d = _mm_xor_si128(_mm_slli_epi32(*d, 8), _mm_srli_epi32(*d, 24));
// c += d; b ^= c; b <<<= (7, 7, 7, 7);
*c = _mm_add_epi32(*c, *d);
*b = _mm_xor_si128(*b, *c);
*b = _mm_xor_si128(_mm_slli_epi32(*b, 7), _mm_srli_epi32(*b, 25));
}
}
+38
View File
@@ -0,0 +1,38 @@
use cipher::{
IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCoreWrapper,
array::Array,
consts::{U12, U32, U64},
};
use crate::{ChaChaCore, R8, R12, R20, Rounds, variants::Ietf};
/// Key type used by all ChaCha variants.
pub type Key = Array<u8, U32>;
/// Nonce type used by ChaCha variants.
pub type Nonce = Array<u8, U12>;
/// ChaCha8 stream cipher (reduced-round variant of [`ChaCha20`] with 8 rounds)
pub type ChaCha8 = StreamCipherCoreWrapper<ChaChaCore<R8, Ietf>>;
/// ChaCha12 stream cipher (reduced-round variant of [`ChaCha20`] with 12 rounds)
pub type ChaCha12 = StreamCipherCoreWrapper<ChaChaCore<R12, Ietf>>;
/// ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce)
pub type ChaCha20 = StreamCipherCoreWrapper<ChaChaCore<R20, Ietf>>;
pub(crate) type Block = Array<u8, U64>;
impl<R: Rounds> KeySizeUser for ChaChaCore<R, Ietf> {
type KeySize = U32;
}
impl<R: Rounds> IvSizeUser for ChaChaCore<R, Ietf> {
type IvSize = U12;
}
impl<R: Rounds> KeyIvInit for ChaChaCore<R, Ietf> {
#[inline]
fn new(key: &Key, iv: &Nonce) -> Self {
ChaChaCore::<R, Ietf>::new_internal(key.as_ref(), iv.as_ref())
}
}
+32
View File
@@ -0,0 +1,32 @@
//! Legacy version of ChaCha20 with a 64-bit nonce
use crate::{ChaChaCore, Key, R20, variants::Legacy};
use cipher::{
IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCoreWrapper,
array::Array,
consts::{U8, U32},
};
/// Nonce type used by [`ChaCha20Legacy`].
pub type LegacyNonce = Array<u8, U8>;
/// The ChaCha20 stream cipher (legacy "djb" construction with 64-bit nonce).
pub type ChaCha20Legacy = StreamCipherCoreWrapper<ChaCha20LegacyCore>;
/// The ChaCha20 stream cipher (legacy "djb" construction with 64-bit nonce).
pub type ChaCha20LegacyCore = ChaChaCore<R20, Legacy>;
impl KeySizeUser for ChaCha20LegacyCore {
type KeySize = U32;
}
impl IvSizeUser for ChaCha20LegacyCore {
type IvSize = U8;
}
impl KeyIvInit for ChaCha20LegacyCore {
#[inline(always)]
fn new(key: &Key, iv: &LegacyNonce) -> Self {
ChaChaCore::<R20, Legacy>::new_internal(key.as_ref(), iv.as_ref())
}
}
+343
View File
@@ -0,0 +1,343 @@
#![no_std]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod variants;
mod backends;
#[cfg(feature = "cipher")]
mod chacha;
#[cfg(feature = "legacy")]
mod legacy;
#[cfg(feature = "rng")]
mod rng;
#[cfg(feature = "xchacha")]
mod xchacha;
#[cfg(feature = "cipher")]
pub use chacha::{ChaCha8, ChaCha12, ChaCha20, Key, Nonce};
#[cfg(feature = "cipher")]
pub use cipher;
#[cfg(feature = "cipher")]
pub use cipher::KeyIvInit;
#[cfg(feature = "legacy")]
pub use legacy::{ChaCha20Legacy, ChaCha20LegacyCore, LegacyNonce};
#[cfg(feature = "rng")]
pub use rand_core;
#[cfg(feature = "rng")]
pub use rng::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng, Seed, SerializedRngState};
#[cfg(feature = "xchacha")]
pub use xchacha::{XChaCha8, XChaCha12, XChaCha20, XNonce, hchacha};
use cfg_if::cfg_if;
use core::{fmt, marker::PhantomData};
use variants::Variant;
#[cfg(feature = "cipher")]
use cipher::{BlockSizeUser, StreamCipherCore, StreamCipherSeekCore, consts::U64};
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};
/// State initialization constant ("expand 32-byte k")
#[cfg(any(feature = "cipher", feature = "rng"))]
const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
/// Number of 32-bit words in the ChaCha state
const STATE_WORDS: usize = 16;
/// Marker type for a number of ChaCha rounds to perform.
pub trait Rounds: Copy {
/// The amount of rounds to perform
const COUNT: usize;
}
/// 8-rounds
#[derive(Copy, Clone, Debug)]
pub struct R8;
impl Rounds for R8 {
const COUNT: usize = 4;
}
/// 12-rounds
#[derive(Copy, Clone, Debug)]
pub struct R12;
impl Rounds for R12 {
const COUNT: usize = 6;
}
/// 20-rounds
#[derive(Copy, Clone, Debug)]
pub struct R20;
impl Rounds for R20 {
const COUNT: usize = 10;
}
cfg_if! {
if #[cfg(chacha20_backend = "soft")] {
type Tokens = ();
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
cfg_if! {
if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
#[cfg(not(all(target_feature = "avx512f", target_feature = "avx512vl")))]
compile_error!("You must enable `avx512f` and `avx512vl` target features with \
`chacha20_backend = "avx512"` configuration option");
type Tokens = ();
} else if #[cfg(chacha20_backend = "avx2")] {
#[cfg(not(target_feature = "avx2"))]
compile_error!("You must enable `avx2` target feature with \
`chacha20_backend = "avx2"` configuration option");
type Tokens = ();
} else if #[cfg(chacha20_backend = "sse2")] {
#[cfg(not(target_feature = "sse2"))]
compile_error!("You must enable `sse2` target feature with \
`chacha20_backend = "sse2"` configuration option");
type Tokens = ();
} else {
#[cfg(chacha20_avx512)]
cpufeatures::new!(avx512_cpuid, "avx512f", "avx512vl");
cpufeatures::new!(avx2_cpuid, "avx2");
cpufeatures::new!(sse2_cpuid, "sse2");
#[cfg(chacha20_avx512)]
type Tokens = (avx512_cpuid::InitToken, avx2_cpuid::InitToken, sse2_cpuid::InitToken);
#[cfg(not(chacha20_avx512))]
type Tokens = (avx2_cpuid::InitToken, sse2_cpuid::InitToken);
}
}
} else {
type Tokens = ();
}
}
/// The ChaCha core function.
pub struct ChaChaCore<R: Rounds, V: Variant> {
/// Internal state of the core function
state: [u32; STATE_WORDS],
/// CPU target feature tokens
#[allow(dead_code)]
tokens: Tokens,
/// Number of rounds to perform and the cipher variant
_pd: PhantomData<(R, V)>,
}
impl<R: Rounds, V: Variant> ChaChaCore<R, V> {
/// Constructs a ChaChaCore with the specified `key` and `iv`.
///
/// You must ensure that the iv is of the correct size when using this method
/// directly.
///
/// # Panics
/// If `iv.len()` is not equal to 4, 8, or 12.
#[must_use]
#[cfg(any(feature = "cipher", feature = "rng"))]
fn new_internal(key: &[u8; 32], iv: &[u8]) -> Self {
assert!(matches!(iv.len(), 4 | 8 | 12));
let mut state = [0u32; STATE_WORDS];
let ctr_size = size_of::<V::Counter>() / size_of::<u32>();
let (const_dst, state_rem) = state.split_at_mut(4);
let (key_dst, state_rem) = state_rem.split_at_mut(8);
let (_ctr_dst, iv_dst) = state_rem.split_at_mut(ctr_size);
const_dst.copy_from_slice(&CONSTANTS);
// TODO(tarcieri): when MSRV 1.88, use `[T]::as_chunks` to avoid panic
#[allow(clippy::unwrap_used, reason = "MSRV TODO")]
{
for (src, dst) in key.chunks_exact(4).zip(key_dst) {
*dst = u32::from_le_bytes(src.try_into().unwrap());
}
assert_eq!(size_of_val(iv_dst), size_of_val(iv));
for (src, dst) in iv.chunks_exact(4).zip(iv_dst) {
*dst = u32::from_le_bytes(src.try_into().unwrap());
}
}
cfg_if! {
if #[cfg(chacha20_backend = "soft")] {
let tokens = ();
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
cfg_if! {
if #[cfg(chacha20_backend = "avx512")] {
let tokens = ();
} else if #[cfg(chacha20_backend = "avx2")] {
let tokens = ();
} else if #[cfg(chacha20_backend = "sse2")] {
let tokens = ();
} else if #[cfg(chacha20_avx512)] {
let tokens = (avx512_cpuid::init(), avx2_cpuid::init(), sse2_cpuid::init());
} else {
let tokens = (avx2_cpuid::init(), sse2_cpuid::init());
}
}
} else {
let tokens = ();
}
}
Self {
state,
tokens,
_pd: PhantomData,
}
}
/// Get the current block position.
#[inline(always)]
#[must_use]
pub fn get_block_pos(&self) -> V::Counter {
V::get_block_pos(&self.state[12..])
}
/// Set the block position.
#[inline(always)]
pub fn set_block_pos(&mut self, pos: V::Counter) {
V::set_block_pos(&mut self.state[12..], pos);
}
}
impl<R: Rounds, V: Variant> fmt::Debug for ChaChaCore<R, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ChaChaCore<R: {}, V: {}-bit)> {{ ... }}",
R::COUNT,
size_of::<V::Counter>() * 8
)
}
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherSeekCore for ChaChaCore<R, V> {
type Counter = V::Counter;
#[inline(always)]
fn get_block_pos(&self) -> Self::Counter {
self.get_block_pos()
}
#[inline(always)]
fn set_block_pos(&mut self, pos: Self::Counter) {
self.set_block_pos(pos);
}
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> StreamCipherCore for ChaChaCore<R, V> {
#[inline(always)]
fn remaining_blocks(&self) -> Option<usize> {
V::remaining_blocks(self.get_block_pos())
}
fn process_with_backend(
&mut self,
f: impl cipher::StreamCipherClosure<BlockSize = Self::BlockSize>,
) {
cfg_if! {
if #[cfg(chacha20_backend = "soft")] {
f.call(&mut backends::soft::Backend(self));
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
cfg_if! {
if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
unsafe {
backends::avx512::inner::<R, _, V>(&mut self.state, f);
}
} else if #[cfg(chacha20_backend = "avx2")] {
unsafe {
backends::avx2::inner::<R, _, V>(&mut self.state, f);
}
} else if #[cfg(chacha20_backend = "sse2")] {
unsafe {
backends::sse2::inner::<R, _, V>(&mut self.state, f);
}
} else {
#[cfg(chacha20_avx512)]
let (avx512_token, avx2_token, sse2_token) = self.tokens;
#[cfg(not(chacha20_avx512))]
let (avx2_token, sse2_token) = self.tokens;
#[cfg(chacha20_avx512)]
if avx512_token.get() {
// SAFETY: runtime CPU feature detection above ensures this is valid
unsafe {
backends::avx512::inner::<R, _, V>(&mut self.state, f);
}
return;
}
if avx2_token.get() {
// SAFETY: runtime CPU feature detection above ensures this is valid
unsafe {
backends::avx2::inner::<R, _, V>(&mut self.state, f);
}
} else if sse2_token.get() {
// SAFETY: runtime CPU feature detection above ensures this is valid
unsafe {
backends::sse2::inner::<R, _, V>(&mut self.state, f);
}
} else {
f.call(&mut backends::soft::Backend(self));
}
}
}
} else if #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] {
// SAFETY: we have used conditional compilation to ensure NEON is available
unsafe {
backends::neon::inner::<R, _, V>(&mut self.state, f);
}
} else {
f.call(&mut backends::soft::Backend(self));
}
}
}
}
#[cfg(feature = "cipher")]
impl<R: Rounds, V: Variant> BlockSizeUser for ChaChaCore<R, V> {
type BlockSize = U64;
}
#[cfg(feature = "zeroize")]
impl<R: Rounds, V: Variant> Drop for ChaChaCore<R, V> {
fn drop(&mut self) {
self.state.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl<R: Rounds, V: Variant> ZeroizeOnDrop for ChaChaCore<R, V> {}
/// The ChaCha20 quarter round function
///
/// We located this function in the root of the crate as we want it to be available
/// for the soft backend and for xchacha.
#[allow(dead_code)]
pub(crate) fn quarter_round(
a: usize,
b: usize,
c: usize,
d: usize,
state: &mut [u32; STATE_WORDS],
) {
state[a] = state[a].wrapping_add(state[b]);
state[d] ^= state[a];
state[d] = state[d].rotate_left(16);
state[c] = state[c].wrapping_add(state[d]);
state[b] ^= state[c];
state[b] = state[b].rotate_left(12);
state[a] = state[a].wrapping_add(state[b]);
state[d] ^= state[a];
state[d] = state[d].rotate_left(8);
state[c] = state[c].wrapping_add(state[d]);
state[b] ^= state[c];
state[b] = state[b].rotate_left(7);
}
+344
View File
@@ -0,0 +1,344 @@
#![allow(clippy::cast_possible_truncation, reason = "needs triage")]
#![allow(clippy::undocumented_unsafe_blocks, reason = "TODO")]
use core::fmt;
use rand_core::{
Infallible, SeedableRng, TryCryptoRng, TryRng,
block::{BlockRng, Generator},
};
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::{
ChaChaCore, R8, R12, R20, Rounds, backends,
variants::{Legacy, Variant},
};
use cfg_if::cfg_if;
/// Seed value used to initialize ChaCha-based RNGs.
pub type Seed = [u8; 32];
/// Serialized RNG state.
pub type SerializedRngState = [u8; 49];
/// Number of 32-bit words per ChaCha block (fixed by algorithm definition).
pub(crate) const BLOCK_WORDS: u8 = 16;
/// Number of blocks generated by RNG core.
const BUF_BLOCKS: u8 = 4;
/// Buffer size in words used by buffered RNG.
const BUFFER_SIZE: usize = (BLOCK_WORDS * BUF_BLOCKS) as usize;
impl<R: Rounds, V: Variant> SeedableRng for ChaChaCore<R, V> {
type Seed = Seed;
#[inline]
fn from_seed(seed: Self::Seed) -> Self {
ChaChaCore::new_internal(&seed, &[0u8; 8])
}
}
impl<R: Rounds, V: Variant> Generator for ChaChaCore<R, V> {
type Output = [u32; BUFFER_SIZE];
/// Generates 4 blocks in parallel with avx2 & neon, but merely fills
/// 4 blocks with sse2 & soft
fn generate(&mut self, buffer: &mut [u32; BUFFER_SIZE]) {
cfg_if! {
if #[cfg(chacha20_backend = "soft")] {
backends::soft::Backend(self).gen_ks_blocks(buffer);
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
cfg_if! {
// AVX-512 doesn't support RNG, so use AVX-2 instead
if #[cfg(any(chacha20_backend = "avx2", chacha20_backend = "avx512"))] {
unsafe {
backends::avx2::rng_inner::<R, V>(self, buffer);
}
} else if #[cfg(chacha20_backend = "sse2")] {
unsafe {
backends::sse2::rng_inner::<R, V>(self, buffer);
}
} else {
#[cfg(chacha20_avx512)]
let (_avx512_token, avx2_token, sse2_token) = self.tokens;
#[cfg(not(chacha20_avx512))]
let (avx2_token, sse2_token) = self.tokens;
if avx2_token.get() {
unsafe {
backends::avx2::rng_inner::<R, V>(self, buffer);
}
} else if sse2_token.get() {
unsafe {
backends::sse2::rng_inner::<R, V>(self, buffer);
}
} else {
backends::soft::Backend(self).gen_ks_blocks(buffer);
}
}
}
} else if #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] {
// SAFETY: we have used conditional compilation to ensure NEON is available
unsafe {
backends::neon::rng_inner::<R, V>(self, buffer);
}
} else {
backends::soft::Backend(self).gen_ks_blocks(buffer);
}
}
}
// `Drop` impl of `BlockRng` calls this method and passes reference to
// its internal buffer in `output`. So we zeroize its contents here.
#[cfg(feature = "zeroize")]
fn drop(&mut self, output: &mut Self::Output) {
output.zeroize();
}
}
macro_rules! impl_chacha_rng {
($Rng:ident, $rounds:ident) => {
/// A cryptographically secure random number generator that uses the ChaCha stream cipher.
///
/// See the [crate docs][crate] for more information about the underlying stream cipher.
///
/// This RNG implementation uses a 64-bit counter and 64-bit stream identifier (a.k.a nonce).
/// A 64-bit counter over 64-byte (16 word) blocks allows 1 ZiB of output before cycling,
/// and the stream identifier allows 2<sup>64</sup> unique streams of output per seed.
/// Both counter and stream are initialized to zero but may be set via the [`set_word_pos`]
/// and [`set_stream`] methods.
///
/// [`set_word_pos`]: Self::set_word_pos
/// [`set_stream`]: Self::set_stream
///
/// # Example
///
/// ```rust
#[doc = concat!("use chacha20::", stringify!($Rng), ";")]
/// use rand_core::{SeedableRng, Rng};
///
/// let seed = [42u8; 32];
#[doc = concat!("let mut rng = ", stringify!($Rng), "::from_seed(seed);")]
///
/// let random_u32 = rng.next_u32();
/// let random_u64 = rng.next_u64();
///
/// let mut random_bytes = [0u8; 3];
/// rng.fill_bytes(&mut random_bytes);
/// ```
///
/// See the [`rand`](https://docs.rs/rand/) crate for more advanced RNG functionality.
pub struct $Rng {
core: BlockRng<ChaChaCore<$rounds, Legacy>>,
}
impl SeedableRng for $Rng {
type Seed = Seed;
#[inline]
fn from_seed(seed: Self::Seed) -> Self {
let core = ChaChaCore::new_internal(&seed, &[0u8; 8]);
Self {
core: BlockRng::new(core),
}
}
}
impl TryRng for $Rng {
type Error = Infallible;
#[inline]
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
Ok(self.core.next_word())
}
#[inline]
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
Ok(self.core.next_u64_from_u32())
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
self.core.fill_bytes(dest);
Ok(())
}
}
impl TryCryptoRng for $Rng {}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for $Rng {}
// We use custom implementation of `PartialEq` because RNG states
// may buffer different parts of the same keystream, while keeping
// buffer cursor pointing towards the same keystream point.
impl PartialEq<$Rng> for $Rng {
fn eq(&self, rhs: &$Rng) -> bool {
(self.get_seed() == rhs.get_seed())
&& (self.get_stream() == rhs.get_stream())
&& (self.get_word_pos() == rhs.get_word_pos())
}
}
impl Eq for $Rng {}
// Custom Debug implementation that does not expose the internal state
impl fmt::Debug for $Rng {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, concat!(stringify!($Rng), " {{ ... }}"))
}
}
impl $Rng {
/// Get the offset from the start of the stream, in 32-bit words.
///
/// Since the generated blocks are 16 words (2<sup>4</sup>) long and the
/// counter is 64-bits, the offset is a 68-bit number. Sub-word offsets are
/// not supported, hence the result can simply be multiplied by 4 to get a
/// byte-offset.
#[inline]
#[must_use]
pub fn get_word_pos(&self) -> u128 {
let mut block_counter = (u64::from(self.core.core.state[13]) << 32)
| u64::from(self.core.core.state[12]);
if self.core.word_offset() != 0 {
block_counter = block_counter.wrapping_sub(u64::from(BUF_BLOCKS));
}
let word_pos = u128::from(block_counter) * u128::from(BLOCK_WORDS)
+ self.core.word_offset() as u128;
// eliminate bits above the 68th bit
word_pos & ((1 << 68) - 1)
}
/// Set the offset from the start of the stream, in 32-bit words.
///
/// **This value will be erased when calling `set_stream()`,
/// so call `set_stream()` before calling `set_word_pos()`**
/// if you intend on using both of them together.
///
/// As with `get_word_pos`, we use a 68-bit number. Since the generator
/// simply cycles at the end of its period (1 ZiB), we ignore the upper
/// 60 bits.
#[inline]
pub fn set_word_pos(&mut self, word_offset: u128) {
let index = (word_offset % u128::from(BLOCK_WORDS)) as usize;
let counter = word_offset / u128::from(BLOCK_WORDS);
//self.set_block_pos(counter as u64);
self.core.core.state[12] = counter as u32;
self.core.core.state[13] = (counter >> 32) as u32;
self.core.reset_and_skip(index);
}
/// Sets the block pos and resets the RNG's index.
///
/// **This value will be erased when calling `set_stream()`,
/// so call `set_stream()` before calling `set_block_pos()`**
/// if you intend on using both of them together.
///
/// The word pos will be equal to `block_pos * 16 words per block`.
#[inline]
#[allow(unused)]
pub fn set_block_pos(&mut self, block_pos: u64) {
self.core.reset_and_skip(0);
self.core.core.set_block_pos(block_pos);
}
/// Get the block pos.
#[inline]
#[allow(unused)]
#[must_use]
pub fn get_block_pos(&self) -> u64 {
let counter = self.core.core.get_block_pos();
let offset = self.core.word_offset();
if offset != 0 {
counter - u64::from(BUF_BLOCKS) + offset as u64 / u64::from(BLOCK_WORDS)
} else {
counter
}
}
/// Set the stream ID and reset the `word_pos` to 0.
#[inline]
pub fn set_stream(&mut self, stream: u64) {
self.core.core.state[14] = stream as u32;
self.core.core.state[15] = (stream >> 32) as u32;
self.set_block_pos(0);
}
/// Get the stream number (nonce).
#[inline]
#[must_use]
pub fn get_stream(&self) -> u64 {
let mut result = [0u8; 8];
result[..4].copy_from_slice(&self.core.core.state[14].to_le_bytes());
result[4..].copy_from_slice(&self.core.core.state[15].to_le_bytes());
u64::from_le_bytes(result)
}
/// Get the RNG seed.
#[inline]
#[must_use]
pub fn get_seed(&self) -> [u8; 32] {
let seed = &self.core.core.state[4..12];
let mut result = [0u8; 32];
for (src, dst) in seed.iter().zip(result.chunks_exact_mut(4)) {
dst.copy_from_slice(&src.to_le_bytes())
}
result
}
/// Serialize RNG state.
///
/// # Warning
/// Leaking serialized RNG state to an attacker defeats security properties
/// provided by the RNG.
#[inline]
pub fn serialize_state(&self) -> SerializedRngState {
let seed = self.get_seed();
let stream = self.get_stream().to_le_bytes();
let word_pos = self.get_word_pos().to_le_bytes();
let mut res = [0u8; 49];
let (seed_dst, res_rem) = res.split_at_mut(32);
let (stream_dst, word_pos_dst) = res_rem.split_at_mut(8);
seed_dst.copy_from_slice(&seed);
stream_dst.copy_from_slice(&stream);
word_pos_dst.copy_from_slice(&word_pos[..9]);
debug_assert_eq!(&word_pos[9..], &[0u8; 7]);
res
}
/// Deserialize RNG state.
#[inline]
pub fn deserialize_state(state: &SerializedRngState) -> Self {
let (seed, state_rem) = state.split_at(32);
let (stream, word_pos_raw) = state_rem.split_at(8);
let seed: &[u8; 32] = seed.try_into().expect("seed.len() is equal to 32");
let stream: &[u8; 8] = stream.try_into().expect("stream.len() is equal to 8");
// Note that we use only 68 bits from `word_pos_raw`, i.e. 4 remaining bits
// get ignored and should be equal to zero in practice.
let mut word_pos_buf = [0u8; 16];
word_pos_buf[..9].copy_from_slice(word_pos_raw);
let word_pos = u128::from_le_bytes(word_pos_buf);
let core = ChaChaCore::new_internal(seed, stream);
let mut res = Self {
core: BlockRng::new(core),
};
res.set_word_pos(word_pos);
res
}
}
};
}
impl_chacha_rng!(ChaCha8Rng, R8);
impl_chacha_rng!(ChaCha12Rng, R12);
impl_chacha_rng!(ChaCha20Rng, R20);
+84
View File
@@ -0,0 +1,84 @@
//! ChaCha variant-specific configurations.
mod sealed {
pub trait Sealed {}
}
/// A trait that distinguishes some ChaCha variants. Contains configurations
/// for "Legacy" DJB variant and the IETF variant.
pub trait Variant: sealed::Sealed {
/// The counter's type.
#[cfg(not(feature = "cipher"))]
type Counter: Copy;
/// The counter's type.
#[cfg(feature = "cipher")]
type Counter: cipher::StreamCipherCounter;
/// Takes a slice of `state[12..NONCE_INDEX]` to convert it into
/// `Self::Counter`.
fn get_block_pos(row: &[u32]) -> Self::Counter;
/// Breaks down the `Self::Counter` type into a u32 array for setting the
/// block pos.
fn set_block_pos(row: &mut [u32], pos: Self::Counter);
/// A helper method for calculating the remaining blocks using these types
fn remaining_blocks(block_pos: Self::Counter) -> Option<usize>;
}
/// IETF ChaCha configuration to use a 32-bit counter and 96-bit nonce.
#[derive(Clone, Copy, Debug)]
pub enum Ietf {}
impl sealed::Sealed for Ietf {}
impl Variant for Ietf {
type Counter = u32;
#[inline(always)]
fn get_block_pos(row: &[u32]) -> u32 {
row[0]
}
#[inline(always)]
fn set_block_pos(row: &mut [u32], pos: u32) {
row[0] = pos;
}
#[inline(always)]
fn remaining_blocks(block_pos: u32) -> Option<usize> {
let remaining = u32::MAX - block_pos;
remaining.try_into().ok()
}
}
/// DJB variant specific features: 64-bit counter and 64-bit nonce.
#[cfg(any(feature = "legacy", feature = "rng"))]
#[derive(Clone, Copy, Debug)]
pub enum Legacy {}
#[cfg(any(feature = "legacy", feature = "rng"))]
impl sealed::Sealed for Legacy {}
#[cfg(any(feature = "legacy", feature = "rng"))]
impl Variant for Legacy {
type Counter = u64;
#[inline(always)]
fn get_block_pos(row: &[u32]) -> u64 {
(u64::from(row[1]) << 32) | u64::from(row[0])
}
#[inline(always)]
fn set_block_pos(row: &mut [u32], pos: u64) {
row[0] = (pos & 0xFFFF_FFFF) as u32;
row[1] = (pos >> 32) as u32;
}
#[inline(always)]
fn remaining_blocks(block_pos: u64) -> Option<usize> {
let remaining = u64::MAX - block_pos;
remaining.try_into().ok()
}
}
+179
View File
@@ -0,0 +1,179 @@
//! XChaCha is an extended nonce variant of ChaCha
use crate::{
CONSTANTS, ChaChaCore, Key, R8, R12, R20, Rounds, STATE_WORDS, quarter_round, variants::Ietf,
};
use cipher::{
BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore,
StreamCipherCoreWrapper, StreamCipherSeekCore,
array::Array,
consts::{U4, U16, U24, U32, U64},
};
#[cfg(feature = "zeroize")]
use zeroize::ZeroizeOnDrop;
/// Nonce type used by XChaCha variants.
pub type XNonce = Array<u8, U24>;
/// XChaCha is a ChaCha20 variant with an extended 192-bit (24-byte) nonce.
///
/// The construction is an adaptation of the same techniques used by
/// XChaCha as described in the paper "Extending the Salsa20 Nonce",
/// applied to the 96-bit nonce variant of ChaCha20, and derive a
/// separate subkey/nonce for each extended nonce:
///
/// <https://cr.yp.to/snuffle/xsalsa-20081128.pdf>
///
/// No authoritative specification exists for XChaCha20, however the
/// construction has "rough consensus and running code" in the form of
/// several interoperable libraries and protocols (e.g. libsodium, WireGuard)
/// and is documented in an (expired) IETF draft:
///
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha>
pub type XChaCha20 = StreamCipherCoreWrapper<XChaChaCore<R20>>;
/// XChaCha12 stream cipher (reduced-round variant of [`XChaCha20`] with 12 rounds)
pub type XChaCha12 = StreamCipherCoreWrapper<XChaChaCore<R12>>;
/// XChaCha8 stream cipher (reduced-round variant of [`XChaCha20`] with 8 rounds)
pub type XChaCha8 = StreamCipherCoreWrapper<XChaChaCore<R8>>;
/// The XChaCha core function.
#[derive(Debug)]
pub struct XChaChaCore<R: Rounds>(ChaChaCore<R, Ietf>);
impl<R: Rounds> KeySizeUser for XChaChaCore<R> {
type KeySize = U32;
}
impl<R: Rounds> IvSizeUser for XChaChaCore<R> {
type IvSize = U24;
}
impl<R: Rounds> BlockSizeUser for XChaChaCore<R> {
type BlockSize = U64;
}
impl<R: Rounds> KeyIvInit for XChaChaCore<R> {
fn new(key: &Key, iv: &XNonce) -> Self {
#[allow(clippy::unwrap_used)]
let subkey = hchacha::<R>(key, iv[..16].as_ref().try_into().unwrap());
let mut nonce = [0u8; 12];
// first 4 bytes are 0, last 8 bytes are last 8 from the iv
// according to draft-arciszewski-xchacha-03
nonce[4..].copy_from_slice(&iv[16..]);
Self(ChaChaCore::<R, Ietf>::new_internal(subkey.as_ref(), &nonce))
}
}
impl<R: Rounds> StreamCipherCore for XChaChaCore<R> {
#[inline(always)]
fn remaining_blocks(&self) -> Option<usize> {
self.0.remaining_blocks()
}
#[inline(always)]
fn process_with_backend(&mut self, f: impl StreamCipherClosure<BlockSize = Self::BlockSize>) {
self.0.process_with_backend(f);
}
}
impl<R: Rounds> StreamCipherSeekCore for XChaChaCore<R> {
type Counter = u32;
#[inline(always)]
fn get_block_pos(&self) -> u32 {
self.0.get_block_pos()
}
#[inline(always)]
fn set_block_pos(&mut self, pos: u32) {
self.0.set_block_pos(pos);
}
}
#[cfg(feature = "zeroize")]
impl<R: Rounds> ZeroizeOnDrop for XChaChaCore<R> {}
/// The HChaCha function: adapts the ChaCha core function in the same
/// manner that HSalsa adapts the Salsa function.
///
/// HChaCha takes 512-bits of input:
///
/// - Constants: `u32` x 4
/// - Key: `u32` x 8
/// - Nonce: `u32` x 4
///
/// It produces 256-bits of output suitable for use as a ChaCha key
///
/// For more information on HSalsa on which HChaCha is based, see:
///
/// <http://cr.yp.to/snuffle/xsalsa-20110204.pdf>
#[must_use]
pub fn hchacha<R: Rounds>(key: &Key, input: &Array<u8, U16>) -> Array<u8, U32> {
let mut state = [0u32; STATE_WORDS];
state[..4].copy_from_slice(&CONSTANTS);
// TODO(tarcieri): use `[T]::as_chunks` when MSRV 1.88
let key_chunks = Array::<u8, U4>::slice_as_chunks(key).0;
for (v, chunk) in state[4..12].iter_mut().zip(key_chunks) {
*v = u32::from_le_bytes(chunk.0);
}
let input_chunks = Array::<u8, U4>::slice_as_chunks(input).0;
for (v, chunk) in state[12..16].iter_mut().zip(input_chunks) {
*v = u32::from_le_bytes(chunk.0);
}
// R rounds consisting of R/2 column rounds and R/2 diagonal rounds
for _ in 0..R::COUNT {
// column rounds
quarter_round(0, 4, 8, 12, &mut state);
quarter_round(1, 5, 9, 13, &mut state);
quarter_round(2, 6, 10, 14, &mut state);
quarter_round(3, 7, 11, 15, &mut state);
// diagonal rounds
quarter_round(0, 5, 10, 15, &mut state);
quarter_round(1, 6, 11, 12, &mut state);
quarter_round(2, 7, 8, 13, &mut state);
quarter_round(3, 4, 9, 14, &mut state);
}
let mut output = Array::default();
for (chunk, val) in output[..16].chunks_exact_mut(4).zip(&state[..4]) {
chunk.copy_from_slice(&val.to_le_bytes());
}
for (chunk, val) in output[16..].chunks_exact_mut(4).zip(&state[12..]) {
chunk.copy_from_slice(&val.to_le_bytes());
}
output
}
#[cfg(test)]
mod hchacha20_tests {
use super::*;
use hex_literal::hex;
/// Test vectors from:
/// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#section-2.2.1
#[test]
fn test_vector() {
const KEY: [u8; 32] = hex!(
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
);
const INPUT: [u8; 16] = hex!("000000090000004a0000000031415927");
const OUTPUT: [u8; 32] = hex!(
"82413b4227b27bfed30e42508a877d73"
"a0f9e4d58a74a853c12ec41326d3ecdc"
);
let actual = hchacha::<R20>(&KEY.into(), &INPUT.into());
assert_eq!(actual.as_slice(), &OUTPUT);
}
}