1use super::{overlapping, quic::Sample, Nonce};
17use crate::cpu;
18use cfg_if::cfg_if;
19
20cfg_if! {
21 if #[cfg(any(
22 all(target_arch = "aarch64", target_endian = "little"),
23 all(target_arch = "arm", target_endian = "little"),
24 target_arch = "x86",
25 target_arch = "x86_64"
26 ))] {
27 #[macro_use]
28 mod ffi;
29 #[cfg(any(target_arch = "x86", test))]
30 mod fallback;
31 } else {
32 mod fallback;
33 }
34}
35
36use crate::polyfill::ArraySplitMap;
37
38pub type Overlapping<'o> = overlapping::Overlapping<'o, u8>;
39
40#[derive(Clone)]
41pub struct Key {
42 words: [u32; KEY_LEN / 4],
43}
44
45impl Key {
46 pub(super) fn new(value: [u8; KEY_LEN]) -> Self {
47 Self {
48 words: value.array_split_map(u32::from_le_bytes),
49 }
50 }
51}
52
53impl Key {
54 #[inline]
57 pub(super) fn encrypt_single_block_with_ctr_0<const N: usize>(
58 &self,
59 nonce: Nonce,
60 in_out: &mut [u8; N],
61 cpu: cpu::Features,
62 ) -> Counter {
63 assert!(N <= BLOCK_LEN);
64 let (zero, one) = Counter::zero_one_less_safe(nonce);
65 self.encrypt(zero, in_out.as_mut().into(), cpu);
66 one
67 }
68
69 #[inline]
70 pub fn new_mask(&self, sample: Sample) -> [u8; 5] {
71 let cpu = cpu::features(); let (ctr, nonce) = sample.split_at(4);
73 let ctr = u32::from_le_bytes(ctr.try_into().unwrap());
74 let nonce = Nonce::assume_unique_for_key(nonce.try_into().unwrap());
75 let ctr = Counter::from_nonce_and_ctr(nonce, ctr);
76
77 let mut out: [u8; 5] = [0; 5];
78 self.encrypt(ctr, out.as_mut().into(), cpu);
79 out
80 }
81
82 #[inline(always)]
83 pub(super) fn encrypt(&self, counter: Counter, in_out: Overlapping<'_>, cpu: cpu::Features) {
84 cfg_if! {
85 if #[cfg(all(target_arch = "aarch64", target_endian = "little"))] {
86 use cpu::{GetFeature as _, arm::Neon};
87 const NEON_MIN_LEN: usize = 192 + 1;
88 if in_out.len() >= NEON_MIN_LEN {
89 if let Some(cpu) = cpu.get_feature() {
90 return chacha20_ctr32_ffi!(
91 unsafe { (NEON_MIN_LEN, Neon, Overlapping<'_>) => ChaCha20_ctr32_neon },
92 self, counter, in_out, cpu);
93 }
94 }
95 if in_out.len() >= 1 {
96 chacha20_ctr32_ffi!(
97 unsafe { (1, (), Overlapping<'_>) => ChaCha20_ctr32_nohw },
98 self, counter, in_out, ())
99 }
100 } else if #[cfg(all(target_arch = "arm", target_endian = "little"))] {
101 use cpu::{GetFeature as _, arm::Neon};
102 const NEON_MIN_LEN: usize = 192 + 1;
103 if in_out.len() >= NEON_MIN_LEN {
104 if let Some(cpu) = cpu.get_feature() {
105 return chacha20_ctr32_ffi!(
106 unsafe { (NEON_MIN_LEN, Neon, &mut [u8]) => ChaCha20_ctr32_neon },
107 self, counter, in_out.copy_within(), cpu);
108 }
109 }
110 if in_out.len() >= 1 {
111 chacha20_ctr32_ffi!(
112 unsafe { (1, (), &mut [u8]) => ChaCha20_ctr32_nohw },
113 self, counter, in_out.copy_within(), ())
114 }
115 } else if #[cfg(target_arch = "x86")] {
116 use cpu::{GetFeature as _, intel::Ssse3};
117 if in_out.len() >= 1 {
118 if let Some(cpu) = cpu.get_feature() {
119 chacha20_ctr32_ffi!(
120 unsafe { (1, Ssse3, &mut [u8]) => ChaCha20_ctr32_ssse3 },
121 self, counter, in_out.copy_within(), cpu)
122 } else {
123 let _: cpu::Features = cpu;
124 fallback::ChaCha20_ctr32(self, counter, in_out)
125 }
126 }
127 } else if #[cfg(target_arch = "x86_64")] {
128 use cpu::{GetFeature, intel::{Avx2, Ssse3}};
129 const SSE_MIN_LEN: usize = 128 + 1; if in_out.len() >= SSE_MIN_LEN {
131 let values = cpu.values();
132 if let Some(cpu) = values.get_feature() {
133 return chacha20_ctr32_ffi!(
134 unsafe { (SSE_MIN_LEN, Avx2, Overlapping<'_>) => ChaCha20_ctr32_avx2 },
135 self, counter, in_out, cpu);
136 }
137 if let Some(cpu) = values.get_feature() {
138 return chacha20_ctr32_ffi!(
139 unsafe { (SSE_MIN_LEN, Ssse3, Overlapping<'_>) =>
140 ChaCha20_ctr32_ssse3_4x },
141 self, counter, in_out, cpu);
142 }
143 }
144 if in_out.len() >= 1 {
145 chacha20_ctr32_ffi!(
146 unsafe { (1, (), Overlapping<'_>) => ChaCha20_ctr32_nohw },
147 self, counter, in_out, ())
148 }
149 } else {
150 let _: cpu::Features = cpu;
151 fallback::ChaCha20_ctr32(self, counter, in_out)
152 }
153 }
154 }
155
156 #[inline]
157 pub(super) fn words_less_safe(&self) -> &[u32; KEY_LEN / 4] {
158 &self.words
159 }
160}
161
162#[repr(transparent)]
164pub struct Counter([u32; 4]);
165
166impl Counter {
167 fn zero_one_less_safe(nonce: Nonce) -> (Self, Self) {
170 let ctr0 @ Self([_, n0, n1, n2]) = Self::from_nonce_and_ctr(nonce, 0);
171 let ctr1 = Self([1, n0, n1, n2]);
172 (ctr0, ctr1)
173 }
174
175 fn from_nonce_and_ctr(nonce: Nonce, ctr: u32) -> Self {
176 let [n0, n1, n2] = nonce.as_ref().array_split_map(u32::from_le_bytes);
177 Self([ctr, n0, n1, n2])
178 }
179
180 #[cfg(any(
183 test,
184 not(any(
185 all(target_arch = "aarch64", target_endian = "little"),
186 all(target_arch = "arm", target_endian = "little"),
187 target_arch = "x86_64"
188 ))
189 ))]
190 fn into_words_less_safe(self) -> [u32; 4] {
191 self.0
192 }
193}
194
195pub const KEY_LEN: usize = 32;
196
197const BLOCK_LEN: usize = 64;
198
199#[cfg(test)]
200mod tests {
201 extern crate alloc;
202
203 use super::{super::overlapping::IndexError, *};
204 use crate::error;
205 use crate::testutil as test;
206 use alloc::vec;
207
208 const MAX_ALIGNMENT_AND_OFFSET: (usize, usize) = (15, 259);
209 const MAX_ALIGNMENT_AND_OFFSET_SUBSET: (usize, usize) =
210 if cfg!(any(not(debug_assertions), feature = "slow_tests")) {
211 MAX_ALIGNMENT_AND_OFFSET
212 } else {
213 (0, 0)
214 };
215
216 #[test]
217 fn chacha20_test_default() {
218 let max_offset = if cfg!(any(
220 all(target_arch = "aarch64", target_endian = "little"),
221 all(target_arch = "arm", target_endian = "little"),
222 target_arch = "x86",
223 target_arch = "x86_64"
224 )) {
225 MAX_ALIGNMENT_AND_OFFSET
226 } else {
227 MAX_ALIGNMENT_AND_OFFSET_SUBSET
228 };
229 chacha20_test(max_offset, Key::encrypt);
230 }
231
232 #[test]
234 fn chacha20_test_fallback() {
235 chacha20_test(MAX_ALIGNMENT_AND_OFFSET_SUBSET, |key, ctr, in_out, _cpu| {
236 fallback::ChaCha20_ctr32(key, ctr, in_out)
237 });
238 }
239
240 fn chacha20_test(
248 max_alignment_and_offset: (usize, usize),
249 f: impl for<'k, 'o> Fn(&'k Key, Counter, Overlapping<'o>, cpu::Features),
250 ) {
251 let cpu = cpu::features();
252
253 let mut buf = vec![0u8; 1300];
255
256 test::run(
257 test_vector_file!("chacha_tests.txt"),
258 move |section, test_case| {
259 assert_eq!(section, "");
260
261 let key = test_case.consume_bytes("Key");
262 let key: &[u8; KEY_LEN] = key.as_slice().try_into()?;
263 let key = Key::new(*key);
264
265 let ctr = test_case.consume_usize("Ctr");
266 let nonce = test_case.consume_bytes("Nonce");
267 let input = test_case.consume_bytes("Input");
268 let output = test_case.consume_bytes("Output");
269
270 for len in 0..=input.len() {
274 #[allow(clippy::cast_possible_truncation)]
275 chacha20_test_case_inner(
276 &key,
277 &nonce,
278 ctr as u32,
279 &input[..len],
280 &output[..len],
281 &mut buf,
282 max_alignment_and_offset,
283 cpu,
284 &f,
285 );
286 }
287
288 Ok(())
289 },
290 );
291 }
292
293 fn chacha20_test_case_inner(
294 key: &Key,
295 nonce: &[u8],
296 ctr: u32,
297 input: &[u8],
298 expected: &[u8],
299 buf: &mut [u8],
300 (max_alignment, max_offset): (usize, usize),
301 cpu: cpu::Features,
302 f: &impl for<'k, 'o> Fn(&'k Key, Counter, Overlapping<'o>, cpu::Features),
303 ) {
304 const ARBITRARY: u8 = 123;
305
306 for alignment in 0..=max_alignment {
307 buf[..alignment].fill(ARBITRARY);
308 let buf = &mut buf[alignment..];
309 for offset in 0..=max_offset {
310 let buf = &mut buf[..(offset + input.len())];
311 buf[..offset].fill(ARBITRARY);
312 let src = offset..;
313 buf[src.clone()].copy_from_slice(input);
314
315 let ctr = Counter::from_nonce_and_ctr(
316 Nonce::try_assume_unique_for_key(nonce).unwrap(),
317 ctr,
318 );
319 let in_out = Overlapping::new(buf, src)
320 .map_err(error::erase::<IndexError>)
321 .unwrap();
322 f(key, ctr, in_out, cpu);
323 assert_eq!(&buf[..input.len()], expected)
324 }
325 }
326 }
327}