extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::cipher::salsa20::salsa20_8;
use crate::hash::Sha256;
use crate::kdf::pbkdf2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
InvalidParam,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("scrypt: invalid parameter")
}
}
impl core::error::Error for Error {}
pub fn scrypt(
password: &[u8],
salt: &[u8],
log_n: u8,
r: u32,
p: u32,
out: &mut [u8],
) -> Result<(), Error> {
if log_n == 0 || log_n >= 64 || r == 0 || p == 0 {
return Err(Error::InvalidParam);
}
let n: u64 = 1u64 << log_n;
let rn = (r as u64).checked_mul(n).ok_or(Error::InvalidParam)?;
if rn >= (1u64 << 30) {
return Err(Error::InvalidParam);
}
let block_size = 128usize
.checked_mul(r as usize)
.ok_or(Error::InvalidParam)?;
let b_len = block_size
.checked_mul(p as usize)
.ok_or(Error::InvalidParam)?;
if out.is_empty() {
return Err(Error::InvalidParam);
}
let dk_blocks = (out.len() as u64).div_ceil(32);
if (p as u64)
.checked_mul(dk_blocks)
.is_none_or(|v| v > u32::MAX as u64)
{
return Err(Error::InvalidParam);
}
if 4u64 * (r as u64) * (p as u64) > u32::MAX as u64 {
return Err(Error::InvalidParam);
}
let mut b: Vec<u8> = vec![0u8; b_len];
pbkdf2::<Sha256>(password, salt, 1, &mut b);
let n_us = n as usize;
let v_len = n_us.checked_mul(block_size).ok_or(Error::InvalidParam)?;
let mut v: Vec<u8> = vec![0u8; v_len];
let mut x: Vec<u8> = vec![0u8; block_size];
for i in 0..p as usize {
let off = i * block_size;
x.copy_from_slice(&b[off..off + block_size]);
romix(&mut x, n_us, r as usize, &mut v);
b[off..off + block_size].copy_from_slice(&x);
}
pbkdf2::<Sha256>(password, &b, 1, out);
b.iter_mut().for_each(|byte| *byte = 0);
v.iter_mut().for_each(|byte| *byte = 0);
x.iter_mut().for_each(|byte| *byte = 0);
let _ = core::hint::black_box(&b);
let _ = core::hint::black_box(&v);
let _ = core::hint::black_box(&x);
Ok(())
}
fn romix(x: &mut [u8], n: usize, r: usize, v: &mut [u8]) {
let block_size = 128 * r;
for i in 0..n {
v[i * block_size..(i + 1) * block_size].copy_from_slice(x);
block_mix(x, r);
}
for _ in 0..n {
let j = integerify(x, r) % n as u64;
let j_off = j as usize * block_size;
for k in 0..block_size {
x[k] ^= v[j_off + k];
}
block_mix(x, r);
}
}
fn block_mix(b: &mut [u8], r: usize) {
let two_r = 2 * r;
let mut x = [0u8; 64];
x.copy_from_slice(&b[(two_r - 1) * 64..two_r * 64]);
let mut y: Vec<u8> = vec![0u8; 128 * r];
for i in 0..two_r {
for k in 0..64 {
x[k] ^= b[i * 64 + k];
}
salsa20_8(&mut x);
y[i * 64..(i + 1) * 64].copy_from_slice(&x);
}
for i in 0..r {
b[i * 64..(i + 1) * 64].copy_from_slice(&y[(2 * i) * 64..(2 * i + 1) * 64]);
b[(r + i) * 64..(r + i + 1) * 64].copy_from_slice(&y[(2 * i + 1) * 64..(2 * i + 2) * 64]);
}
}
fn integerify(b: &[u8], r: usize) -> u64 {
let off = (2 * r - 1) * 64;
u64::from_le_bytes(b[off..off + 8].try_into().unwrap())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::from_hex;
#[test]
fn rfc7914_vector_1() {
let expected = from_hex::<64>(
"77d6576238657b203b19ca42c18a0497\
f16b4844e3074ae8dfdffa3fede21442\
fcd0069ded0948f8326a753a0fc81f17\
e8d3e0fb2e0d3628cf35e20c38d18906",
);
let mut out = [0u8; 64];
scrypt(b"", b"", 4, 1, 1, &mut out).unwrap();
assert_eq!(out, expected);
}
#[test]
fn rfc7914_vector_2() {
let expected = from_hex::<64>(
"fdbabe1c9d3472007856e7190d01e9fe\
7c6ad7cbc8237830e77376634b373162\
2eaf30d92e22a3886ff109279d9830da\
c727afb94a83ee6d8360cbdfa2cc0640",
);
let mut out = [0u8; 64];
scrypt(b"password", b"NaCl", 10, 8, 16, &mut out).unwrap();
assert_eq!(out, expected);
}
#[test]
#[ignore = "16-MiB allocation; slow in debug — `cargo test --release -- --ignored`"]
fn rfc7914_vector_3() {
let expected = from_hex::<64>(
"7023bdcb3afd7348461c06cd81fd38eb\
fda8fbba904f8e3ea9b543f6545da1f2\
d5432955613f0fcf62d49705242a9af9\
e61e85dc0d651e40dfcf017b45575887",
);
let mut out = [0u8; 64];
scrypt(b"pleaseletmein", b"SodiumChloride", 14, 8, 1, &mut out).unwrap();
assert_eq!(out, expected);
}
#[test]
fn rejects_invalid_parameters() {
let mut out = [0u8; 32];
assert_eq!(
scrypt(b"p", b"s", 0, 1, 1, &mut out),
Err(Error::InvalidParam)
);
assert_eq!(
scrypt(b"p", b"s", 4, 0, 1, &mut out),
Err(Error::InvalidParam)
);
assert_eq!(
scrypt(b"p", b"s", 4, 1, 0, &mut out),
Err(Error::InvalidParam)
);
assert_eq!(
scrypt(b"p", b"s", 4, 1, 1, &mut []),
Err(Error::InvalidParam)
);
assert_eq!(
scrypt(b"p", b"s", 30, 1, 1, &mut out),
Err(Error::InvalidParam)
);
let mut out64 = [0u8; 64];
assert_eq!(
scrypt(b"p", b"s", 4, 1, u32::MAX, &mut out64),
Err(Error::InvalidParam)
);
assert_eq!(
scrypt(b"p", b"s", 1, 1 << 20, 1 << 11, &mut out),
Err(Error::InvalidParam)
);
}
#[test]
fn large_params_reject_without_panic() {
let mut out = [0u8; 32];
assert_eq!(
scrypt(b"p", b"s", 31, 64, 1, &mut out),
Err(Error::InvalidParam)
);
}
}