use alloc::vec;
#[cfg(feature = "safe_api")]
use crate::Secret;
use crate::errors::UnknownCryptoError;
#[cfg(feature = "safe_api")]
use crate::generics::TypeSpec;
use crate::hazardous::kdf::pbkdf2;
#[cfg(feature = "safe_api")]
use crate::hazardous::kdf::scrypt::phc::ScryptPhc;
use crate::util;
#[cfg(all(feature = "serde", feature = "safe_api"))]
use serde::{
de::{self, Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
#[cfg(feature = "safe_api")]
mod phc;
pub const RP_MAX: u64 = 1 << 30;
pub const RP_BLK_MAX: u32 = (i32::MAX as u32) / 128;
pub const R_BLK_MAX: u32 = (i32::MAX as u32) / 256;
const N_MAX: u32 = (i32::MAX as u32) / 128;
pub const LOGN_MAX: u32 = 31;
pub const PHC_MIN_HASHLEN: usize = 10;
#[derive(Debug)]
#[cfg(feature = "safe_api")]
pub struct ScryptPasswordHash;
#[cfg(feature = "safe_api")]
impl crate::generics::sealed::Sealed for ScryptPasswordHash {}
#[cfg(feature = "safe_api")]
impl TypeSpec for ScryptPasswordHash {
const NAME: &'static str = stringify!(PasswordHash);
type TypeData = ScryptPhc;
}
#[cfg(feature = "safe_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
pub type PasswordHash = Secret<ScryptPasswordHash>;
#[cfg(feature = "safe_api")]
impl PasswordHash {
pub fn cost_params(&self) -> CostParams {
CostParams {
logn: self.data.logn,
blocksize: self.data.blocksize,
parallelism: self.data.parallelism,
}
}
}
#[cfg(all(feature = "serde", feature = "safe_api"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "safe_api"))))]
impl Serialize for PasswordHash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let encoded_string = self.unprotected_as_ref::<str>();
serializer.serialize_str(encoded_string)
}
}
#[cfg(all(feature = "serde", feature = "safe_api"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "safe_api"))))]
impl<'de> Deserialize<'de> for PasswordHash {
fn deserialize<D>(deserializer: D) -> Result<PasswordHash, D::Error>
where
D: Deserializer<'de>,
{
let encoded_str = String::deserialize(deserializer)?;
PasswordHash::try_from(encoded_str.as_str()).map_err(de::Error::custom)
}
}
#[cfg(feature = "safe_api")]
impl TryFrom<&str> for PasswordHash {
type Error = UnknownCryptoError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self::from_data(ScryptPhc::try_from(value)?))
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct CostParams {
pub(crate) logn: u32,
pub(crate) blocksize: u32,
pub(crate) parallelism: u32,
}
impl CostParams {
fn validate_cost_parameters(
logn: u32,
blocksize: u32,
parallelism: u32,
) -> Result<(), UnknownCryptoError> {
if logn == 0 || blocksize == 0 || parallelism == 0 {
return Err(UnknownCryptoError);
}
if logn as u64 >= 16u64 * blocksize as u64 {
return Err(UnknownCryptoError);
}
let n = 1u32.checked_shl(logn).ok_or(UnknownCryptoError)?;
let fits_indexing = (blocksize as u64) * (parallelism as u64) < RP_MAX
&& blocksize <= RP_BLK_MAX / parallelism
&& blocksize <= R_BLK_MAX
&& n <= N_MAX / blocksize;
if !fits_indexing {
return Err(UnknownCryptoError);
}
Ok(())
}
pub fn new(logn: u32, blocksize: u32, parallelism: u32) -> Result<Self, UnknownCryptoError> {
Self::validate_cost_parameters(logn, blocksize, parallelism)?;
Ok(Self {
logn,
blocksize,
parallelism,
})
}
pub fn logn_from_n(n: u64) -> Result<u32, UnknownCryptoError> {
if n < 2 || !n.is_power_of_two() {
return Err(UnknownCryptoError);
}
Ok(n.trailing_zeros())
}
pub fn logn(&self) -> u32 {
self.logn
}
pub fn blocksize(&self) -> u32 {
self.blocksize
}
pub fn parallelism(&self) -> u32 {
self.parallelism
}
}
fn block_copy(dst: &mut [u32], src: &[u32], n: usize) {
dst[..n].copy_from_slice(&src[..n]);
}
fn block_xor(dst: &mut [u32], src: &[u32], n: usize) {
for (i, elem) in src[..n].iter().enumerate() {
dst[i] ^= elem;
}
}
#[rustfmt::skip]
fn salsa_xor(tmp: &mut [u32], inn: &[u32], out: &mut [u32]) {
let w0 = tmp[0] ^ inn[0];
let w1 = tmp[1] ^ inn[1];
let w2 = tmp[2] ^ inn[2];
let w3 = tmp[3] ^ inn[3];
let w4 = tmp[4] ^ inn[4];
let w5 = tmp[5] ^ inn[5];
let w6 = tmp[6] ^ inn[6];
let w7 = tmp[7] ^ inn[7];
let w8 = tmp[8] ^ inn[8];
let w9 = tmp[9] ^ inn[9];
let w10 = tmp[10] ^ inn[10];
let w11 = tmp[11] ^ inn[11];
let w12 = tmp[12] ^ inn[12];
let w13 = tmp[13] ^ inn[13];
let w14 = tmp[14] ^ inn[14];
let w15 = tmp[15] ^ inn[15];
let mut x0 = w0;
let mut x1 = w1;
let mut x2 = w2;
let mut x3 = w3;
let mut x4 = w4;
let mut x5 = w5;
let mut x6 = w6;
let mut x7 = w7;
let mut x8 = w8;
let mut x9 = w9;
let mut x10 = w10;
let mut x11 = w11;
let mut x12 = w12;
let mut x13 = w13;
let mut x14 = w14;
let mut x15 = w15;
for _ in (0..8).step_by(2) {
x4 ^= x0.wrapping_add(x12).rotate_left(7);
x8 ^= x4.wrapping_add(x0).rotate_left(9);
x12 ^= x8.wrapping_add(x4).rotate_left(13);
x0 ^= x12.wrapping_add(x8).rotate_left(18);
x9 ^= x5.wrapping_add(x1).rotate_left(7);
x13 ^= x9.wrapping_add(x5).rotate_left(9);
x1 ^= x13.wrapping_add(x9).rotate_left(13);
x5 ^= x1.wrapping_add(x13).rotate_left(18);
x14 ^= x10.wrapping_add(x6).rotate_left(7);
x2 ^= x14.wrapping_add(x10).rotate_left(9);
x6 ^= x2.wrapping_add(x14).rotate_left(13);
x10 ^= x6.wrapping_add(x2).rotate_left(18);
x3 ^= x15.wrapping_add(x11).rotate_left(7);
x7 ^= x3.wrapping_add(x15).rotate_left(9);
x11 ^= x7.wrapping_add(x3).rotate_left(13);
x15 ^= x11.wrapping_add(x7).rotate_left(18);
x1 ^= x0.wrapping_add(x3).rotate_left(7);
x2 ^= x1.wrapping_add(x0).rotate_left(9);
x3 ^= x2.wrapping_add(x1).rotate_left(13);
x0 ^= x3.wrapping_add(x2).rotate_left(18);
x6 ^= x5.wrapping_add(x4).rotate_left(7);
x7 ^= x6.wrapping_add(x5).rotate_left(9);
x4 ^= x7.wrapping_add(x6).rotate_left(13);
x5 ^= x4.wrapping_add(x7).rotate_left(18);
x11 ^= x10.wrapping_add(x9).rotate_left(7);
x8 ^= x11.wrapping_add(x10).rotate_left(9);
x9 ^= x8.wrapping_add(x11).rotate_left(13);
x10 ^= x9.wrapping_add(x8).rotate_left(18);
x12 ^= x15.wrapping_add(x14).rotate_left(7);
x13 ^= x12.wrapping_add(x15).rotate_left(9);
x14 ^= x13.wrapping_add(x12).rotate_left(13);
x15 ^= x14.wrapping_add(x13).rotate_left(18);
}
x0 = x0.wrapping_add(w0);
x1 = x1.wrapping_add(w1);
x2 = x2.wrapping_add(w2);
x3 = x3.wrapping_add(w3);
x4 = x4.wrapping_add(w4);
x5 = x5.wrapping_add(w5);
x6 = x6.wrapping_add(w6);
x7 = x7.wrapping_add(w7);
x8 = x8.wrapping_add(w8);
x9 = x9.wrapping_add(w9);
x10 = x10.wrapping_add(w10);
x11 = x11.wrapping_add(w11);
x12 = x12.wrapping_add(w12);
x13 = x13.wrapping_add(w13);
x14 = x14.wrapping_add(w14);
x15 = x15.wrapping_add(w15);
out[0] = x0; tmp[0] = x0;
out[1] = x1; tmp[1] = x1;
out[2] = x2; tmp[2] = x2;
out[3] = x3; tmp[3] = x3;
out[4] = x4; tmp[4] = x4;
out[5] = x5; tmp[5] = x5;
out[6] = x6; tmp[6] = x6;
out[7] = x7; tmp[7] = x7;
out[8] = x8; tmp[8] = x8;
out[9] = x9; tmp[9] = x9;
out[10] = x10; tmp[10] = x10;
out[11] = x11; tmp[11] = x11;
out[12] = x12; tmp[12] = x12;
out[13] = x13; tmp[13] = x13;
out[14] = x14; tmp[14] = x14;
out[15] = x15; tmp[15] = x15;
}
#[rustfmt::skip]
fn block_mix(tmp: &mut [u32], inn: &[u32], out: &mut [u32], r: usize) {
block_copy(tmp, &inn[(2*r-1)*16..], 16);
for i in (0..2*r).step_by(2) {
salsa_xor(tmp, &inn[i*16..], &mut out[i*8..]);
salsa_xor(tmp, &inn[i*16+16..], &mut out[i*8+r*16..]);
}
}
fn integer(b: &[u32], r: usize) -> u64 {
let j = (2 * r - 1) * 16;
u64::from(b[j]) | u64::from(b[j + 1]) << 32
}
#[allow(non_snake_case)]
#[allow(clippy::needless_range_loop)]
fn smix(b: &mut [u8], r: usize, N: usize, v: &mut [u32], x: &mut [u32], y: &mut [u32]) {
let mut tmp = [0u32; 16];
let R = 32 * r;
let mut j = 0;
for i in 0..R {
x[i] = u32::from_le_bytes(b[j..j + 4].try_into().unwrap());
j += 4;
}
for i in (0..N).step_by(2) {
block_copy(&mut v[i * R..], x, R);
block_mix(&mut tmp, x, y, r);
block_copy(&mut v[(i + 1) * R..], y, R);
block_mix(&mut tmp, y, x, r);
}
for _ in (0..N).step_by(2) {
let j = (integer(x, r) & (N - 1) as u64) as usize;
block_xor(x, &v[j * R..], R);
block_mix(&mut tmp, x, y, r);
let j = (integer(y, r) & (N - 1) as u64) as usize;
block_xor(y, &v[j * R..], R);
block_mix(&mut tmp, y, x, r);
}
let mut j = 0;
for v in &x[..R] {
b[j..j + 4].copy_from_slice(&v.to_le_bytes());
j += 4;
}
}
#[derive(Debug)]
pub struct Scrypt {}
impl Scrypt {
#[cfg(feature = "safe_api")]
#[allow(clippy::too_many_arguments)]
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn derive_key_encoded(
password: &[u8],
salt: &[u8],
cost: &CostParams,
hash_len: usize,
) -> Result<PasswordHash, UnknownCryptoError> {
if salt.is_empty() {
return Err(UnknownCryptoError);
}
if hash_len < PHC_MIN_HASHLEN {
return Err(UnknownCryptoError);
}
let mut pwhash = ScryptPhc {
variant: ScryptPhc::VALID_VARIANT.into(),
logn: cost.logn,
blocksize: cost.blocksize,
parallelism: cost.parallelism,
salt: salt.to_vec(),
hash: vec![0u8; hash_len],
phc_string: String::new(),
};
Self::derive_key(password, salt, cost, &mut pwhash.hash)?;
pwhash.encode_to_phc()?;
Ok(PasswordHash::from_data(pwhash))
}
#[cfg(feature = "safe_api")]
#[allow(clippy::too_many_arguments)]
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn verify_encoded(
expected: &PasswordHash,
password: &[u8],
) -> Result<(), UnknownCryptoError> {
let cost = CostParams::new(
expected.data.logn,
expected.data.blocksize,
expected.data.parallelism,
)?;
let actual = Self::derive_key_encoded(
password,
&expected.data.salt,
&cost,
expected.data.hash.len(),
)?;
if expected != &actual {
Err(UnknownCryptoError)
} else {
Ok(())
}
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn derive_key(
password: &[u8],
salt: &[u8],
cost: &CostParams,
dst_out: &mut [u8],
) -> Result<(), UnknownCryptoError> {
let n = (1u32 << cost.logn) as usize;
let r = cost.blocksize as usize;
let p = cost.parallelism as usize;
let vlen: usize = 32 * n * r;
let mut x = vec![0u32; 32 * r];
let mut y = vec![0u32; 32 * r];
let mut v = vec![0u32; vlen];
let blen: usize = p * 128 * r;
let mut b = zeroize_wrap!(vec![0u8; blen]);
pbkdf2::Pbkdf2::<pbkdf2::SHA256>::derive_key(password, salt, 1, b.as_mut())?;
for i in 0..p {
smix(&mut b[i * 128 * r..], r, n, &mut v, &mut x, &mut y);
}
pbkdf2::Pbkdf2::<pbkdf2::SHA256>::derive_key(password, b.as_ref(), 1, dst_out)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn verify(
expected: &[u8],
password: &[u8],
salt: &[u8],
cost: &CostParams,
dst_out: &mut [u8],
) -> Result<(), UnknownCryptoError> {
Self::derive_key(password, salt, cost, dst_out)?;
util::secure_cmp(dst_out, expected)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(feature = "serde", feature = "safe_api"))]
mod test_serde_impls {
use super::*;
#[test]
fn test_valid_deserialization() {
let encoded_hash = "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E";
let expected = PasswordHash::try_from(encoded_hash).unwrap();
let deserialized: PasswordHash =
serde_json::from_str(format!("\"{encoded_hash}\"").as_str()).unwrap();
assert_eq!(deserialized, expected);
}
#[test]
fn test_valid_serialization() {
let encoded_hash = "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E";
let hash = PasswordHash::try_from(encoded_hash).unwrap();
let serialized: String = serde_json::to_string(&hash).unwrap();
assert_eq!(serialized, format!("\"{encoded_hash}\""));
}
}
#[test]
#[cfg(feature = "safe_api")]
fn test_get_cost_params() {
let valid = "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E";
let hash = PasswordHash::try_from(valid).unwrap();
let costp = hash.cost_params();
assert_eq!(CostParams::new(16, 8, 1).unwrap(), costp);
}
#[cfg(any(feature = "safe_api", feature = "alloc"))]
mod test_verify {
use crate::hazardous::kdf::scrypt::CostParams;
use super::*;
use alloc::vec;
#[test]
fn test_verify_input() {
let password = b"password";
let salt = b"salt";
const DK_LEN: usize = 32;
let cost = CostParams::new(CostParams::logn_from_n(2).unwrap(), 10, 10).unwrap();
let expected_dk: [u8; DK_LEN] = [
72, 44, 133, 142, 34, 144, 85, 230, 47, 65, 224, 236, 129, 154, 94, 225, 139, 219,
135, 37, 26, 83, 79, 117, 172, 217, 90, 197, 229, 10, 161, 95,
];
let modified_dk: [u8; DK_LEN] = [
73, 44, 133, 142, 34, 144, 85, 230, 47, 65, 224, 236, 129, 154, 94, 225, 139, 219,
135, 37, 26, 83, 79, 117, 172, 217, 90, 197, 229, 10, 161, 95,
];
let mut dst_out = vec![0u8; DK_LEN];
assert!(Scrypt::verify(&expected_dk, password, salt, &cost, &mut dst_out).is_ok());
assert!(Scrypt::verify(&modified_dk, password, salt, &cost, &mut dst_out).is_err());
assert!(Scrypt::verify(&expected_dk, password, b"tlas", &cost, &mut dst_out).is_err());
let mut dkshort = [0u8; DK_LEN - 1];
let mut dklong = [0u8; DK_LEN + 1];
let mut dkzero = [0u8; 0];
assert!(Scrypt::verify(&expected_dk, password, salt, &cost, &mut dkshort).is_err());
assert!(Scrypt::verify(&expected_dk, password, salt, &cost, &mut dklong).is_err());
assert!(Scrypt::verify(&expected_dk, password, salt, &cost, &mut dkzero).is_err());
}
}
#[cfg(any(feature = "safe_api", feature = "alloc"))]
mod test_derive_key {
use crate::hazardous::kdf::scrypt::CostParams;
use super::Scrypt;
use alloc::vec;
struct ScryptVector<'a> {
password: &'a [u8],
salt: &'a [u8],
n: u32,
r: u32,
p: u32,
dk_len: usize,
expected_dk: &'a [u8],
}
static SCRYPT_VECTORS: [ScryptVector<'_>; 7] = [
ScryptVector {
password: b"password",
salt: b"salt",
n: 2,
r: 10,
p: 10,
dk_len: 32,
expected_dk: &[
72, 44, 133, 142, 34, 144, 85, 230, 47, 65, 224, 236, 129, 154, 94, 225, 139,
219, 135, 37, 26, 83, 79, 117, 172, 217, 90, 197, 229, 10, 161, 95,
],
},
ScryptVector {
password: b"password",
salt: b"salt",
n: 16,
r: 100,
p: 100,
dk_len: 32,
expected_dk: &[
136, 189, 94, 219, 82, 209, 221, 0, 24, 135, 114, 173, 54, 23, 18, 144, 34, 78,
116, 130, 149, 37, 177, 141, 115, 35, 165, 127, 145, 150, 60, 55,
],
},
ScryptVector {
password: b"this is a long \x00 password",
salt: b"and this is a long \x00 salt",
n: 16384,
r: 8,
p: 1,
dk_len: 77,
expected_dk: &[
195, 241, 130, 238, 45, 236, 132, 110, 112, 166, 148, 47, 181, 41, 152, 90, 58,
9, 118, 94, 240, 76, 97, 41, 35, 177, 127, 24, 85, 90, 55, 7, 109, 235, 43,
152, 48, 214, 157, 229, 73, 38, 81, 228, 80, 106, 229, 119, 109, 150, 212, 15,
103, 170, 238, 55, 225, 119, 123, 138, 213, 195, 17, 20, 50, 187, 59, 111, 126,
18, 100, 64, 24, 121, 230, 65, 174,
],
},
ScryptVector {
password: b"p",
salt: b"s",
n: 2,
r: 1,
p: 1,
dk_len: 16,
expected_dk: &[
72, 176, 210, 168, 163, 39, 38, 17, 152, 76, 80, 235, 214, 48, 175, 82,
],
},
ScryptVector {
password: b"",
salt: b"",
n: 16,
r: 1,
p: 1,
dk_len: 64,
expected_dk: &[
119, 214, 87, 98, 56, 101, 123, 32, 59, 25, 202, 66, 193, 138, 4, 151, 241,
107, 72, 68, 227, 7, 74, 232, 223, 223, 250, 63, 237, 226, 20, 66, 252, 208, 6,
157, 237, 9, 72, 248, 50, 106, 117, 58, 15, 200, 31, 23, 232, 211, 224, 251,
46, 13, 54, 40, 207, 53, 226, 12, 56, 209, 137, 6,
],
},
ScryptVector {
password: b"password",
salt: b"NaCl",
n: 1024,
r: 8,
p: 16,
dk_len: 64,
expected_dk: &[
253, 186, 190, 28, 157, 52, 114, 0, 120, 86, 231, 25, 13, 1, 233, 254, 124,
106, 215, 203, 200, 35, 120, 48, 231, 115, 118, 99, 75, 55, 49, 98, 46, 175,
48, 217, 46, 34, 163, 136, 111, 241, 9, 39, 157, 152, 48, 218, 199, 39, 175,
185, 74, 131, 238, 109, 131, 96, 203, 223, 162, 204, 6, 64,
],
},
ScryptVector {
password: b"pleaseletmein",
salt: b"SodiumChloride",
n: 16384,
r: 8,
p: 1,
dk_len: 64,
expected_dk: &[
112, 35, 189, 203, 58, 253, 115, 72, 70, 28, 6, 205, 129, 253, 56, 235, 253,
168, 251, 186, 144, 79, 142, 62, 169, 181, 67, 246, 84, 93, 161, 242, 213, 67,
41, 85, 97, 63, 15, 207, 98, 212, 151, 5, 36, 42, 154, 249, 230, 30, 133, 220,
13, 101, 30, 64, 223, 207, 1, 123, 69, 87, 88, 135,
],
},
];
#[test]
fn test_scrypt_vectors() {
for case in SCRYPT_VECTORS.iter().take(SCRYPT_VECTORS.len() - 1) {
let mut got = vec![0u8; case.dk_len];
let logn = CostParams::logn_from_n(case.n as u64).unwrap();
let cost = CostParams::new(logn, case.r, case.p).unwrap();
Scrypt::derive_key(case.password, case.salt, &cost, &mut got)
.expect("invalid scrypt parameters");
let exp = case.expected_dk;
assert_eq!(exp, got.as_slice());
}
}
#[test]
fn test_invalid_params() {
let valid_n = CostParams::logn_from_n(1024).unwrap();
let valid_r = 8;
let valid_p = 1;
let cost = CostParams::new(valid_n, valid_r, valid_p).unwrap();
assert_eq!(cost.logn(), valid_n);
assert_eq!(cost.blocksize(), valid_r);
assert_eq!(cost.parallelism(), valid_p);
assert!(CostParams::logn_from_n(1025).is_err());
assert!(CostParams::new(1025, valid_r, valid_p).is_err());
assert!(CostParams::new(valid_n, u32::MAX / 255, valid_p).is_err());
assert!(CostParams::new(valid_n, valid_r, u32::MAX).is_err());
assert!(CostParams::new(valid_n, 0, valid_p).is_err());
assert!(CostParams::new(valid_n, valid_r, 0).is_err());
}
}
#[cfg(feature = "safe_api")]
mod test_derive_key_encoded {
use super::*;
#[test]
fn test_salt_out_cannot_be_empty_when_encoding() {
let cost = CostParams::new(4, 8, 1).unwrap();
let hash = Scrypt::derive_key_encoded(b"password", b"salt", &cost, 32).unwrap();
let parsed = PasswordHash::try_from(hash.unprotected_as_ref::<str>()).unwrap();
assert_eq!(hash, parsed);
assert!(Scrypt::verify_encoded(&parsed, b"password").is_ok());
assert!(Scrypt::derive_key_encoded(b"password", b"", &cost, 32).is_err());
assert!(Scrypt::derive_key_encoded(b"password", b"", &cost, 0).is_err());
assert!(
PasswordHash::try_from(
"$scrypt$ln=17,r=8,p=1$$6EIh7dD/3Ap8lWSA1MpshgKf04pqs0TS4YncEhzCyKQ"
)
.is_err()
);
}
#[test]
fn test_hashlen_min() {
let cost = CostParams::new(4, 8, 1).unwrap();
assert!(Scrypt::derive_key_encoded(b"password", b"salt", &cost, 0).is_err());
assert!(Scrypt::derive_key_encoded(b"password", b"salt", &cost, 9).is_err());
assert!(Scrypt::derive_key_encoded(b"password", b"salt", &cost, 10).is_ok());
}
}
}