1use scrypt::{Params, scrypt};
17
18use crate::contract::{Identified, PasswordBasedKdf};
19use crate::error::{Error, Result};
20
21const MAX_OUTPUT_LEN: usize = u32::MAX as usize;
26
27#[derive(Debug, Clone, Copy)]
29pub struct ScryptKdf {
30 params: Params,
31}
32
33impl ScryptKdf {
34 pub const IDENTIFIER: &'static str = "scrypt";
35
36 pub fn new() -> Self {
38 Self {
39 params: Params::recommended(),
40 }
41 }
42
43 pub fn with_params(log_n: u8, r: u32, p: u32) -> Result<Self> {
47 let params = Params::new(log_n, r, p, Params::RECOMMENDED_LEN)
50 .map_err(|_| Error::InvalidKdfParams { label: "scrypt" })?;
51 Ok(Self { params })
52 }
53
54 pub fn log_n(&self) -> u8 {
55 self.params.log_n()
56 }
57
58 pub fn r(&self) -> u32 {
59 self.params.r()
60 }
61
62 pub fn p(&self) -> u32 {
63 self.params.p()
64 }
65}
66
67impl Default for ScryptKdf {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl Identified for ScryptKdf {
74 fn identifier(&self) -> &str {
75 Self::IDENTIFIER
76 }
77}
78
79impl PasswordBasedKdf for ScryptKdf {
80 fn derive_from_password(&self, password: &[u8], salt: &[u8], length: usize) -> Result<Vec<u8>> {
81 if length == 0 || length > MAX_OUTPUT_LEN {
82 return Err(Error::InvalidOutputLength {
83 requested: length,
84 max: MAX_OUTPUT_LEN,
85 });
86 }
87
88 let mut output = vec![0u8; length];
89 scrypt(password, salt, &self.params, &mut output)
90 .map_err(|_| Error::InvalidOutputLength {
93 requested: length,
94 max: MAX_OUTPUT_LEN,
95 })?;
96 Ok(output)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
109 fn matches_rfc7914_test_vector() {
110 let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
111 let derived = kdf.derive_from_password(b"", b"", 64).unwrap();
112 assert_eq!(
113 crate::internal::hex_encode(&derived),
114 concat!(
115 "77d6576238657b203b19ca42c18a0497",
116 "f16b4844e3074ae8dfdffa3fede21442",
117 "fcd0069ded0948f8326a753a0fc81f17",
118 "e8d3e0fb2e0d3628cf35e20c38d18906"
119 )
120 );
121 }
122
123 #[test]
126 fn matches_rfc7914_test_vector_par16() {
127 let kdf = ScryptKdf::with_params(10, 8, 16).unwrap();
128 let derived = kdf.derive_from_password(b"password", b"NaCl", 64).unwrap();
129 assert_eq!(
130 crate::internal::hex_encode(&derived),
131 concat!(
132 "fdbabe1c9d3472007856e7190d01e9fe",
133 "7c6ad7cbc8237830e77376634b373162",
134 "2eaf30d92e22a3886ff109279d9830da",
135 "c727afb94a83ee6d8360cbdfa2cc0640"
136 )
137 );
138 }
139
140 #[test]
142 fn default_params_are_recommended() {
143 let kdf = ScryptKdf::default();
144 assert_eq!((kdf.log_n(), kdf.r(), kdf.p()), (17, 8, 1));
145 }
146
147 #[test]
148 fn same_inputs_same_output_and_length_validation() {
149 let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
150 let first = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
151 let second = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
152 assert_eq!(first, second);
153 assert_eq!(first.len(), 16);
154 assert_eq!(
157 kdf.derive_from_password(b"pw", b"salt", 64).unwrap().len(),
158 64
159 );
160 assert_eq!(
161 kdf.derive_from_password(b"pw", b"salt", 0).unwrap_err(),
162 Error::InvalidOutputLength {
163 requested: 0,
164 max: MAX_OUTPUT_LEN
165 }
166 );
167 assert_eq!(
170 kdf.derive_from_password(b"pw", b"salt", usize::MAX / 2)
171 .unwrap_err(),
172 Error::InvalidOutputLength {
173 requested: usize::MAX / 2,
174 max: MAX_OUTPUT_LEN
175 }
176 );
177 assert_eq!(
179 kdf.derive_from_password(b"pw", b"salt", MAX_OUTPUT_LEN + 1)
180 .unwrap_err(),
181 Error::InvalidOutputLength {
182 requested: MAX_OUTPUT_LEN + 1,
183 max: MAX_OUTPUT_LEN
184 }
185 );
186 }
187
188 #[test]
189 fn password_and_salt_both_affect_output() {
190 let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
191 let base = kdf.derive_from_password(b"pw", b"salt", 32).unwrap();
192 assert_ne!(base, kdf.derive_from_password(b"pw2", b"salt", 32).unwrap());
193 assert_ne!(base, kdf.derive_from_password(b"pw", b"salt2", 32).unwrap());
194 }
195
196 #[test]
197 fn invalid_params_are_rejected() {
198 assert_eq!(
199 ScryptKdf::with_params(64, 1, 1).unwrap_err(),
200 Error::InvalidKdfParams { label: "scrypt" }
201 );
202 assert_eq!(
203 ScryptKdf::with_params(4, 0, 1).unwrap_err(),
204 Error::InvalidKdfParams { label: "scrypt" }
205 );
206 assert_eq!(
207 ScryptKdf::with_params(4, 1, 0).unwrap_err(),
208 Error::InvalidKdfParams { label: "scrypt" }
209 );
210 }
211}