use crate::Digest;
use crate::blake2::Blake2b;
use alloc::vec;
use alloc::vec::Vec;
const BLOCK_WORDS: usize = 128; const SYNC_POINTS: usize = 4;
type Block = [u64; BLOCK_WORDS];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Variant {
D = 0,
I = 1,
Id = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidParams,
}
fn blake2b(n: usize, parts: &[&[u8]]) -> Vec<u8> {
let mut h = Blake2b::with_output_len(n);
for p in parts {
h.update(p);
}
let mut out = vec![0u8; n];
h.finalize(&mut out);
out
}
fn h_prime(out_len: usize, input: &[u8]) -> Vec<u8> {
let len_le = (out_len as u32).to_le_bytes();
if out_len <= 64 {
return blake2b(out_len, &[&len_le, input]);
}
let r = out_len.div_ceil(32) - 2;
let mut out = Vec::with_capacity(out_len);
let mut v = blake2b(64, &[&len_le, input]); out.extend_from_slice(&v[..32]);
for _ in 1..r {
v = blake2b(64, &[&v]); out.extend_from_slice(&v[..32]);
}
let last = out_len - 32 * r; let vlast = blake2b(last, &[&v]);
out.extend_from_slice(&vlast);
out
}
macro_rules! gb {
($v:expr, $a:expr, $b:expr, $c:expr, $d:expr) => {{
let (mut a, mut b, mut c, mut d) = ($v[$a], $v[$b], $v[$c], $v[$d]);
let m = |x: u64, y: u64| {
x.wrapping_add(y)
.wrapping_add(2u64.wrapping_mul((x & 0xffff_ffff).wrapping_mul(y & 0xffff_ffff)))
};
a = m(a, b);
d = (d ^ a).rotate_right(32);
c = m(c, d);
b = (b ^ c).rotate_right(24);
a = m(a, b);
d = (d ^ a).rotate_right(16);
c = m(c, d);
b = (b ^ c).rotate_right(63);
$v[$a] = a;
$v[$b] = b;
$v[$c] = c;
$v[$d] = d;
}};
}
fn permute(v: &mut [u64]) {
gb!(v, 0, 4, 8, 12);
gb!(v, 1, 5, 9, 13);
gb!(v, 2, 6, 10, 14);
gb!(v, 3, 7, 11, 15);
gb!(v, 0, 5, 10, 15);
gb!(v, 1, 6, 11, 12);
gb!(v, 2, 7, 8, 13);
gb!(v, 3, 4, 9, 14);
}
fn g(x: &Block, y: &Block, out: &mut Block, xor_prev: bool) {
let mut r = [0u64; BLOCK_WORDS];
for i in 0..BLOCK_WORDS {
r[i] = x[i] ^ y[i];
}
let mut q = r;
for row in 0..8 {
permute(&mut q[row * 16..row * 16 + 16]);
}
for col in 0..8 {
let mut tmp = [0u64; 16];
for k in 0..8 {
tmp[2 * k] = q[16 * k + 2 * col];
tmp[2 * k + 1] = q[16 * k + 2 * col + 1];
}
permute(&mut tmp);
for k in 0..8 {
q[16 * k + 2 * col] = tmp[2 * k];
q[16 * k + 2 * col + 1] = tmp[2 * k + 1];
}
}
for i in 0..BLOCK_WORDS {
let z = q[i] ^ r[i];
out[i] = if xor_prev { out[i] ^ z } else { z };
}
}
fn block_from_bytes(b: &[u8]) -> Block {
let mut out = [0u64; BLOCK_WORDS];
for (i, w) in out.iter_mut().enumerate() {
*w = u64::from_le_bytes(b[8 * i..8 * i + 8].try_into().unwrap());
}
out
}
fn block_to_bytes(b: &Block) -> [u8; 1024] {
let mut out = [0u8; 1024];
for i in 0..BLOCK_WORDS {
out[8 * i..8 * i + 8].copy_from_slice(&b[i].to_le_bytes());
}
out
}
#[allow(clippy::too_many_arguments)]
pub fn argon2(
variant: Variant,
password: &[u8],
salt: &[u8],
secret: &[u8],
ad: &[u8],
m_kib: u32,
t_cost: u32,
lanes: u32,
out: &mut [u8],
) -> Result<(), Error> {
let p = lanes as usize;
let tag_len = out.len();
if p == 0 || t_cost == 0 || tag_len < 4 || m_kib < 8 * lanes {
return Err(Error::InvalidParams);
}
let le = |x: u32| x.to_le_bytes();
let h0 = blake2b(
64,
&[
&le(lanes),
&le(tag_len as u32),
&le(m_kib),
&le(t_cost),
&le(0x13),
&le(variant as u32),
&le(password.len() as u32),
password,
&le(salt.len() as u32),
salt,
&le(secret.len() as u32),
secret,
&le(ad.len() as u32),
ad,
],
);
let m_prime = 4 * p * (m_kib as usize / (4 * p));
let q = m_prime / p; let seg_len = q / SYNC_POINTS;
let mut mem: Vec<Block> = vec![[0u64; BLOCK_WORDS]; m_prime];
for i in 0..p {
let b0 = h_prime(1024, &[&h0[..], &le(0), &le(i as u32)].concat());
let b1 = h_prime(1024, &[&h0[..], &le(1), &le(i as u32)].concat());
mem[i * q] = block_from_bytes(&b0);
mem[i * q + 1] = block_from_bytes(&b1);
}
let zero: Block = [0u64; BLOCK_WORDS];
for pass in 0..t_cost as usize {
for slice in 0..SYNC_POINTS {
for lane in 0..p {
let data_independent = match variant {
Variant::D => false,
Variant::I => true,
Variant::Id => pass == 0 && slice < 2,
};
let mut input_block: Block = [0u64; BLOCK_WORDS];
input_block[0] = pass as u64;
input_block[1] = lane as u64;
input_block[2] = slice as u64;
input_block[3] = m_prime as u64;
input_block[4] = t_cost as u64;
input_block[5] = variant as u64;
let mut addr: Block = [0u64; BLOCK_WORDS];
let gen_addresses = |ctr: &mut u64, input_block: &mut Block, addr: &mut Block| {
*ctr += 1;
input_block[6] = *ctr;
let mut t = [0u64; BLOCK_WORDS];
g(&zero, input_block, &mut t, false);
g(&zero, &t, addr, false);
};
let mut ctr: u64 = 0;
if data_independent && pass == 0 && slice == 0 {
gen_addresses(&mut ctr, &mut input_block, &mut addr);
}
let start = if pass == 0 && slice == 0 { 2 } else { 0 };
for index in start..seg_len {
let col = slice * seg_len + index; let prev_col = if col == 0 { q - 1 } else { col - 1 };
let rand = if data_independent {
if index % BLOCK_WORDS == 0 {
gen_addresses(&mut ctr, &mut input_block, &mut addr);
}
addr[index % BLOCK_WORDS]
} else {
mem[lane * q + prev_col][0]
};
let j1 = rand & 0xffff_ffff;
let j2 = rand >> 32;
let ref_lane = if pass == 0 && slice == 0 {
lane
} else {
(j2 as usize) % p
};
let same_lane = ref_lane == lane;
let ref_area: usize = if pass == 0 {
if slice == 0 {
index - 1
} else if same_lane {
slice * seg_len + index - 1
} else {
slice * seg_len - usize::from(index == 0)
}
} else if same_lane {
(SYNC_POINTS - 1) * seg_len + index - 1
} else {
(SYNC_POINTS - 1) * seg_len - usize::from(index == 0)
};
let x = (j1 * j1) >> 32;
let y = ((ref_area as u64) * x) >> 32;
let z = ref_area - 1 - (y as usize);
let ref_start = if pass == 0 || slice == SYNC_POINTS - 1 {
0
} else {
(slice + 1) * seg_len
};
let ref_col = (ref_start + z) % q;
let prev = mem[lane * q + prev_col];
let refb = mem[ref_lane * q + ref_col];
let mut cur = mem[lane * q + col];
g(&prev, &refb, &mut cur, pass != 0);
mem[lane * q + col] = cur;
}
}
}
}
let mut c = mem[q - 1];
for i in 1..p {
let b = mem[i * q + q - 1];
for k in 0..BLOCK_WORDS {
c[k] ^= b[k];
}
}
let tag = h_prime(tag_len, &block_to_bytes(&c));
out.copy_from_slice(&tag);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn hx(h: &str) -> Vec<u8> {
(0..h.len())
.step_by(2)
.map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn rfc9106_vectors() {
let (p, s, k, x) = ([1u8; 32], [2u8; 16], [3u8; 8], [4u8; 12]);
let mut out = [0u8; 32];
argon2(Variant::D, &p, &s, &k, &x, 32, 3, 4, &mut out).unwrap();
assert_eq!(
out[..],
hx("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb")[..]
);
argon2(Variant::I, &p, &s, &k, &x, 32, 3, 4, &mut out).unwrap();
assert_eq!(
out[..],
hx("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8")[..]
);
argon2(Variant::Id, &p, &s, &k, &x, 32, 3, 4, &mut out).unwrap();
assert_eq!(
out[..],
hx("0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659")[..]
);
}
}