scrypt_opt/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(
3    all(not(test), not(feature = "std"), not(target_arch = "wasm32")),
4    no_std
5)]
6#![cfg_attr(feature = "portable-simd", feature(portable_simd))]
7#![warn(missing_docs)]
8
9#[cfg(feature = "alloc")]
10extern crate alloc;
11
12#[rustfmt::skip]
13macro_rules! repeat4 {
14    ($i:ident, $c:block) => {
15        { let $i = 0; $c; }
16        { let $i = 1; $c; }
17        { let $i = 2; $c; }
18        { let $i = 3; $c; }
19    };
20}
21
22#[rustfmt::skip]
23macro_rules! repeat8 {
24    ($i:ident, $b:block) => {{
25        repeat4!(di, { let $i = di; $b });
26        repeat4!(di, { let $i = di + 4; $b });
27    }};
28}
29
30/// Re-export sha2
31pub use sha2;
32
33/// Re-export generic_array
34pub use generic_array;
35
36/// Algorithmic Self-Test (CAST)
37pub mod self_test;
38
39/// Memory utilities
40pub mod memory;
41
42/// Salsa20 kernels
43pub mod salsa20;
44
45/// SIMD utilities
46pub(crate) mod simd;
47
48/// PBKDF2-HMAC-SHA256 implementation (1 iteration special case)
49pub mod pbkdf2_1;
50
51/// Pipeline support
52pub mod pipeline;
53
54/// Multi-buffer SHA256 implementation
55#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
56pub(crate) mod sha2_mb;
57
58/// Runtime feature detection
59pub mod features;
60
61/// Compat APIs
62#[cfg(any(feature = "std", target_arch = "wasm32"))]
63pub mod compat;
64
65/// Fixed R buffer set
66pub mod fixed_r;
67
68use core::num::{NonZeroU8, NonZeroU32};
69
70use generic_array::typenum::{
71    B1, IsLess, IsLessOrEqual, PowerOfTwo, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13,
72    U14, U15, U16, U17, U18, U19, U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, U30, U31,
73    U4294967296, Unsigned,
74};
75
76use generic_array::{ArrayLength, GenericArray, typenum::NonZero};
77
78#[allow(unused_imports)]
79use crate::features::Feature as _;
80
81use crate::memory::Align64;
82use crate::salsa20::{BlockType, Salsa20};
83
84include!("block_mix.rs");
85
86#[cfg(target_arch = "x86_64")]
87cfg_if::cfg_if! {
88    if #[cfg(target_feature = "avx512f")] {
89        /// The default engine for this architecture that is guaranteed to be available
90        pub type DefaultEngine1 = salsa20::x86_64::BlockAvx512F;
91        /// The default engine for this architecture that is guaranteed to be available
92        pub type DefaultEngine2 = salsa20::x86_64::BlockAvx512FMb2;
93    } else if #[cfg(target_feature = "avx2")] {
94        /// The default engine for this architecture that is guaranteed to be available
95        pub type DefaultEngine1 = salsa20::x86_64::BlockAvx2;
96        /// The default engine for this architecture that is guaranteed to be available
97        pub type DefaultEngine2 = salsa20::x86_64::BlockAvx2Mb2;
98    } else if #[cfg(feature = "portable-simd")] {
99        /// The default engine for this architecture that is guaranteed to be available
100        pub type DefaultEngine1 = salsa20::BlockPortableSimd;
101        /// The default engine for this architecture that is guaranteed to be available
102        pub type DefaultEngine2 = salsa20::BlockPortableSimd2;
103    } else {
104        /// The default engine for this architecture that is guaranteed to be available
105        pub type DefaultEngine1 = salsa20::BlockScalar<U1>;
106        /// The default engine for this architecture that is guaranteed to be available
107        pub type DefaultEngine2 = salsa20::BlockScalar<U2>;
108    }
109}
110
111#[cfg(not(target_arch = "x86_64"))]
112cfg_if::cfg_if! {
113    if #[cfg(feature = "portable-simd")] {
114        /// The default engine for this architecture that is guaranteed to be available
115        pub type DefaultEngine1 = salsa20::BlockPortableSimd;
116        /// The default engine for this architecture that is guaranteed to be available
117        pub type DefaultEngine2 = salsa20::BlockPortableSimd2;
118    } else {
119        /// The default engine for this architecture that is guaranteed to be available
120        pub type DefaultEngine1 = salsa20::BlockScalar<U1>;
121        /// The default engine for this architecture that is guaranteed to be available
122        pub type DefaultEngine2 = salsa20::BlockScalar<U2>;
123    }
124}
125
126mod sealing {
127    pub trait Sealed {}
128}
129
130/// A trait for valid cost factors
131pub trait ValidCostFactor: Unsigned + NonZero + sealing::Sealed {
132    /// The output type
133    type Output: ArrayLength + PowerOfTwo + NonZero + IsLess<U4294967296, Output = B1>;
134
135    /// The minimum number of blocks required for a given Cost Factor (log2(N))
136    type MinimumBlocks: ArrayLength + NonZero + IsLessOrEqual<U4294967296, Output = B1>;
137}
138
139const MAX_CF: u8 = 31;
140const MAX_N: u32 = 1 << MAX_CF;
141
142macro_rules! impl_valid_cost_factor {
143    ($($base:ty),*) => {
144        $(
145            impl sealing::Sealed for $base {}
146            impl ValidCostFactor for $base {
147                type Output = <U1 as core::ops::Shl<$base>>::Output;
148                type MinimumBlocks = <<U1 as core::ops::Shl<$base>>::Output as core::ops::Add<U2>>::Output;
149            }
150        )*
151    };
152}
153
154impl_valid_cost_factor!(
155    U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18, U19, U20, U21,
156    U22, U23, U24, U25, U26, U27, U28, U29, U30, U31
157);
158
159/// Generalized RoMix interface with a runtime R value
160pub trait RoMix {
161    /// Perform the front part of the $RoMix$ operation
162    ///
163    /// Buffer must be at least 128 * r * (n + 1) bytes long.
164    fn ro_mix_front_ex<S: Salsa20<Lanes = U1>>(&mut self, r: NonZeroU32, cf: NonZeroU8);
165    /// Perform the back part of the $RoMix$ operation
166    ///
167    /// Buffer must be at least 128 * r * (n + 2) bytes long.
168    ///
169    /// Return: the raw salt output for the completed $RoMix$ operation
170    fn ro_mix_back_ex<S: Salsa20<Lanes = U1>>(&mut self, r: NonZeroU32, cf: NonZeroU8) -> &[u8];
171    /// Interleave the front and back parts of the $RoMix$ operation in two independent buffers
172    ///
173    /// Buffer must be at least 128 * r * (n + 2) bytes long.
174    ///
175    /// Return: the raw salt output for the completed $RoMix$ operation
176    fn ro_mix_interleaved_ex<'a, S: Salsa20<Lanes = U2>>(
177        &'a mut self,
178        front: &mut Self,
179        r: NonZeroU32,
180        cf: NonZeroU8,
181    ) -> &'a [u8];
182
183    /// Convenience method to get the input buffer for the $RoMix$ operation
184    ///
185    /// Always return the 128 * r bytes of the buffer
186    fn ro_mix_input_buffer(&mut self, r: NonZeroU32) -> &mut [u8];
187
188    /// Perform the front part of the $RoMix$ operation
189    ///
190    /// Buffer must be at least 128 * r * (n + 1) bytes long.
191    fn ro_mix_front(&mut self, r: NonZeroU32, cf: NonZeroU8) {
192        self.ro_mix_front_ex::<DefaultEngine1>(r, cf);
193    }
194    /// Perform the back part of the $RoMix$ operation
195    ///
196    /// Buffer must be at least 128 * r * (n + 2) bytes long.
197    ///
198    /// Return: the raw salt output for the completed $RoMix$ operation
199    fn ro_mix_back(&mut self, r: NonZeroU32, cf: NonZeroU8) -> &[u8] {
200        self.ro_mix_back_ex::<DefaultEngine1>(r, cf)
201    }
202    /// Interleave the front and back parts of the $RoMix$ operation in two independent buffers
203    ///
204    /// Buffer must be at least 128 * r * (n + 2) bytes long.
205    ///
206    /// Return: the raw salt output for the completed $RoMix$ operation
207    fn ro_mix_interleaved(&mut self, front: &mut Self, r: NonZeroU32, cf: NonZeroU8) -> &[u8] {
208        self.ro_mix_interleaved_ex::<DefaultEngine2>(front, r, cf)
209    }
210}
211
212#[cfg_attr(
213    all(target_arch = "x86_64", not(target_feature = "avx2")),
214    scrypt_opt_derive::generate_target_variant("avx2")
215)]
216#[cfg_attr(
217    not(all(target_arch = "x86_64", not(target_feature = "avx2"))),
218    inline(always)
219)]
220fn ro_mix_front_ex_dyn<S: Salsa20<Lanes = U1>>(
221    v: &mut [Align64<fixed_r::Block<U1>>],
222    r: NonZeroU32,
223    cf: NonZeroU8,
224) {
225    let r = r.get() as usize;
226    let n = 1 << cf.get();
227    assert!(
228        v.len() >= r * (n + 1),
229        "ro_mix_front_ex: v.len() < r * (n + 1)"
230    );
231
232    // SAFETY: n is at least 1, v is at least r long
233    unsafe {
234        v.get_unchecked_mut(..r).iter_mut().for_each(|chunk| {
235            S::shuffle_in(
236                chunk
237                    .as_mut_ptr()
238                    .cast::<Align64<[u32; 16]>>()
239                    .as_mut()
240                    .unwrap(),
241            );
242            S::shuffle_in(
243                chunk
244                    .as_mut_ptr()
245                    .cast::<Align64<[u32; 16]>>()
246                    .add(1)
247                    .as_mut()
248                    .unwrap(),
249            );
250        });
251    }
252
253    for i in 0..n {
254        let [src, dst] = unsafe {
255            v.get_disjoint_unchecked_mut([(i * r)..((i + 1) * r), ((i + 1) * r)..((i + 2) * r)])
256        };
257        block_mix_dyn!(r; [<S> &*src => &mut *dst]);
258    }
259}
260
261#[cfg_attr(
262    all(target_arch = "x86_64", not(target_feature = "avx2")),
263    scrypt_opt_derive::generate_target_variant("avx2")
264)]
265#[cfg_attr(
266    not(all(target_arch = "x86_64", not(target_feature = "avx2"))),
267    inline(always)
268)]
269fn ro_mix_back_ex_dyn<S: Salsa20<Lanes = U1>>(
270    v: &mut [Align64<fixed_r::Block<U1>>],
271    r: NonZeroU32,
272    cf: NonZeroU8,
273) -> &[u8] {
274    let r = r.get() as usize;
275    let n = 1 << cf.get();
276    assert!(
277        v.len() >= r * (n + 2),
278        "pipeline_end_ex: v.len() < r * (n + 2)"
279    );
280
281    for _ in (0..n).step_by(2) {
282        let idx = unsafe {
283            v.as_ptr()
284                .add((n * r) as usize)
285                .cast::<u32>()
286                .add(r * 32 - 16)
287                .read()
288        } as usize;
289
290        let j = idx & (n - 1);
291
292        // SAFETY: the largest j value is n-1, so the largest index of the 3 is n+1, which is in bounds after the >=n+2 check
293        let [in0, in1, out] = unsafe {
294            v.get_disjoint_unchecked_mut([
295                (n * r)..((n + 1) * r),
296                (j * r)..((j + 1) * r),
297                ((n + 1) * r)..((n + 2) * r),
298            ])
299        };
300        block_mix_dyn!(r; [<S> &(&*in0, &*in1) => &mut *out]);
301        let idx2 = unsafe {
302            v.as_ptr()
303                .add(((n + 1) * r) as usize)
304                .cast::<u32>()
305                .add(r * 32 - 16)
306                .read()
307        } as usize;
308
309        let j2 = idx2 & (n - 1);
310
311        // SAFETY: the largest j2 value is n-1, so the largest index of the 3 is n+1, which is in bounds after the >=n+2 check
312        let [b, v, t] = unsafe {
313            v.get_disjoint_unchecked_mut([
314                (n * r)..((n + 1) * r),
315                (j2 * r)..((j2 + 1) * r),
316                ((n + 1) * r)..((n + 2) * r),
317            ])
318        };
319        block_mix_dyn!(r; [<S> &(&*v, &*t) => &mut *b]);
320    }
321
322    // SAFETY: n is at least 1, v is at least r * (n + 2) long
323    unsafe {
324        v.get_unchecked_mut(r * n..r * (n + 1))
325            .iter_mut()
326            .for_each(|chunk| {
327                S::shuffle_out(
328                    chunk
329                        .as_mut_ptr()
330                        .cast::<Align64<[u32; 16]>>()
331                        .as_mut()
332                        .unwrap(),
333                );
334                S::shuffle_out(
335                    chunk
336                        .as_mut_ptr()
337                        .cast::<Align64<[u32; 16]>>()
338                        .add(1)
339                        .as_mut()
340                        .unwrap(),
341                );
342            });
343
344        core::slice::from_raw_parts(v.as_ptr().add(r * n).cast::<u8>(), 128 * r)
345    }
346}
347
348#[cfg_attr(
349    all(target_arch = "x86_64", not(target_feature = "avx2")),
350    scrypt_opt_derive::generate_target_variant("avx2")
351)]
352#[cfg_attr(
353    not(all(target_arch = "x86_64", not(target_feature = "avx2"))),
354    inline(always)
355)]
356fn ro_mix_interleaved_ex_dyn<'a, S: Salsa20<Lanes = U2>>(
357    self_v: &mut [Align64<fixed_r::Block<U1>>],
358    other_v: &mut [Align64<fixed_r::Block<U1>>],
359    r: NonZeroU32,
360    cf: NonZeroU8,
361) -> &'a [u8] {
362    let r = r.get() as usize;
363    let n = 1 << cf.get();
364
365    assert!(
366        other_v.len() >= r * (n + 2),
367        "ro_mix_interleaved_ex: other_v.len() < r * (n + 2)"
368    );
369    assert!(
370        self_v.len() >= r * (n + 2),
371        "ro_mix_interleaved_ex: self_v.len() < r * (n + 2)"
372    );
373
374    // SAFETY: other_v is always 64-byte aligned
375    // SAFETY: other_v is at least r long
376    unsafe {
377        other_v.get_unchecked_mut(..r).iter_mut().for_each(|chunk| {
378            S::shuffle_in(
379                chunk
380                    .as_mut_ptr()
381                    .cast::<Align64<[u32; 16]>>()
382                    .as_mut()
383                    .unwrap(),
384            );
385            S::shuffle_in(
386                chunk
387                    .as_mut_ptr()
388                    .cast::<Align64<[u32; 16]>>()
389                    .add(1)
390                    .as_mut()
391                    .unwrap(),
392            );
393        });
394    }
395
396    for i in (0..n).step_by(2) {
397        // SAFETY: the largest i value is n-1, so the largest index is n+1, which is in bounds after the >=n+2 check
398        let [src, middle, dst] = unsafe {
399            other_v.get_disjoint_unchecked_mut([
400                (i * r)..((i + 1) * r),
401                ((i + 1) * r)..((i + 2) * r),
402                ((i + 2) * r)..((i + 3) * r),
403            ])
404        };
405
406        {
407            // Self: Compute T <- BlockMix(B ^ V[j])
408            // Other: Compute V[i+1] <- BlockMix(V[i])
409            let idx = unsafe {
410                self_v
411                    .as_ptr()
412                    .add((n * r) as usize)
413                    .cast::<u32>()
414                    .add(r * 32 - 16)
415                    .read()
416            } as usize;
417
418            let j = idx & (n - 1);
419
420            let [in0, in1, out] = unsafe {
421                self_v.get_disjoint_unchecked_mut([
422                    (j * r)..((j + 1) * r),
423                    (n * r)..((n + 1) * r),
424                    ((n + 1) * r)..((n + 2) * r),
425                ])
426            };
427
428            block_mix_dyn!(r; [<S> &&*src => &mut *middle, <S> &(&*in0, &*in1) => &mut *out]);
429        }
430
431        {
432            // Self: Compute B <- BlockMix(T ^ V[j'])
433            // Other: Compute V[i+2] <- BlockMix(V[i+1]) on last iteration it "naturally overflows" to V[n], so let B = V[n]
434            let idx2 = unsafe {
435                self_v
436                    .as_ptr()
437                    .add(((n + 1) * r) as usize)
438                    .cast::<u32>()
439                    .add(r * 32 - 16)
440                    .read()
441            } as usize;
442
443            let j2 = idx2 & (n - 1);
444            let [self_b, self_v, self_t] = unsafe {
445                self_v.get_disjoint_unchecked_mut([
446                    (n * r)..((n + 1) * r),
447                    (j2 * r)..((j2 + 1) * r),
448                    ((n + 1) * r)..((n + 2) * r),
449                ])
450            };
451
452            block_mix_dyn!(r; [<S> &*middle => &mut *dst, <S> &(&*self_v, &*self_t) => &mut *self_b]);
453        }
454    }
455    // SAFETY: n is at least 1, self_v is at least r * (n + 2) long
456    unsafe {
457        self_v
458            .get_unchecked_mut(r * n..r * (n + 1))
459            .iter_mut()
460            .for_each(|chunk| {
461                S::shuffle_out(
462                    chunk
463                        .as_mut_ptr()
464                        .cast::<Align64<[u32; 16]>>()
465                        .as_mut()
466                        .unwrap(),
467                );
468                S::shuffle_out(
469                    chunk
470                        .as_mut_ptr()
471                        .cast::<Align64<[u32; 16]>>()
472                        .add(1)
473                        .as_mut()
474                        .unwrap(),
475                );
476            });
477
478        core::slice::from_raw_parts(self_v.as_ptr().add(r * n).cast::<u8>(), 128 * r)
479    }
480}
481
482impl<Q: AsRef<[Align64<fixed_r::Block<U1>>]> + AsMut<[Align64<fixed_r::Block<U1>>]>> RoMix for Q {
483    fn ro_mix_input_buffer(&mut self, r: NonZeroU32) -> &mut [u8] {
484        let r = r.get() as usize;
485        let v = self.as_mut();
486        assert!(v.len() >= r, "ro_mix_input_buffer: v.len() <  r");
487        unsafe { core::slice::from_raw_parts_mut(v.as_mut_ptr().cast::<u8>(), 128 * r) }
488    }
489
490    fn ro_mix_front_ex<S: Salsa20<Lanes = U1>>(&mut self, r: NonZeroU32, cf: NonZeroU8) {
491        let v = self.as_mut();
492
493        #[cfg(all(target_arch = "x86_64", not(target_feature = "avx2")))]
494        {
495            if features::Avx2.check() {
496                unsafe { ro_mix_front_ex_dyn_avx2::<salsa20::x86_64::BlockAvx2>(v, r, cf) }
497                return;
498            }
499        }
500
501        ro_mix_front_ex_dyn::<S>(v, r, cf)
502    }
503
504    fn ro_mix_back_ex<S: Salsa20<Lanes = U1>>(&mut self, r: NonZeroU32, cf: NonZeroU8) -> &[u8] {
505        let v = self.as_mut();
506
507        #[cfg(all(target_arch = "x86_64", not(target_feature = "avx2")))]
508        {
509            if features::Avx2.check() {
510                return unsafe { ro_mix_back_ex_dyn_avx2::<salsa20::x86_64::BlockAvx2>(v, r, cf) };
511            }
512        }
513
514        ro_mix_back_ex_dyn::<S>(v, r, cf)
515    }
516
517    fn ro_mix_interleaved_ex<'a, S: Salsa20<Lanes = U2>>(
518        &'a mut self,
519        front: &mut Self,
520        r: NonZeroU32,
521        cf: NonZeroU8,
522    ) -> &'a [u8] {
523        let self_v = self.as_mut();
524        let other_v = front.as_mut();
525
526        #[cfg(all(target_arch = "x86_64", not(target_feature = "avx2")))]
527        {
528            if features::Avx2.check() {
529                return unsafe {
530                    ro_mix_interleaved_ex_dyn_avx2::<salsa20::x86_64::BlockAvx2Mb2>(
531                        self_v, other_v, r, cf,
532                    )
533                };
534            }
535        }
536
537        ro_mix_interleaved_ex_dyn::<S>(self_v, other_v, r, cf)
538    }
539}
540
541/// Trait for loading a block from a buffer
542pub trait ScryptBlockMixInput<'a, B: BlockType> {
543    /// Load a block from the buffer
544    unsafe fn load(&self, word_idx: usize) -> B;
545}
546
547impl<'a, B: BlockType> ScryptBlockMixInput<'a, B> for &'a [Align64<fixed_r::Block<U1>>] {
548    #[inline(always)]
549    unsafe fn load(&self, word_idx: usize) -> B {
550        unsafe { B::read_from_ptr(self.as_ptr().cast::<[u8; 64]>().add(word_idx).cast()) }
551    }
552}
553
554impl<'a, B: BlockType, Lhs: ScryptBlockMixInput<'a, B>, Rhs: ScryptBlockMixInput<'a, B>>
555    ScryptBlockMixInput<'a, B> for (Lhs, Rhs)
556{
557    #[inline(always)]
558    unsafe fn load(&self, word_idx: usize) -> B {
559        let mut x0 = unsafe { self.0.load(word_idx) };
560        let x1 = unsafe { self.1.load(word_idx) };
561        x0.xor_with(x1);
562        x0
563    }
564}
565
566/// Trait for storing a block to a buffer
567pub trait ScryptBlockMixOutput<'a, R: ArrayLength, B: BlockType> {
568    /// Store even-numbered words
569    fn store_even(&mut self, word_idx: usize, value: B);
570    /// Store odd-numbered words
571    fn store_odd(&mut self, word_idx: usize, value: B);
572}
573
574impl<
575    'a,
576    R: ArrayLength,
577    B: BlockType,
578    U: ScryptBlockMixOutput<'a, R, B>,
579    V: ScryptBlockMixOutput<'a, R, B>,
580> ScryptBlockMixOutput<'a, R, B> for (U, V)
581{
582    #[inline(always)]
583    fn store_even(&mut self, word_idx: usize, value: B) {
584        self.0.store_even(word_idx, value);
585        self.1.store_even(word_idx, value);
586    }
587    #[inline(always)]
588    fn store_odd(&mut self, word_idx: usize, value: B) {
589        self.0.store_odd(word_idx, value);
590        self.1.store_odd(word_idx, value);
591    }
592}
593
594#[cfg(test)]
595mod tests {
596    use generic_array::typenum::{U1, U2, U4, U8, U16};
597
598    use super::*;
599    use crate::{
600        fixed_r::{Block, BufferSet},
601        pbkdf2_1::Pbkdf2HmacSha256State,
602        pipeline::PipelineContext,
603    };
604
605    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
606    fn test_ro_mix_cas_zmm<R: ArrayLength + NonZero>() {
607        const CF: u8 = 8;
608
609        let password = b"password";
610        let hmac = Pbkdf2HmacSha256State::new(password);
611        let salt = b"salt";
612        let mut expected = [0u8; 64];
613
614        let params = scrypt::Params::new(CF, R::U32, 1, 64).unwrap();
615
616        scrypt::scrypt(password, salt, &params, &mut expected).expect("scrypt failed");
617
618        let mut buffers = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
619        buffers.set_input(&hmac, salt);
620
621        buffers.scrypt_ro_mix_ex_zmm::<salsa20::x86_64::BlockAvx512F>();
622
623        let mut output = [0u8; 64];
624
625        buffers.extract_output(&hmac, &mut output);
626
627        assert_eq!(output, expected);
628    }
629
630    #[test]
631    fn test_pipeline() {
632        for cf in 1..=8 {
633            let mut buffers0 = BufferSet::<_, U1>::new_boxed(cf.try_into().unwrap());
634            let mut buffers1 = BufferSet::<_, U1>::new_boxed(cf.try_into().unwrap());
635
636            let input_passwords = [
637                b"password0".as_slice(),
638                b"password1".as_slice(),
639                b"password2".as_slice(),
640                b"password3".as_slice(),
641                b"password4".as_slice(),
642                b"password5".as_slice(),
643                b"password6".as_slice(),
644                b"password7".as_slice(),
645                b"password8".as_slice(),
646                b"password9".as_slice(),
647                b"password10".as_slice(),
648            ];
649
650            let input_salts = [
651                b"salt0".as_slice(),
652                b"salt1".as_slice(),
653                b"salt2".as_slice(),
654                b"salt3".as_slice(),
655                b"salt4".as_slice(),
656                b"salt5".as_slice(),
657                b"salt6".as_slice(),
658                b"salt7".as_slice(),
659                b"salt8".as_slice(),
660                b"salt9".as_slice(),
661                b"salt10".as_slice(),
662            ];
663
664            struct Context<'a> {
665                params: scrypt::Params,
666                i: usize,
667                total: usize,
668                password: &'a [u8],
669                salt: &'a [u8],
670            }
671
672            impl<'a, R: ArrayLength + NonZero> PipelineContext<usize, Vec<Align64<Block<R>>>, R, ()>
673                for Context<'a>
674            {
675                fn begin(
676                    &mut self,
677                    _ratchet: &mut usize,
678                    buffer_set: &mut BufferSet<Vec<Align64<Block<R>>>, R>,
679                ) {
680                    buffer_set.set_input(&Pbkdf2HmacSha256State::new(self.password), self.salt);
681                }
682
683                fn drain(
684                    self,
685                    ratchet: &mut usize,
686                    buffer_set: &mut BufferSet<Vec<Align64<Block<R>>>, R>,
687                ) -> Option<()> {
688                    assert_eq!(*ratchet, self.i, "output should be in order");
689                    assert!(*ratchet < self.total, "should have processed all passwords");
690                    *ratchet += 1;
691                    let mut output = [0u8; 64];
692                    buffer_set
693                        .extract_output(&Pbkdf2HmacSha256State::new(self.password), &mut output);
694                    let mut expected = [0u8; 64];
695
696                    scrypt::scrypt(self.password, self.salt, &self.params, &mut expected)
697                        .expect("scrypt failed");
698
699                    assert_eq!(output, expected, "unexpected output at round {}", self.i);
700
701                    if *ratchet == self.total {
702                        Some(())
703                    } else {
704                        None
705                    }
706                }
707            }
708
709            let params = scrypt::Params::new(cf, U1::U32, 1, 64).unwrap();
710
711            // test all possible input counts
712            for test_len in 0..input_passwords.len() {
713                let mut ratchet = 0;
714                let ret = buffers0.pipeline(
715                    &mut buffers1,
716                    input_passwords
717                        .iter()
718                        .zip(input_salts.iter())
719                        .enumerate()
720                        .map(|(i, (p, s))| Context {
721                            params,
722                            i,
723                            total: test_len,
724                            password: p,
725                            salt: s,
726                        })
727                        .take(test_len),
728                    &mut ratchet,
729                );
730
731                assert_eq!(
732                    ret.is_some(),
733                    test_len > 0,
734                    "should have processed all passwords"
735                );
736            }
737        }
738    }
739
740    fn test_ro_mix_cas<R: ArrayLength + NonZero>() {
741        const CF: u8 = 8;
742
743        let password = b"password";
744        let salt = b"salt";
745        let mut expected = [0u8; 64];
746
747        let params = scrypt::Params::new(CF, R::U32, 1, 64).unwrap();
748
749        scrypt::scrypt(password, salt, &params, &mut expected).expect("scrypt failed");
750
751        let mut buffers = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
752
753        assert_eq!(buffers.n(), 1 << CF);
754
755        buffers.set_input(&Pbkdf2HmacSha256State::new(password), salt);
756
757        buffers.scrypt_ro_mix();
758
759        let mut output = [0u8; 64];
760
761        buffers.extract_output(&Pbkdf2HmacSha256State::new(password), &mut output);
762
763        assert_eq!(output, expected);
764    }
765
766    fn test_ro_mix_cas_ex<R: ArrayLength + NonZero, S: Salsa20<Lanes = U1>>() {
767        const CF: u8 = 8;
768
769        let password = b"password";
770        let salt = b"salt";
771        let mut expected = [0u8; 64];
772
773        let params = scrypt::Params::new(CF, R::U32, 1, 64).unwrap();
774
775        scrypt::scrypt(password, salt, &params, &mut expected).expect("scrypt failed");
776
777        let mut buffers = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
778
779        let mut buffer_dyn = vec![Default::default(); R::USIZE * ((1 << CF) + 2)];
780
781        assert_eq!(buffers.n(), 1 << CF);
782
783        buffers.set_input(&Pbkdf2HmacSha256State::new(password), salt);
784        buffer_dyn
785            .ro_mix_input_buffer(R::U32.try_into().unwrap())
786            .copy_from_slice(buffers.input_buffer().as_slice());
787
788        buffer_dyn.ro_mix_front_ex::<S>(R::U32.try_into().unwrap(), CF.try_into().unwrap());
789        buffers.ro_mix_front_ex::<S>();
790        let dyn_output =
791            buffer_dyn.ro_mix_back_ex::<S>(R::U32.try_into().unwrap(), CF.try_into().unwrap());
792        buffers.ro_mix_back_ex::<S>();
793
794        assert_eq!(dyn_output, buffers.raw_salt_output().as_slice());
795
796        let mut output = [0u8; 64];
797
798        buffers.extract_output(&Pbkdf2HmacSha256State::new(password), &mut output);
799
800        assert_eq!(output, expected);
801    }
802
803    fn test_ro_mix_cas_interleaved<R: ArrayLength + NonZero>() {
804        const CF: u8 = 8;
805
806        let passwords = [
807            b"password0".as_slice(),
808            b"password1".as_slice(),
809            b"password2".as_slice(),
810            b"password3".as_slice(),
811            b"password4".as_slice(),
812            b"password5".as_slice(),
813            b"password6".as_slice(),
814            b"password7".as_slice(),
815            b"password8".as_slice(),
816            b"password9".as_slice(),
817            b"password10".as_slice(),
818            b"password11".as_slice(),
819            b"password12".as_slice(),
820            b"password13".as_slice(),
821            b"password14".as_slice(),
822            b"password15".as_slice(),
823        ];
824
825        let mut expected = [[0u8; 64]; 16];
826
827        for (i, password) in passwords.iter().enumerate() {
828            let params = scrypt::Params::new(CF, R::U32, 1, 64).unwrap();
829            scrypt::scrypt(password, b"salt", &params, &mut expected[i]).expect("scrypt failed");
830        }
831
832        let mut buffers0 = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
833        let mut buffers1 = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
834
835        let mut output = [0u8; 64];
836        buffers0.set_input(&Pbkdf2HmacSha256State::new(passwords[0]), b"salt");
837        buffers1.set_input(&Pbkdf2HmacSha256State::new(passwords[1]), b"salt");
838        buffers0.ro_mix_front();
839        for i in 2..16 {
840            buffers0.ro_mix_interleaved(&mut buffers1);
841            buffers0.extract_output(&Pbkdf2HmacSha256State::new(passwords[i - 2]), &mut output);
842            assert_eq!(output, expected[i - 2], "error at round {}", i);
843            core::hint::black_box(&mut buffers0);
844            (buffers0, buffers1) = (buffers1, buffers0);
845            buffers1.set_input(&Pbkdf2HmacSha256State::new(passwords[i]), b"salt");
846        }
847        buffers0.ro_mix_back();
848        buffers1.scrypt_ro_mix();
849        buffers0.extract_output(&Pbkdf2HmacSha256State::new(passwords[14]), &mut output);
850        assert_eq!(output, expected[14]);
851        buffers1.extract_output(&Pbkdf2HmacSha256State::new(passwords[15]), &mut output);
852        assert_eq!(output, expected[15]);
853    }
854
855    fn test_ro_mix_cas_interleaved_ex<
856        R: ArrayLength + NonZero,
857        S1: Salsa20<Lanes = U1>,
858        S2: Salsa20<Lanes = U2>,
859    >() {
860        const CF: u8 = 8;
861
862        let passwords = [
863            b"password0".as_slice(),
864            b"password1".as_slice(),
865            b"password2".as_slice(),
866            b"password3".as_slice(),
867            b"password4".as_slice(),
868            b"password5".as_slice(),
869            b"password6".as_slice(),
870            b"password7".as_slice(),
871            b"password8".as_slice(),
872            b"password9".as_slice(),
873            b"password10".as_slice(),
874            b"password11".as_slice(),
875            b"password12".as_slice(),
876            b"password13".as_slice(),
877            b"password14".as_slice(),
878            b"password15".as_slice(),
879        ];
880
881        let mut expected = [[0u8; 64]; 16];
882
883        for (i, password) in passwords.iter().enumerate() {
884            let params = scrypt::Params::new(CF, R::U32, 1, 64).unwrap();
885            scrypt::scrypt(password, b"salt", &params, &mut expected[i]).expect("scrypt failed");
886        }
887
888        let mut buffers0 = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
889        let mut buffers1 = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
890        let mut buffers0_dyn = vec![Default::default(); R::USIZE * ((1 << CF) + 2)];
891        let mut buffers1_dyn = vec![Default::default(); R::USIZE * ((1 << CF) + 2)];
892
893        let mut output = [0u8; 64];
894        buffers0.set_input(&Pbkdf2HmacSha256State::new(passwords[0]), b"salt");
895        buffers1.set_input(&Pbkdf2HmacSha256State::new(passwords[1]), b"salt");
896        buffers0_dyn
897            .ro_mix_input_buffer(R::U32.try_into().unwrap())
898            .copy_from_slice(buffers0.input_buffer().as_slice());
899        buffers1_dyn
900            .ro_mix_input_buffer(R::U32.try_into().unwrap())
901            .copy_from_slice(buffers1.input_buffer().as_slice());
902
903        buffers0.ro_mix_front_ex::<S1>();
904        buffers0_dyn.ro_mix_front_ex::<S1>(R::U32.try_into().unwrap(), CF.try_into().unwrap());
905        for i in 2..16 {
906            buffers0.ro_mix_interleaved_ex::<S2>(&mut buffers1);
907            let dyn_salt_output = buffers0_dyn.ro_mix_interleaved_ex::<S2>(
908                &mut buffers1_dyn,
909                R::U32.try_into().unwrap(),
910                CF.try_into().unwrap(),
911            );
912            buffers0.extract_output(&Pbkdf2HmacSha256State::new(passwords[i - 2]), &mut output);
913            assert_eq!(dyn_salt_output, buffers0.raw_salt_output().as_slice());
914
915            assert_eq!(output, expected[i - 2], "error at round {}", i);
916            core::hint::black_box(&mut buffers0);
917            (buffers0, buffers1) = (buffers1, buffers0);
918            (buffers0_dyn, buffers1_dyn) = (buffers1_dyn, buffers0_dyn);
919            buffers1.set_input(&Pbkdf2HmacSha256State::new(passwords[i]), b"salt");
920            buffers1_dyn
921                .ro_mix_input_buffer(R::U32.try_into().unwrap())
922                .copy_from_slice(buffers1.input_buffer().as_slice());
923        }
924        buffers0.ro_mix_back_ex::<S1>();
925        let dyn_salt_output =
926            buffers0_dyn.ro_mix_back_ex::<S1>(R::U32.try_into().unwrap(), CF.try_into().unwrap());
927        assert_eq!(dyn_salt_output, buffers0.raw_salt_output().as_slice());
928
929        buffers1.scrypt_ro_mix();
930        buffers1_dyn.ro_mix_front_ex::<S1>(R::U32.try_into().unwrap(), CF.try_into().unwrap());
931        let dyn_salt_output =
932            buffers1_dyn.ro_mix_back_ex::<S1>(R::U32.try_into().unwrap(), CF.try_into().unwrap());
933        assert_eq!(dyn_salt_output, buffers1.raw_salt_output().as_slice());
934
935        buffers0.extract_output(&Pbkdf2HmacSha256State::new(passwords[14]), &mut output);
936        assert_eq!(output, expected[14]);
937        buffers1.extract_output(&Pbkdf2HmacSha256State::new(passwords[15]), &mut output);
938        assert_eq!(output, expected[15]);
939    }
940
941    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
942    fn test_ro_mix_cas_interleaved_zmm<R: ArrayLength + NonZero>() {
943        const CF: u8 = 8;
944
945        let passwords = [
946            b"password0".as_slice(),
947            b"password1".as_slice(),
948            b"password2".as_slice(),
949            b"password3".as_slice(),
950            b"password4".as_slice(),
951            b"password5".as_slice(),
952            b"password6".as_slice(),
953            b"password7".as_slice(),
954            b"password8".as_slice(),
955            b"password9".as_slice(),
956            b"password10".as_slice(),
957            b"password11".as_slice(),
958            b"password12".as_slice(),
959            b"password13".as_slice(),
960            b"password14".as_slice(),
961            b"password15".as_slice(),
962        ];
963
964        let hmacs: [Pbkdf2HmacSha256State; 16] =
965            core::array::from_fn(|i| Pbkdf2HmacSha256State::new(passwords[i]));
966
967        let mut expected = [[0u8; 64]; 16];
968
969        for (i, password) in passwords.iter().enumerate() {
970            let params = scrypt::Params::new(CF, R::U32, 1, 64).unwrap();
971            scrypt::scrypt(password, b"salt", &params, &mut expected[i]).expect("scrypt failed");
972        }
973
974        let mut buffers0 = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
975        let mut buffers1 = BufferSet::<_, R>::new_boxed(CF.try_into().unwrap());
976
977        let mut output = [0u8; 64];
978        buffers0.set_input(&hmacs[0], b"salt");
979        buffers1.set_input(&hmacs[1], b"salt");
980        buffers0.ro_mix_front();
981        for i in 2..16 {
982            buffers0.ro_mix_interleaved_ex_zmm::<salsa20::x86_64::BlockAvx512FMb2>(&mut buffers1);
983            buffers0.extract_output(&hmacs[i - 2], &mut output);
984            assert_eq!(output, expected[i - 2], "error at round {}", i);
985            core::hint::black_box(&mut buffers0);
986            (buffers0, buffers1) = (buffers1, buffers0);
987            buffers1.set_input(&hmacs[i], b"salt");
988        }
989        buffers0.ro_mix_back();
990        buffers1.scrypt_ro_mix();
991        buffers0.extract_output(&hmacs[14], &mut output);
992        assert_eq!(output, expected[14]);
993        buffers1.extract_output(&hmacs[15], &mut output);
994        assert_eq!(output, expected[15]);
995    }
996
997    macro_rules! write_test {
998        ($name:ident, $test:ident, $($generic:ty),* $(,)?) => {
999            #[test]
1000            fn $name() {
1001                $test::<$($generic),*>();
1002            }
1003        };
1004    }
1005
1006    // tests for whatever is the default/publicly visible version
1007    write_test!(test_ro_mix_cas_1, test_ro_mix_cas, U1);
1008    write_test!(test_ro_mix_cas_2, test_ro_mix_cas, U2);
1009    write_test!(test_ro_mix_cas_4, test_ro_mix_cas, U4);
1010    write_test!(test_ro_mix_cas_8, test_ro_mix_cas, U8);
1011    write_test!(test_ro_mix_cas_16, test_ro_mix_cas, U16);
1012
1013    write_test!(
1014        test_ro_mix_cas_interleaved_1,
1015        test_ro_mix_cas_interleaved,
1016        U1
1017    );
1018
1019    write_test!(
1020        test_ro_mix_cas_interleaved_2,
1021        test_ro_mix_cas_interleaved,
1022        U2
1023    );
1024
1025    write_test!(
1026        test_ro_mix_cas_interleaved_4,
1027        test_ro_mix_cas_interleaved,
1028        U4
1029    );
1030
1031    write_test!(
1032        test_ro_mix_cas_interleaved_8,
1033        test_ro_mix_cas_interleaved,
1034        U8
1035    );
1036
1037    write_test!(
1038        test_ro_mix_cas_interleaved_16,
1039        test_ro_mix_cas_interleaved,
1040        U16
1041    );
1042
1043    // AVX-2 versions
1044
1045    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1046    write_test!(
1047        test_ro_mix_cas_interleaved_1_avx2,
1048        test_ro_mix_cas_interleaved_ex,
1049        U1,
1050        salsa20::x86_64::BlockAvx2,
1051        salsa20::x86_64::BlockAvx2Mb2
1052    );
1053
1054    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1055    write_test!(
1056        test_ro_mix_cas_interleaved_2_avx2,
1057        test_ro_mix_cas_interleaved_ex,
1058        U2,
1059        salsa20::x86_64::BlockAvx2,
1060        salsa20::x86_64::BlockAvx2Mb2
1061    );
1062
1063    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1064    write_test!(
1065        test_ro_mix_cas_interleaved_4_avx2,
1066        test_ro_mix_cas_interleaved_ex,
1067        U4,
1068        salsa20::x86_64::BlockAvx2,
1069        salsa20::x86_64::BlockAvx2Mb2
1070    );
1071
1072    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1073    write_test!(
1074        test_ro_mix_cas_interleaved_8_avx2,
1075        test_ro_mix_cas_interleaved_ex,
1076        U8,
1077        salsa20::x86_64::BlockAvx2,
1078        salsa20::x86_64::BlockAvx2Mb2
1079    );
1080
1081    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1082    write_test!(
1083        test_ro_mix_cas_interleaved_16_avx2,
1084        test_ro_mix_cas_interleaved_ex,
1085        U16,
1086        salsa20::x86_64::BlockAvx2,
1087        salsa20::x86_64::BlockAvx2Mb2
1088    );
1089
1090    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1091    write_test!(
1092        test_ro_mix_cas_1_avx2,
1093        test_ro_mix_cas_ex,
1094        U1,
1095        salsa20::x86_64::BlockAvx2,
1096    );
1097    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1098    write_test!(
1099        test_ro_mix_cas_2_avx2,
1100        test_ro_mix_cas_ex,
1101        U2,
1102        salsa20::x86_64::BlockAvx2,
1103    );
1104    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1105    write_test!(
1106        test_ro_mix_cas_4_avx2,
1107        test_ro_mix_cas_ex,
1108        U4,
1109        salsa20::x86_64::BlockAvx2,
1110    );
1111    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1112    write_test!(
1113        test_ro_mix_cas_8_avx2,
1114        test_ro_mix_cas_ex,
1115        U8,
1116        salsa20::x86_64::BlockAvx2,
1117    );
1118    #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1119    write_test!(
1120        test_ro_mix_cas_16_avx2,
1121        test_ro_mix_cas_ex,
1122        U16,
1123        salsa20::x86_64::BlockAvx2,
1124    );
1125
1126    // scalar versions
1127
1128    write_test!(
1129        test_ro_mix_cas_scalar_1,
1130        test_ro_mix_cas_ex,
1131        U1,
1132        salsa20::BlockScalar<U1>
1133    );
1134
1135    write_test!(
1136        test_ro_mix_cas_scalar_2,
1137        test_ro_mix_cas_ex,
1138        U2,
1139        salsa20::BlockScalar<U1>
1140    );
1141
1142    write_test!(
1143        test_ro_mix_cas_scalar_4,
1144        test_ro_mix_cas_ex,
1145        U4,
1146        salsa20::BlockScalar<U1>
1147    );
1148
1149    write_test!(
1150        test_ro_mix_cas_scalar_8,
1151        test_ro_mix_cas_ex,
1152        U8,
1153        salsa20::BlockScalar<U1>
1154    );
1155
1156    write_test!(
1157        test_ro_mix_cas_scalar_16,
1158        test_ro_mix_cas_ex,
1159        U16,
1160        salsa20::BlockScalar<U1>
1161    );
1162
1163    write_test!(
1164        test_ro_mix_cas_scalar_interleaved_1,
1165        test_ro_mix_cas_interleaved_ex,
1166        U1,
1167        salsa20::BlockScalar<U1>,
1168        salsa20::BlockScalar<U2>
1169    );
1170
1171    write_test!(
1172        test_ro_mix_cas_scalar_interleaved_2,
1173        test_ro_mix_cas_interleaved_ex,
1174        U2,
1175        salsa20::BlockScalar<U1>,
1176        salsa20::BlockScalar<U2>
1177    );
1178
1179    write_test!(
1180        test_ro_mix_cas_scalar_interleaved_4,
1181        test_ro_mix_cas_interleaved_ex,
1182        U4,
1183        salsa20::BlockScalar<U1>,
1184        salsa20::BlockScalar<U2>
1185    );
1186
1187    write_test!(
1188        test_ro_mix_cas_scalar_interleaved_8,
1189        test_ro_mix_cas_interleaved_ex,
1190        U8,
1191        salsa20::BlockScalar<U1>,
1192        salsa20::BlockScalar<U2>
1193    );
1194
1195    write_test!(
1196        test_ro_mix_cas_scalar_interleaved_16,
1197        test_ro_mix_cas_interleaved_ex,
1198        U16,
1199        salsa20::BlockScalar<U1>,
1200        salsa20::BlockScalar<U2>
1201    );
1202
1203    // AVX-512 versions
1204
1205    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1206    write_test!(
1207        test_ro_mix_cas_avx512f_1,
1208        test_ro_mix_cas_ex,
1209        U1,
1210        salsa20::x86_64::BlockAvx512F
1211    );
1212    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1213    write_test!(
1214        test_ro_mix_cas_avx512f_2,
1215        test_ro_mix_cas_ex,
1216        U2,
1217        salsa20::x86_64::BlockAvx512F
1218    );
1219    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1220    write_test!(
1221        test_ro_mix_cas_avx512f_4,
1222        test_ro_mix_cas_ex,
1223        U4,
1224        salsa20::x86_64::BlockAvx512F
1225    );
1226    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1227    write_test!(
1228        test_ro_mix_cas_avx512f_8,
1229        test_ro_mix_cas_ex,
1230        U8,
1231        salsa20::x86_64::BlockAvx512F
1232    );
1233
1234    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1235    write_test!(
1236        test_ro_mix_cas_avx512f_16,
1237        test_ro_mix_cas_ex,
1238        U16,
1239        salsa20::x86_64::BlockAvx512F
1240    );
1241    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1242    write_test!(
1243        test_ro_mix_cas_interleaved_avx512f_1,
1244        test_ro_mix_cas_interleaved_ex,
1245        U1,
1246        salsa20::x86_64::BlockAvx512F,
1247        salsa20::x86_64::BlockAvx512FMb2
1248    );
1249    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1250    write_test!(
1251        test_ro_mix_cas_interleaved_avx512f_2,
1252        test_ro_mix_cas_interleaved_ex,
1253        U2,
1254        salsa20::x86_64::BlockAvx512F,
1255        salsa20::x86_64::BlockAvx512FMb2
1256    );
1257    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1258    write_test!(
1259        test_ro_mix_cas_interleaved_avx512f_4,
1260        test_ro_mix_cas_interleaved_ex,
1261        U4,
1262        salsa20::x86_64::BlockAvx512F,
1263        salsa20::x86_64::BlockAvx512FMb2
1264    );
1265    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1266    write_test!(
1267        test_ro_mix_cas_interleaved_avx512f_8,
1268        test_ro_mix_cas_interleaved_ex,
1269        U8,
1270        salsa20::x86_64::BlockAvx512F,
1271        salsa20::x86_64::BlockAvx512FMb2
1272    );
1273
1274    // AVX-512 register resident versions
1275
1276    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1277    write_test!(test_ro_mix_cas_zmm_1, test_ro_mix_cas_zmm, U1);
1278    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1279    write_test!(test_ro_mix_cas_zmm_2, test_ro_mix_cas_zmm, U2);
1280    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1281    write_test!(test_ro_mix_cas_zmm_4, test_ro_mix_cas_zmm, U4);
1282    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1283    write_test!(test_ro_mix_cas_zmm_8, test_ro_mix_cas_zmm, U8);
1284
1285    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1286    write_test!(
1287        test_ro_mix_cas_interleaved_zmm_1,
1288        test_ro_mix_cas_interleaved_zmm,
1289        U1
1290    );
1291    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1292    write_test!(
1293        test_ro_mix_cas_interleaved_zmm_2,
1294        test_ro_mix_cas_interleaved_zmm,
1295        U2
1296    );
1297    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1298    write_test!(
1299        test_ro_mix_cas_interleaved_zmm_4,
1300        test_ro_mix_cas_interleaved_zmm,
1301        U4
1302    );
1303    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
1304    write_test!(
1305        test_ro_mix_cas_interleaved_zmm_8,
1306        test_ro_mix_cas_interleaved_zmm,
1307        U8
1308    );
1309
1310    // portable SIMD versions
1311
1312    #[cfg(feature = "portable-simd")]
1313    write_test!(
1314        test_ro_mix_cas_portable_simd_1,
1315        test_ro_mix_cas_ex,
1316        U1,
1317        salsa20::BlockPortableSimd
1318    );
1319
1320    #[cfg(feature = "portable-simd")]
1321    write_test!(
1322        test_ro_mix_cas_portable_simd_2,
1323        test_ro_mix_cas_ex,
1324        U2,
1325        salsa20::BlockPortableSimd
1326    );
1327
1328    #[cfg(feature = "portable-simd")]
1329    write_test!(
1330        test_ro_mix_cas_portable_simd_4,
1331        test_ro_mix_cas_ex,
1332        U4,
1333        salsa20::BlockPortableSimd
1334    );
1335
1336    #[cfg(feature = "portable-simd")]
1337    write_test!(
1338        test_ro_mix_cas_portable_simd_8,
1339        test_ro_mix_cas_ex,
1340        U8,
1341        salsa20::BlockPortableSimd
1342    );
1343
1344    #[cfg(feature = "portable-simd")]
1345    write_test!(
1346        test_ro_mix_cas_portable_simd_16,
1347        test_ro_mix_cas_ex,
1348        U16,
1349        salsa20::BlockPortableSimd
1350    );
1351
1352    // portable SIMD interleaved versions
1353
1354    #[cfg(feature = "portable-simd")]
1355    write_test!(
1356        test_ro_mix_cas_portable_simd_interleaved_1,
1357        test_ro_mix_cas_interleaved_ex,
1358        U1,
1359        salsa20::BlockPortableSimd,
1360        salsa20::BlockPortableSimd2
1361    );
1362
1363    #[cfg(feature = "portable-simd")]
1364    write_test!(
1365        test_ro_mix_cas_portable_simd_interleaved_2,
1366        test_ro_mix_cas_interleaved_ex,
1367        U2,
1368        salsa20::BlockPortableSimd,
1369        salsa20::BlockPortableSimd2
1370    );
1371
1372    #[cfg(feature = "portable-simd")]
1373    write_test!(
1374        test_ro_mix_cas_portable_simd_interleaved_4,
1375        test_ro_mix_cas_interleaved_ex,
1376        U4,
1377        salsa20::BlockPortableSimd,
1378        salsa20::BlockPortableSimd2
1379    );
1380
1381    #[cfg(feature = "portable-simd")]
1382    write_test!(
1383        test_ro_mix_cas_portable_simd_interleaved_8,
1384        test_ro_mix_cas_interleaved_ex,
1385        U8,
1386        salsa20::BlockPortableSimd,
1387        salsa20::BlockPortableSimd2
1388    );
1389
1390    #[cfg(feature = "portable-simd")]
1391    write_test!(
1392        test_ro_mix_cas_portable_simd_interleaved_16,
1393        test_ro_mix_cas_interleaved_ex,
1394        U16,
1395        salsa20::BlockPortableSimd,
1396        salsa20::BlockPortableSimd2
1397    );
1398}