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