use crate::error::{Error, Result};
use crate::Params;
use core::mem;
use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero};
use digest::generic_array::GenericArray;
use digest::{Digest, FixedOutputReset};
pub fn balloon<D: Digest + FixedOutputReset>(
pwd: &[u8],
salt: &[u8],
secret: Option<&[u8]>,
params: Params,
memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
) -> Result<GenericArray<u8, D::OutputSize>>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
if params.p_cost.get() == 1 {
hash_internal::<D>(pwd, salt, secret, params, memory_blocks, None)
} else {
Err(Error::ThreadsTooMany)
}
}
pub fn balloon_m<D: Digest + FixedOutputReset>(
pwd: &[u8],
salt: &[u8],
secret: Option<&[u8]>,
params: Params,
memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
output: &mut GenericArray<u8, D::OutputSize>,
) -> Result<()>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
#[cfg(not(feature = "parallel"))]
let output_xor = {
let mut output = GenericArray::<_, D::OutputSize>::default();
for thread in 1..=u64::from(params.p_cost.get()) {
let hash = hash_internal::<D>(pwd, salt, secret, params, memory_blocks, Some(thread))?;
output = output.into_iter().zip(hash).map(|(a, b)| a ^ b).collect();
}
output
};
#[cfg(feature = "parallel")]
let output_xor = {
use rayon::iter::{ParallelBridge, ParallelIterator};
if memory_blocks.len() < (params.s_cost.get() * params.p_cost.get()) as usize {
return Err(Error::MemoryTooLittle);
}
if params.p_cost.get() == 1 {
hash_internal::<D>(pwd, salt, secret, params, memory_blocks, Some(1))
} else {
(1..=u64::from(params.p_cost.get()))
.zip(memory_blocks.chunks_exact_mut(params.s_cost.get() as usize))
.par_bridge()
.map_with((params, secret), |(params, secret), (thread, memory)| {
hash_internal::<D>(pwd, salt, *secret, *params, memory, Some(thread))
})
.try_reduce(GenericArray::default, |a, b| {
Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect())
})
}?
};
let mut digest = D::new();
Digest::update(&mut digest, pwd);
Digest::update(&mut digest, salt);
if let Some(secret) = secret {
Digest::update(&mut digest, secret);
}
Digest::update(&mut digest, output_xor);
Digest::finalize_into(digest, output);
Ok(())
}
fn hash_internal<D: Digest + FixedOutputReset>(
pwd: &[u8],
salt: &[u8],
secret: Option<&[u8]>,
params: Params,
memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
thread_id: Option<u64>,
) -> Result<GenericArray<u8, D::OutputSize>>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
let s_cost = params.s_cost.get() as usize;
let s_cost_bigint = {
let mut s_cost = GenericArray::<u8, D::OutputSize>::default();
s_cost[..mem::size_of::<u32>()].copy_from_slice(¶ms.s_cost.get().to_le_bytes());
NonZero::new(s_cost.into_uint_le()).unwrap()
};
let mut digest = D::new();
const DELTA: u64 = 3;
let mut cnt: u64 = 0;
let buf = memory_blocks
.get_mut(..s_cost)
.ok_or(Error::MemoryTooLittle)?;
Digest::update(&mut digest, cnt.to_le_bytes());
cnt += 1;
Digest::update(&mut digest, pwd);
Digest::update(&mut digest, salt);
if let Some(secret) = secret {
Digest::update(&mut digest, secret);
}
if let Some(thread_id) = thread_id {
Digest::update(&mut digest, thread_id.to_le_bytes());
}
buf[0] = digest.finalize_reset();
for m in 1..s_cost {
Digest::update(&mut digest, cnt.to_le_bytes());
cnt += 1;
Digest::update(&mut digest, &buf[m - 1]);
buf[m] = digest.finalize_reset();
}
for t in 0..u64::from(params.t_cost.get()) {
for m in 0..s_cost {
let prev = if m == 0 {
buf.last().unwrap()
} else {
&buf[m - 1]
};
Digest::update(&mut digest, cnt.to_le_bytes());
cnt += 1;
Digest::update(&mut digest, prev);
Digest::update(&mut digest, &buf[m]);
buf[m] = digest.finalize_reset();
for i in 0..DELTA {
Digest::update(&mut digest, t.to_le_bytes());
Digest::update(&mut digest, (m as u64).to_le_bytes());
Digest::update(&mut digest, i.to_le_bytes());
let idx_block = digest.finalize_reset();
Digest::update(&mut digest, cnt.to_le_bytes());
cnt += 1;
Digest::update(&mut digest, salt);
if let Some(secret) = secret {
Digest::update(&mut digest, secret);
}
if let Some(thread_id) = thread_id {
Digest::update(&mut digest, thread_id.to_le_bytes());
}
Digest::update(&mut digest, idx_block);
let other = digest.finalize_reset().into_uint_le() % s_cost_bigint;
let other = usize::from_le_bytes(
other.to_le_byte_array()[..mem::size_of::<usize>()]
.try_into()
.unwrap(),
);
Digest::update(&mut digest, cnt.to_le_bytes());
cnt += 1;
Digest::update(&mut digest, &buf[m]);
Digest::update(&mut digest, &buf[other]);
buf[m] = digest.finalize_reset();
}
}
}
Ok(buf.last().unwrap().clone())
}