use crate::params::{Algorithm, BLOCK_SIZE, Params, QWORDS_IN_BLOCK, SYNC_POINTS, Version};
#[derive(Clone, Copy)]
#[repr(C, align(64))]
pub struct Block(pub [u64; QWORDS_IN_BLOCK]);
impl Block {
pub const ZERO: Block = Block([0; QWORDS_IN_BLOCK]);
#[inline]
pub fn fill(&mut self, byte: u8) {
let word = u64::from_ne_bytes([byte; 8]);
self.0 = [word; QWORDS_IN_BLOCK];
}
#[inline]
pub fn copy_from(&mut self, src: &Block) {
self.0 = src.0;
}
#[inline]
pub fn xor_with(&mut self, src: &Block) {
for i in 0..QWORDS_IN_BLOCK {
self.0[i] ^= src.0[i];
}
}
#[inline]
pub fn load_le(&mut self, bytes: &[u8; BLOCK_SIZE]) {
for (word, chunk) in self.0.iter_mut().zip(bytes.chunks_exact(8)) {
let mut buf = [0u8; 8];
buf.copy_from_slice(chunk);
*word = u64::from_le_bytes(buf);
}
}
#[inline]
pub fn store_le(&self, out: &mut [u8; BLOCK_SIZE]) {
for (word, chunk) in self.0.iter().zip(out.chunks_exact_mut(8)) {
chunk.copy_from_slice(&word.to_le_bytes());
}
}
#[inline]
#[must_use]
pub fn from_le_bytes(bytes: &[u8; BLOCK_SIZE]) -> Block {
let mut b = Block::ZERO;
b.load_le(bytes);
b
}
#[allow(clippy::wrong_self_convention)]
#[inline]
#[must_use]
pub fn to_le_bytes(&self) -> [u8; BLOCK_SIZE] {
let mut out = [0u8; BLOCK_SIZE];
self.store_le(&mut out);
out
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8; BLOCK_SIZE] {
unsafe { &*self.0.as_ptr().cast::<[u8; BLOCK_SIZE]>() }
}
#[inline]
#[must_use]
pub fn as_bytes_mut(&mut self) -> &mut [u8; BLOCK_SIZE] {
unsafe { &mut *self.0.as_mut_ptr().cast::<[u8; BLOCK_SIZE]>() }
}
#[inline]
#[must_use]
pub fn as_ptr(&self) -> *const u64 {
self.0.as_ptr()
}
#[inline]
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut u64 {
self.0.as_mut_ptr()
}
}
impl Default for Block {
#[inline]
fn default() -> Block {
Block::ZERO
}
}
impl core::ops::Index<usize> for Block {
type Output = u64;
#[inline]
fn index(&self, index: usize) -> &u64 {
&self.0[index]
}
}
impl core::ops::IndexMut<usize> for Block {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut u64 {
&mut self.0[index]
}
}
impl PartialEq for Block {
#[inline]
fn eq(&self, other: &Block) -> bool {
self.0 == other.0
}
}
impl Eq for Block {}
impl core::fmt::Debug for Block {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Block([{:#018x}, {:#018x}, .., {:#018x}])",
self.0[0],
self.0[1],
self.0[QWORDS_IN_BLOCK - 1]
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Position {
pub pass: u32,
pub lane: u32,
pub slice: u32,
pub index: u32,
}
impl Position {
#[inline]
#[must_use]
pub const fn new(pass: u32, lane: u32, slice: u32, index: u32) -> Position {
Position {
pass,
lane,
slice,
index,
}
}
}
#[derive(Debug)]
pub struct Instance {
memory: *mut Block,
memory_len: usize,
pub version: Version,
pub passes: u32,
pub memory_blocks: u32,
pub segment_length: u32,
pub lane_length: u32,
pub lanes: u32,
pub threads: u32,
pub algorithm: Algorithm,
}
impl Instance {
#[must_use]
pub unsafe fn new(
memory: *mut Block,
memory_len: usize,
algorithm: Algorithm,
version: Version,
params: &Params,
) -> Instance {
let (memory_blocks, segment_length, lane_length) = params.memory_layout();
Instance {
memory,
memory_len,
version,
passes: params.t_cost(),
memory_blocks,
segment_length,
lane_length,
lanes: params.lanes(),
threads: params.effective_threads(),
algorithm,
}
}
#[inline]
#[must_use]
pub fn memory_ptr(&self) -> *mut Block {
self.memory
}
#[inline]
#[must_use]
pub fn memory_len(&self) -> usize {
self.memory_len
}
#[inline]
#[must_use]
pub unsafe fn block_ptr(&self, index: u32) -> *mut Block {
debug_assert!((index as usize) < self.memory_len);
unsafe { self.memory.add(index as usize) }
}
#[inline]
#[must_use]
pub unsafe fn block(&self, index: u32) -> &Block {
unsafe { &*self.block_ptr(index) }
}
#[inline]
#[must_use]
#[allow(clippy::mut_from_ref)]
pub unsafe fn block_mut(&self, index: u32) -> &mut Block {
unsafe { &mut *self.block_ptr(index) }
}
#[inline]
#[must_use]
pub fn data_independent_addressing(&self, position: &Position) -> bool {
matches!(self.algorithm, Algorithm::Argon2i)
|| (matches!(self.algorithm, Algorithm::Argon2id)
&& position.pass == 0
&& position.slice < SYNC_POINTS / 2)
}
#[inline]
#[must_use]
pub fn with_xor(&self, pass: u32) -> bool {
!matches!(self.version, Version::V0x10) && pass != 0
}
#[inline]
#[must_use]
pub fn address_input_block(&self, position: &Position) -> Block {
let mut b = Block::ZERO;
b.0[0] = u64::from(position.pass);
b.0[1] = u64::from(position.lane);
b.0[2] = u64::from(position.slice);
b.0[3] = u64::from(self.memory_blocks);
b.0[4] = u64::from(self.passes);
b.0[5] = u64::from(self.algorithm.as_u32());
b
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_is_1kib_and_64_byte_aligned() {
assert_eq!(size_of::<Block>(), BLOCK_SIZE);
assert_eq!(align_of::<Block>(), 64);
}
#[test]
fn fill_sets_every_byte() {
let mut b = Block::ZERO;
b.fill(0xAB);
assert!(b.as_bytes().iter().all(|&x| x == 0xAB));
b.fill(0);
assert_eq!(b, Block::ZERO);
}
#[test]
fn le_round_trip() {
let mut bytes = [0u8; BLOCK_SIZE];
for (i, byte) in bytes.iter_mut().enumerate() {
*byte = (i % 251) as u8;
}
let b = Block::from_le_bytes(&bytes);
assert_eq!(b.0[0], u64::from_le_bytes([0, 1, 2, 3, 4, 5, 6, 7]));
assert_eq!(b.to_le_bytes(), bytes);
}
#[test]
fn xor_and_copy() {
let mut a = Block::ZERO;
a.fill(0xF0);
let mut b = Block::ZERO;
b.fill(0x0F);
a.xor_with(&b);
assert!(a.as_bytes().iter().all(|&x| x == 0xFF));
a.copy_from(&b);
assert_eq!(a, b);
}
#[test]
fn with_xor_follows_version() {
let params = Params::new(1 << 12, 2, 1, 32).unwrap();
let mut arena = [Block::ZERO; 2];
let inst = unsafe {
Instance::new(
arena.as_mut_ptr(),
arena.len(),
Algorithm::Argon2id,
Version::V0x13,
¶ms,
)
};
assert!(!inst.with_xor(0));
assert!(inst.with_xor(1));
let inst10 = unsafe {
Instance::new(
arena.as_mut_ptr(),
arena.len(),
Algorithm::Argon2id,
Version::V0x10,
¶ms,
)
};
assert!(!inst10.with_xor(0));
assert!(!inst10.with_xor(1));
}
#[test]
fn data_independent_addressing_matches_ref_c() {
let params = Params::new(1 << 12, 2, 1, 32).unwrap();
let mut arena = [Block::ZERO; 2];
let ptr = arena.as_mut_ptr();
let len = arena.len();
let mk = |alg| {
unsafe { Instance::new(ptr, len, alg, Version::V0x13, ¶ms) }
};
let i = mk(Algorithm::Argon2i);
let d = mk(Algorithm::Argon2d);
let id = mk(Algorithm::Argon2id);
for pass in 0..2 {
for slice in 0..SYNC_POINTS {
let p = Position::new(pass, 0, slice, 0);
assert!(i.data_independent_addressing(&p));
assert!(!d.data_independent_addressing(&p));
assert_eq!(
id.data_independent_addressing(&p),
pass == 0 && slice < 2,
"argon2id pass={pass} slice={slice}"
);
}
}
}
#[test]
fn address_input_block_layout() {
let params = Params::new(1 << 16, 3, 2, 32).unwrap();
let mut arena = [Block::ZERO; 2];
let inst = unsafe {
Instance::new(
arena.as_mut_ptr(),
arena.len(),
Algorithm::Argon2i,
Version::V0x13,
¶ms,
)
};
let b = inst.address_input_block(&Position::new(1, 1, 3, 0));
assert_eq!(b.0[0], 1);
assert_eq!(b.0[1], 1);
assert_eq!(b.0[2], 3);
assert_eq!(b.0[3], u64::from(params.memory_blocks()));
assert_eq!(b.0[4], 3);
assert_eq!(b.0[5], Algorithm::Argon2i.as_u32() as u64);
assert_eq!(b.0[6], 0);
}
}