scrypt_opt/
self_test.rs

1use core::marker::PhantomData;
2
3#[cfg(feature = "alloc")]
4use generic_array::typenum::{B1, IsGreaterOrEqual};
5use generic_array::{
6    ArrayLength, GenericArray,
7    typenum::{NonZero, U1, U2, U3, U4, U8, U10, U14, U16, U20, U53, U64, Unsigned},
8};
9
10use crate::{
11    Align64, ValidCostFactor,
12    fixed_r::{Block, BufferSet},
13    pbkdf2_1::Pbkdf2HmacSha256State,
14    pipeline::PipelineContext,
15};
16
17/// Test case for P = 1, N = 16, R = 1 in the scrypt specification
18pub struct CaseN16R1P1;
19
20impl CaseP1 for CaseN16R1P1 {
21    type OutputLen = U64;
22    type CF = U4;
23    type R = U1;
24    const PASSWORD: &'static [u8] = b"";
25    const SALT: &'static [u8] = b"";
26    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
27        0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x04,
28        0x97, 0xf1, 0x6b, 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, 0x3f, 0xed, 0xe2,
29        0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a, 0x75, 0x3a, 0x0f,
30        0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c,
31        0x38, 0xd1, 0x89, 0x06,
32    ]);
33}
34
35/// Test case for P = 1, N = 16384, R = 8 in the scrypt specification
36pub struct CaseN16384R8P1;
37
38impl CaseP1 for CaseN16384R8P1 {
39    type OutputLen = U64;
40    type CF = U14;
41    type R = U8;
42    const PASSWORD: &'static [u8] = b"pleaseletmein";
43    const SALT: &'static [u8] = b"SodiumChloride";
44    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
45        0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, 0x1c, 0x06, 0xcd, 0x81, 0xfd, 0x38,
46        0xeb, 0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, 0xf6, 0x54, 0x5d,
47        0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4, 0x97, 0x05, 0x24,
48        0x2a, 0x9a, 0xf9, 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, 0x1e, 0x40, 0xdf, 0xcf, 0x01, 0x7b,
49        0x45, 0x57, 0x58, 0x87,
50    ]);
51}
52
53/// Test case for P = 1, N = 1048576, R = 8 in the scrypt specification
54pub struct CaseN1048576R8P1;
55
56impl CaseP1 for CaseN1048576R8P1 {
57    type OutputLen = U64;
58    type CF = U20;
59    type R = U8;
60    const PASSWORD: &'static [u8] = b"pleaseletmein";
61    const SALT: &'static [u8] = b"SodiumChloride";
62    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
63        0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad, 0xdb, 0xbe, 0x09, 0xcf, 0x70, 0xf8,
64        0x81, 0xec, 0x56, 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d, 0xab, 0xe5, 0xee, 0x98, 0x20, 0xad,
65        0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f, 0x4b, 0xa5, 0xd0, 0x9f, 0xfa, 0x1c, 0x6d, 0x92, 0x7c,
66        0x40, 0xf4, 0xc3, 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9, 0x52, 0xfb, 0xcb, 0xf4, 0x5c, 0x6f,
67        0xa7, 0x7a, 0x41, 0xa4,
68    ]);
69}
70
71/// Supplementary case for P = 2, N = 1024, R = 1
72pub struct CaseN1024R1P2;
73
74impl Case for CaseN1024R1P2 {
75    type OutputLen = U64;
76    type CF = U10;
77    type R = U1;
78    type P = U2;
79    const PASSWORD: &'static [u8] = b"password";
80    const SALT: &'static [u8] = b"NaCl";
81    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
82        0x09, 0xc4, 0x23, 0x86, 0xb2, 0x46, 0x97, 0x53, 0xeb, 0x76, 0x27, 0x75, 0x15, 0xbe, 0xff,
83        0x09, 0x80, 0x9d, 0x18, 0xd9, 0x3f, 0xb4, 0xd3, 0x16, 0xea, 0xe1, 0xa8, 0x63, 0x43, 0x9a,
84        0x48, 0x98, 0x17, 0xcf, 0x56, 0xa5, 0x87, 0x69, 0xcc, 0x13, 0xbd, 0xb3, 0x33, 0x14, 0x11,
85        0xcc, 0xd7, 0xd5, 0x7f, 0x8e, 0x43, 0x9b, 0xa1, 0xa4, 0x84, 0x58, 0x0f, 0x41, 0x9f, 0x7c,
86        0x8e, 0x34, 0x99, 0x41,
87    ]);
88}
89
90#[cfg(feature = "alloc")]
91/// Test case for P = 16, N = 1024, R = 8 in the scrypt specification
92pub struct CaseN1024R8P16;
93
94#[cfg(feature = "alloc")]
95impl Case for CaseN1024R8P16 {
96    type P = U16;
97    type OutputLen = U64;
98    type CF = U10;
99    type R = U8;
100    const PASSWORD: &'static [u8] = b"password";
101    const SALT: &'static [u8] = b"NaCl";
102    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
103        0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, 0x56, 0xe7, 0x19, 0x0d, 0x01, 0xe9,
104        0xfe, 0x7c, 0x6a, 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76, 0x63, 0x4b, 0x37,
105        0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1, 0x09, 0x27, 0x9d,
106        0x98, 0x30, 0xda, 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d, 0x83, 0x60, 0xcb, 0xdf,
107        0xa2, 0xcc, 0x06, 0x40,
108    ]);
109}
110
111/// Supplementary case for exotic parameters
112pub struct CaseN1024R53P1;
113
114impl CaseP1 for CaseN1024R53P1 {
115    type OutputLen = U64;
116    type CF = U10;
117    type R = U53;
118    const PASSWORD: &'static [u8] = b"password";
119    const SALT: &'static [u8] = b"NaCl";
120    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
121        0x7c, 0x34, 0x45, 0xf8, 0x73, 0xde, 0x41, 0x9a, 0x96, 0xd7, 0xa3, 0x20, 0x51, 0xd1, 0xb4,
122        0x8f, 0xb3, 0xde, 0x94, 0x8d, 0xac, 0x06, 0x78, 0x57, 0xf3, 0x7a, 0x58, 0xdf, 0x2d, 0x71,
123        0xeb, 0x4d, 0x4b, 0xeb, 0x87, 0xd6, 0xe8, 0x7d, 0x70, 0xd4, 0xb7, 0xa5, 0x86, 0x66, 0x99,
124        0xe4, 0x7a, 0xcd, 0x09, 0x4e, 0x93, 0x4e, 0xc4, 0xa1, 0xd7, 0xb8, 0x7d, 0x53, 0x93, 0x10,
125        0x7e, 0x75, 0xdc, 0x70,
126    ]);
127}
128
129/// Supplementary case for exotic parameters
130pub struct CaseN1024R53P3;
131
132impl Case for CaseN1024R53P3 {
133    type P = U3;
134    type OutputLen = U64;
135    type CF = U10;
136    type R = U53;
137    const PASSWORD: &'static [u8] = b"password";
138    const SALT: &'static [u8] = b"NaCl";
139    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen> = GenericArray::from_array([
140        0x98, 0x13, 0x74, 0xb7, 0x0a, 0x02, 0x9a, 0x5b, 0x8a, 0xe0, 0x36, 0x28, 0x62, 0x84, 0x9f,
141        0xed, 0x87, 0xd7, 0x3e, 0x29, 0xf6, 0x08, 0x99, 0xda, 0x4f, 0xc5, 0x37, 0xa3, 0xc0, 0x6b,
142        0x36, 0x81, 0x04, 0x4a, 0xbf, 0x68, 0x0c, 0x6f, 0x40, 0x68, 0xf0, 0x4f, 0x5f, 0xa2, 0x8c,
143        0x8a, 0x16, 0x32, 0x26, 0x5b, 0xdc, 0x82, 0xa6, 0xa8, 0x06, 0xce, 0x08, 0x9a, 0x62, 0x18,
144        0xca, 0x02, 0x93, 0xb2,
145    ]);
146}
147
148/// Test case for P = 1
149pub trait CaseP1 {
150    /// The length of the output
151    type OutputLen: ArrayLength + NonZero;
152    /// The cost factor
153    type CF: ValidCostFactor;
154    /// The number of rounds
155    type R: ArrayLength + NonZero;
156    /// The password
157    const PASSWORD: &'static [u8];
158    /// The salt
159    const SALT: &'static [u8];
160    /// The known answer
161    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen>;
162
163    /// Test the algorithm implementation
164    fn algorithm_self_test() {
165        #[cfg(not(feature = "alloc"))]
166        let mut buffer0: GenericArray<
167            Align64<Block<Self::R>>,
168            <Self::CF as ValidCostFactor>::MinimumBlocks,
169        > = GenericArray::default();
170
171        #[cfg(feature = "alloc")]
172        let mut buffer0: alloc::boxed::Box<
173            GenericArray<Align64<Block<Self::R>>, <Self::CF as ValidCostFactor>::MinimumBlocks>,
174        > = unsafe { alloc::boxed::Box::new_uninit().assume_init() };
175
176        #[cfg(not(feature = "alloc"))]
177        let mut buffer1: GenericArray<
178            Align64<Block<Self::R>>,
179            <Self::CF as ValidCostFactor>::MinimumBlocks,
180        > = GenericArray::default();
181
182        #[cfg(feature = "alloc")]
183        let mut buffer1: alloc::boxed::Box<
184            GenericArray<Align64<Block<Self::R>>, <Self::CF as ValidCostFactor>::MinimumBlocks>,
185        > = unsafe { alloc::boxed::Box::new_uninit().assume_init() };
186
187        let mut buffer_set0 = BufferSet::<_, Self::R>::new(buffer0.as_mut_slice());
188        let mut buffer_set1 = BufferSet::<_, Self::R>::new(buffer1.as_mut_slice());
189
190        assert_eq!(buffer_set0.n(), 1 << Self::CF::U8);
191        assert_eq!(buffer_set1.n(), 1 << Self::CF::U8);
192
193        let mut output0 = GenericArray::default();
194        let mut output1 = GenericArray::default();
195        let mut output_dummy = GenericArray::default();
196
197        // compat API test
198        #[cfg(feature = "std")]
199        {
200            crate::compat::scrypt(
201                Self::PASSWORD,
202                Self::SALT,
203                Self::CF::U8.try_into().unwrap(),
204                Self::R::U32.try_into().unwrap(),
205                1.try_into().unwrap(),
206                output0.as_mut_slice(),
207            );
208            assert_eq!(output0, Self::KNOWN_ANSWER);
209            output0.fill(0);
210        }
211
212        let hmac_state = Pbkdf2HmacSha256State::new(Self::PASSWORD);
213        let hmac_state_dummy = Pbkdf2HmacSha256State::new(b"not a password");
214
215        // exercise basic functionality
216        buffer_set0.set_input(&hmac_state, Self::SALT);
217
218        buffer_set0.scrypt_ro_mix();
219
220        buffer_set0.extract_output(&hmac_state, &mut output0);
221
222        assert_eq!(output0, Self::KNOWN_ANSWER);
223
224        // check output is not stuck
225        buffer_set0.set_input(&hmac_state_dummy, Self::SALT);
226        buffer_set0.scrypt_ro_mix();
227        buffer_set0.extract_output(&hmac_state_dummy, &mut output_dummy);
228        assert_ne!(output_dummy, Self::KNOWN_ANSWER, "stuck output");
229
230        // basic interleaved functionality
231        buffer_set0.set_input(&hmac_state, Self::SALT);
232        buffer_set1.set_input(&hmac_state_dummy, Self::SALT);
233
234        buffer_set0.ro_mix_front();
235        buffer_set0.ro_mix_interleaved(&mut buffer_set1);
236        buffer_set1.ro_mix_back();
237
238        buffer_set0.extract_output(&hmac_state, &mut output0);
239        buffer_set1.extract_output(&hmac_state_dummy, &mut output1);
240
241        assert_eq!(output0, Self::KNOWN_ANSWER);
242        assert_eq!(output1, output_dummy);
243
244        buffer_set0.as_mut().iter_mut().for_each(|b| {
245            b.fill(0);
246        });
247
248        buffer_set1.as_mut().iter_mut().for_each(|b| {
249            b.fill(0);
250        });
251    }
252
253    /// Test the pipeline API
254    fn pipeline_api_test() {
255        #[cfg(not(feature = "alloc"))]
256        let mut buffer0: GenericArray<
257            Align64<Block<Self::R>>,
258            <Self::CF as ValidCostFactor>::MinimumBlocks,
259        > = GenericArray::default();
260
261        #[cfg(feature = "alloc")]
262        let mut buffer0: alloc::boxed::Box<
263            GenericArray<Align64<Block<Self::R>>, <Self::CF as ValidCostFactor>::MinimumBlocks>,
264        > = unsafe { alloc::boxed::Box::new_uninit().assume_init() };
265
266        #[cfg(not(feature = "alloc"))]
267        let mut buffer1: GenericArray<
268            Align64<Block<Self::R>>,
269            <Self::CF as ValidCostFactor>::MinimumBlocks,
270        > = GenericArray::default();
271
272        #[cfg(feature = "alloc")]
273        let mut buffer1: alloc::boxed::Box<
274            GenericArray<Align64<Block<Self::R>>, <Self::CF as ValidCostFactor>::MinimumBlocks>,
275        > = unsafe { alloc::boxed::Box::new_uninit().assume_init() };
276
277        let hmac_state = Pbkdf2HmacSha256State::new(Self::PASSWORD);
278        let hmac_state_dummy = Pbkdf2HmacSha256State::new(b"not a password");
279
280        let mut buffer_set0 = BufferSet::<_, Self::R>::new(buffer0.as_mut_slice());
281        let mut buffer_set1 = BufferSet::<_, Self::R>::new(buffer1.as_mut_slice());
282
283        // high level pipeline API
284        struct Context<R: ArrayLength + NonZero, OutputLen: ArrayLength + NonZero> {
285            i: usize,
286            salt: &'static [u8],
287            known_answer: GenericArray<u8, OutputLen>,
288
289            hmac_state: Pbkdf2HmacSha256State,
290            hmac_state_dummy: Pbkdf2HmacSha256State,
291            _marker: PhantomData<R>,
292        }
293
294        impl<R: ArrayLength + NonZero, OutputLen: ArrayLength + NonZero>
295            PipelineContext<usize, &mut [Align64<Block<R>>], R, ()> for Context<R, OutputLen>
296        {
297            fn begin(
298                &mut self,
299                _state: &mut usize,
300                buffer_set: &mut BufferSet<&mut [Align64<Block<R>>], R>,
301            ) {
302                match self.i % 3 {
303                    0 | 1 => {
304                        buffer_set.set_input(&self.hmac_state, self.salt);
305                    }
306                    2 => {
307                        buffer_set.set_input(&self.hmac_state_dummy, self.salt);
308                    }
309                    _ => unreachable!(),
310                }
311            }
312            fn drain(
313                self,
314                state: &mut usize,
315                buffer_set: &mut BufferSet<&mut [Align64<Block<R>>], R>,
316            ) -> Option<()> {
317                match self.i % 3 {
318                    0 | 1 => {
319                        let mut output = GenericArray::<_, OutputLen>::default();
320                        buffer_set.extract_output(&self.hmac_state, &mut output);
321                        assert_eq!(output, self.known_answer);
322                    }
323                    2 => {
324                        let mut output_dummy = GenericArray::<_, OutputLen>::default();
325                        buffer_set.extract_output(&self.hmac_state_dummy, &mut output_dummy);
326                        assert_ne!(output_dummy, self.known_answer, "stuck output");
327                    }
328                    _ => unreachable!(),
329                }
330                *state += self.i;
331
332                None
333            }
334        }
335
336        for pipeline_length in 0..11 {
337            let mut counter = 0;
338            buffer_set0.pipeline(
339                &mut buffer_set1,
340                (0..pipeline_length).map(|i| Context {
341                    i,
342                    hmac_state,
343                    hmac_state_dummy,
344                    salt: Self::SALT,
345                    known_answer: Self::KNOWN_ANSWER,
346                    _marker: PhantomData,
347                }),
348                &mut counter,
349            );
350
351            let expected_sum = pipeline_length * (pipeline_length.saturating_sub(1)) / 2;
352            assert_eq!(counter, expected_sum);
353        }
354    }
355}
356
357/// Test case for P > 1
358#[cfg(feature = "alloc")]
359pub trait Case {
360    /// The parallel width
361    type P: ArrayLength + NonZero + IsGreaterOrEqual<U2, Output = B1>;
362    /// The length of the output
363    type OutputLen: ArrayLength + NonZero;
364    /// The number of blocks
365    type CF: ValidCostFactor;
366    /// The number of rounds
367    type R: ArrayLength + NonZero;
368    /// The password
369    const PASSWORD: &'static [u8];
370    /// The salt
371    const SALT: &'static [u8];
372    /// The known answer
373    const KNOWN_ANSWER: GenericArray<u8, Self::OutputLen>;
374
375    /// Test the algorithm implementation
376    fn algorithm_self_test() {
377        let hmac_state = Pbkdf2HmacSha256State::new(Self::PASSWORD);
378        let mut input_buffers: GenericArray<Align64<Block<Self::R>>, Self::P> =
379            GenericArray::default();
380        let mut output_buffers: GenericArray<Align64<Block<Self::R>>, Self::P> =
381            GenericArray::default();
382
383        let mut buffer0 = BufferSet::<_, Self::R>::new_boxed(Self::CF::U8.try_into().unwrap());
384        let mut buffer1 = BufferSet::<_, Self::R>::new_boxed(Self::CF::U8.try_into().unwrap());
385
386        hmac_state.emit_scatter(Self::SALT, input_buffers.iter_mut());
387
388        buffer0.pipeline(
389            &mut buffer1,
390            input_buffers.iter().zip(output_buffers.iter_mut()),
391            &mut (),
392        );
393
394        let mut output = GenericArray::default();
395
396        // compat API test
397        #[cfg(feature = "std")]
398        {
399            crate::compat::scrypt(
400                Self::PASSWORD,
401                Self::SALT,
402                Self::CF::U8.try_into().unwrap(),
403                Self::R::U32.try_into().unwrap(),
404                Self::P::U32.try_into().unwrap(),
405                output.as_mut_slice(),
406            );
407            assert_eq!(output, Self::KNOWN_ANSWER);
408            output.fill(0);
409        }
410
411        hmac_state.emit_gather(output_buffers.iter(), &mut output);
412
413        assert_eq!(output, Self::KNOWN_ANSWER);
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn test_case_16_1_1_algorithm_self_test() {
423        CaseN16R1P1::algorithm_self_test();
424    }
425
426    #[test]
427    fn test_case_16_1_1_pipeline_api_test() {
428        CaseN16R1P1::pipeline_api_test();
429    }
430
431    #[cfg(feature = "alloc")]
432    #[test]
433    fn test_case_16384_8_1_algorithm_self_test() {
434        CaseN16384R8P1::algorithm_self_test();
435    }
436
437    #[cfg(feature = "alloc")]
438    #[test]
439    fn test_case_1024_8_16_algorithm_self_test() {
440        CaseN1024R8P16::algorithm_self_test();
441    }
442
443    #[cfg(feature = "alloc")]
444    #[test]
445    fn test_case_1024_1_2_algorithm_self_test() {
446        CaseN1024R1P2::algorithm_self_test();
447    }
448
449    #[cfg(feature = "alloc")]
450    #[test]
451    fn test_case_1024_53_1_algorithm_self_test() {
452        CaseN1024R53P1::algorithm_self_test();
453    }
454
455    #[cfg(feature = "alloc")]
456    #[test]
457    fn test_case_1024_53_1_pipeline_api_test() {
458        CaseN1024R53P1::pipeline_api_test();
459    }
460
461    #[cfg(feature = "alloc")]
462    #[test]
463    fn test_case_1024_53_3_algorithm_self_test() {
464        CaseN1024R53P3::algorithm_self_test();
465    }
466}