use scrypt::{Params, scrypt};
use crate::contract::{Identified, PasswordBasedKdf};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub struct ScryptKdf {
params: Params,
}
impl ScryptKdf {
pub const IDENTIFIER: &'static str = "scrypt";
pub fn new() -> Self {
Self {
params: Params::recommended(),
}
}
pub fn with_params(log_n: u8, r: u32, p: u32) -> Result<Self> {
let params = Params::new(log_n, r, p, Params::RECOMMENDED_LEN)
.map_err(|_| Error::InvalidKdfParams { label: "scrypt" })?;
Ok(Self { params })
}
pub fn log_n(&self) -> u8 {
self.params.log_n()
}
pub fn r(&self) -> u32 {
self.params.r()
}
pub fn p(&self) -> u32 {
self.params.p()
}
}
impl Default for ScryptKdf {
fn default() -> Self {
Self::new()
}
}
impl Identified for ScryptKdf {
fn identifier(&self) -> &str {
Self::IDENTIFIER
}
}
impl PasswordBasedKdf for ScryptKdf {
fn derive_from_password(&self, password: &[u8], salt: &[u8], length: usize) -> Result<Vec<u8>> {
if length == 0 {
return Err(Error::InvalidOutputLength {
requested: 0,
max: usize::MAX,
});
}
let mut output = vec![0u8; length];
scrypt(password, salt, &self.params, &mut output)
.map_err(|_| Error::InvalidOutputLength {
requested: length,
max: usize::MAX,
})?;
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_rfc7914_test_vector() {
let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
let derived = kdf.derive_from_password(b"", b"", 64).unwrap();
assert_eq!(
crate::internal::hex_encode(&derived),
concat!(
"77d6576238657b203b19ca42c18a0497",
"f16b4844e3074ae8dfdffa3fede21442",
"fcd0069ded0948f8326a753a0fc81f17",
"e8d3e0fb2e0d3628cf35e20c38d18906"
)
);
}
#[test]
fn matches_rfc7914_test_vector_par16() {
let kdf = ScryptKdf::with_params(10, 8, 16).unwrap();
let derived = kdf.derive_from_password(b"password", b"NaCl", 64).unwrap();
assert_eq!(
crate::internal::hex_encode(&derived),
concat!(
"fdbabe1c9d3472007856e7190d01e9fe",
"7c6ad7cbc8237830e77376634b373162",
"2eaf30d92e22a3886ff109279d9830da",
"c727afb94a83ee6d8360cbdfa2cc0640"
)
);
}
#[test]
fn default_params_are_recommended() {
let kdf = ScryptKdf::default();
assert_eq!((kdf.log_n(), kdf.r(), kdf.p()), (17, 8, 1));
}
#[test]
fn same_inputs_same_output_and_length_validation() {
let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
let first = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
let second = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
assert_eq!(first, second);
assert_eq!(first.len(), 16);
assert_eq!(
kdf.derive_from_password(b"pw", b"salt", 64).unwrap().len(),
64
);
assert_eq!(
kdf.derive_from_password(b"pw", b"salt", 0).unwrap_err(),
Error::InvalidOutputLength {
requested: 0,
max: usize::MAX
}
);
}
#[test]
fn password_and_salt_both_affect_output() {
let kdf = ScryptKdf::with_params(4, 1, 1).unwrap();
let base = kdf.derive_from_password(b"pw", b"salt", 32).unwrap();
assert_ne!(base, kdf.derive_from_password(b"pw2", b"salt", 32).unwrap());
assert_ne!(base, kdf.derive_from_password(b"pw", b"salt2", 32).unwrap());
}
#[test]
fn invalid_params_are_rejected() {
assert_eq!(
ScryptKdf::with_params(64, 1, 1).unwrap_err(),
Error::InvalidKdfParams { label: "scrypt" }
);
assert_eq!(
ScryptKdf::with_params(4, 0, 1).unwrap_err(),
Error::InvalidKdfParams { label: "scrypt" }
);
assert_eq!(
ScryptKdf::with_params(4, 1, 0).unwrap_err(),
Error::InvalidKdfParams { label: "scrypt" }
);
}
}