1use scrypt::{Params, scrypt};
17
18use crate::contract::{Identified, PasswordBasedKdf};
19use crate::error::{Error, Result};
20
21#[derive(Debug, Clone, Copy)]
23pub struct ScryptKdf {
24 params: Params,
25}
26
27impl ScryptKdf {
28 pub const IDENTIFIER: &'static str = "scrypt";
29
30 pub fn new() -> Self {
32 Self {
33 params: Params::recommended(),
34 }
35 }
36
37 pub fn with_params(log_n: u8, r: u32, p: u32) -> Result<Self> {
41 let params = Params::new(log_n, r, p, Params::RECOMMENDED_LEN)
44 .map_err(|_| Error::InvalidKdfParams { label: "scrypt" })?;
45 Ok(Self { params })
46 }
47
48 pub fn log_n(&self) -> u8 {
49 self.params.log_n()
50 }
51
52 pub fn r(&self) -> u32 {
53 self.params.r()
54 }
55
56 pub fn p(&self) -> u32 {
57 self.params.p()
58 }
59}
60
61impl Default for ScryptKdf {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl Identified for ScryptKdf {
68 fn identifier(&self) -> &str {
69 Self::IDENTIFIER
70 }
71}
72
73impl PasswordBasedKdf for ScryptKdf {
74 fn derive_from_password(&self, password: &[u8], salt: &[u8], length: usize) -> Result<Vec<u8>> {
75 if length == 0 {
76 return Err(Error::InvalidOutputLength {
77 requested: 0,
78 max: usize::MAX,
79 });
80 }
81
82 let mut output = vec![0u8; length];
83 scrypt(password, salt, &self.params, &mut output)
84 .map_err(|_| Error::InvalidOutputLength {
87 requested: length,
88 max: usize::MAX,
89 })?;
90 Ok(output)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
103 fn matches_rfc7914_test_vector() {
104 let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
105 let derived = kdf.derive_from_password(b"", b"", 64).unwrap();
106 assert_eq!(
107 crate::internal::hex_encode(&derived),
108 concat!(
109 "77d6576238657b203b19ca42c18a0497",
110 "f16b4844e3074ae8dfdffa3fede21442",
111 "fcd0069ded0948f8326a753a0fc81f17",
112 "e8d3e0fb2e0d3628cf35e20c38d18906"
113 )
114 );
115 }
116
117 #[test]
120 fn matches_rfc7914_test_vector_par16() {
121 let kdf = ScryptKdf::with_params(10, 8, 16).unwrap();
122 let derived = kdf.derive_from_password(b"password", b"NaCl", 64).unwrap();
123 assert_eq!(
124 crate::internal::hex_encode(&derived),
125 concat!(
126 "fdbabe1c9d3472007856e7190d01e9fe",
127 "7c6ad7cbc8237830e77376634b373162",
128 "2eaf30d92e22a3886ff109279d9830da",
129 "c727afb94a83ee6d8360cbdfa2cc0640"
130 )
131 );
132 }
133
134 #[test]
136 fn default_params_are_recommended() {
137 let kdf = ScryptKdf::default();
138 assert_eq!((kdf.log_n(), kdf.r(), kdf.p()), (17, 8, 1));
139 }
140
141 #[test]
142 fn same_inputs_same_output_and_length_validation() {
143 let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
144 let first = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
145 let second = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
146 assert_eq!(first, second);
147 assert_eq!(first.len(), 16);
148 assert_eq!(
151 kdf.derive_from_password(b"pw", b"salt", 64).unwrap().len(),
152 64
153 );
154 assert_eq!(
155 kdf.derive_from_password(b"pw", b"salt", 0).unwrap_err(),
156 Error::InvalidOutputLength {
157 requested: 0,
158 max: usize::MAX
159 }
160 );
161 }
162
163 #[test]
164 fn password_and_salt_both_affect_output() {
165 let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
166 let base = kdf.derive_from_password(b"pw", b"salt", 32).unwrap();
167 assert_ne!(base, kdf.derive_from_password(b"pw2", b"salt", 32).unwrap());
168 assert_ne!(base, kdf.derive_from_password(b"pw", b"salt2", 32).unwrap());
169 }
170
171 #[test]
172 fn invalid_params_are_rejected() {
173 assert_eq!(
174 ScryptKdf::with_params(64, 1, 1).unwrap_err(),
175 Error::InvalidKdfParams { label: "scrypt" }
176 );
177 assert_eq!(
178 ScryptKdf::with_params(4, 0, 1).unwrap_err(),
179 Error::InvalidKdfParams { label: "scrypt" }
180 );
181 assert_eq!(
182 ScryptKdf::with_params(4, 1, 0).unwrap_err(),
183 Error::InvalidKdfParams { label: "scrypt" }
184 );
185 }
186}