#![allow(unsafe_op_in_unsafe_fn)]
use core::arch::wasm32::*;
use crate::block::{Block, Instance, Position};
use crate::core::index_alpha;
use crate::params::QWORDS_IN_BLOCK;
const OWORDS_IN_BLOCK: usize = QWORDS_IN_BLOCK / 2;
const ADDRESSES_IN_BLOCK_U32: u32 = crate::params::ADDRESSES_IN_BLOCK as u32;
#[inline(always)]
fn f_blamka2(x0: v128, y0: v128, x1: v128, y1: v128) -> (v128, v128) {
let xe = i32x4_shuffle::<0, 2, 4, 6>(x0, x1);
let ye = i32x4_shuffle::<0, 2, 4, 6>(y0, y1);
let z0 = u64x2_extmul_low_u32x4(xe, ye);
let z1 = u64x2_extmul_high_u32x4(xe, ye);
(
i64x2_add(i64x2_add(x0, y0), i64x2_add(z0, z0)),
i64x2_add(i64x2_add(x1, y1), i64x2_add(z1, z1)),
)
}
#[inline(always)]
fn ror32(x: v128) -> v128 {
i32x4_shuffle::<1, 0, 3, 2>(x, x)
}
#[inline(always)]
fn ror24(x: v128) -> v128 {
i8x16_shuffle::<3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10>(x, x)
}
#[inline(always)]
fn ror16(x: v128) -> v128 {
i8x16_shuffle::<2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9>(x, x)
}
#[inline(always)]
fn ror63(x: v128) -> v128 {
v128_xor(u64x2_shr(x, 63), i64x2_add(x, x))
}
#[inline(always)]
fn alignr8(hi: v128, lo: v128) -> v128 {
i64x2_shuffle::<1, 2>(lo, hi)
}
macro_rules! g1 {
($a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
($a0, $a1) = f_blamka2($a0, $b0, $a1, $b1);
$d0 = ror32(v128_xor($d0, $a0));
$d1 = ror32(v128_xor($d1, $a1));
($c0, $c1) = f_blamka2($c0, $d0, $c1, $d1);
$b0 = ror24(v128_xor($b0, $c0));
$b1 = ror24(v128_xor($b1, $c1));
}};
}
macro_rules! g2 {
($a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
($a0, $a1) = f_blamka2($a0, $b0, $a1, $b1);
$d0 = ror16(v128_xor($d0, $a0));
$d1 = ror16(v128_xor($d1, $a1));
($c0, $c1) = f_blamka2($c0, $d0, $c1, $d1);
$b0 = ror63(v128_xor($b0, $c0));
$b1 = ror63(v128_xor($b1, $c1));
}};
}
macro_rules! diagonalize {
($a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
let t0 = alignr8($b1, $b0);
let t1 = alignr8($b0, $b1);
$b0 = t0;
$b1 = t1;
core::mem::swap(&mut $c0, &mut $c1);
let t0 = alignr8($d1, $d0);
let t1 = alignr8($d0, $d1);
$d0 = t1;
$d1 = t0;
}};
}
macro_rules! undiagonalize {
($a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
let t0 = alignr8($b0, $b1);
let t1 = alignr8($b1, $b0);
$b0 = t0;
$b1 = t1;
core::mem::swap(&mut $c0, &mut $c1);
let t0 = alignr8($d0, $d1);
let t1 = alignr8($d1, $d0);
$d0 = t1;
$d1 = t0;
}};
}
macro_rules! round8 {
($s:expr, $i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr) => {{
let mut a0 = $s[$i0];
let mut a1 = $s[$i1];
let mut b0 = $s[$i2];
let mut b1 = $s[$i3];
let mut c0 = $s[$i4];
let mut c1 = $s[$i5];
let mut d0 = $s[$i6];
let mut d1 = $s[$i7];
g1!(a0, b0, c0, d0, a1, b1, c1, d1);
g2!(a0, b0, c0, d0, a1, b1, c1, d1);
diagonalize!(a0, b0, c0, d0, a1, b1, c1, d1);
g1!(a0, b0, c0, d0, a1, b1, c1, d1);
g2!(a0, b0, c0, d0, a1, b1, c1, d1);
undiagonalize!(a0, b0, c0, d0, a1, b1, c1, d1);
$s[$i0] = a0;
$s[$i1] = a1;
$s[$i2] = b0;
$s[$i3] = b1;
$s[$i4] = c0;
$s[$i5] = c1;
$s[$i6] = d0;
$s[$i7] = d1;
}};
}
#[target_feature(enable = "simd128")]
unsafe fn fill_block(
prev_block: *const u64,
ref_block: *const u64,
next_block: *mut u64,
with_xor: bool,
) {
unsafe {
let mut state: [v128; OWORDS_IN_BLOCK] = [i64x2_splat(0); OWORDS_IN_BLOCK];
let mut xy: [v128; OWORDS_IN_BLOCK] = [i64x2_splat(0); OWORDS_IN_BLOCK];
for i in 0..OWORDS_IN_BLOCK {
let p = v128_load(prev_block.add(2 * i).cast::<v128>());
let r = v128_load(ref_block.add(2 * i).cast::<v128>());
let s = v128_xor(p, r);
state[i] = s;
xy[i] = if with_xor {
let n = v128_load(next_block.add(2 * i).cast_const().cast::<v128>());
v128_xor(s, n)
} else {
s
};
}
round8!(state, 0, 1, 2, 3, 4, 5, 6, 7);
round8!(state, 8, 9, 10, 11, 12, 13, 14, 15);
round8!(state, 16, 17, 18, 19, 20, 21, 22, 23);
round8!(state, 24, 25, 26, 27, 28, 29, 30, 31);
round8!(state, 32, 33, 34, 35, 36, 37, 38, 39);
round8!(state, 40, 41, 42, 43, 44, 45, 46, 47);
round8!(state, 48, 49, 50, 51, 52, 53, 54, 55);
round8!(state, 56, 57, 58, 59, 60, 61, 62, 63);
round8!(state, 0, 8, 16, 24, 32, 40, 48, 56);
round8!(state, 1, 9, 17, 25, 33, 41, 49, 57);
round8!(state, 2, 10, 18, 26, 34, 42, 50, 58);
round8!(state, 3, 11, 19, 27, 35, 43, 51, 59);
round8!(state, 4, 12, 20, 28, 36, 44, 52, 60);
round8!(state, 5, 13, 21, 29, 37, 45, 53, 61);
round8!(state, 6, 14, 22, 30, 38, 46, 54, 62);
round8!(state, 7, 15, 23, 31, 39, 47, 55, 63);
for i in 0..OWORDS_IN_BLOCK {
let out = v128_xor(xy[i], state[i]);
v128_store(next_block.add(2 * i).cast::<v128>(), out);
}
}
}
#[target_feature(enable = "simd128")]
unsafe fn next_addresses(address_block: *mut Block, input_block: *mut Block) {
let zero = Block::ZERO;
unsafe {
(*input_block).0[6] = (*input_block).0[6].wrapping_add(1);
fill_block(zero.0.as_ptr(), (*input_block).0.as_ptr(), address_block.cast(), false);
fill_block(zero.0.as_ptr(), (*address_block).0.as_ptr(), address_block.cast(), false);
}
}
#[target_feature(enable = "simd128")]
pub unsafe fn fill_segment(instance: &Instance, mut position: Position) {
if instance.lane_length == 0 || instance.lanes == 0 {
return;
}
let data_independent_addressing = instance.data_independent_addressing(&position);
let with_xor = instance.with_xor(position.pass);
let mut address_block = Block::ZERO;
let mut input_block = if data_independent_addressing {
instance.address_input_block(&position)
} else {
Block::ZERO
};
let mut starting_index: u32 = 0;
if position.pass == 0 && position.slice == 0 {
starting_index = 2;
if data_independent_addressing {
unsafe { next_addresses(&mut address_block, &mut input_block) };
}
}
let mut curr_offset = position
.lane
.wrapping_mul(instance.lane_length)
.wrapping_add(position.slice.wrapping_mul(instance.segment_length))
.wrapping_add(starting_index);
#[allow(clippy::manual_is_multiple_of)]
let mut prev_offset = if curr_offset % instance.lane_length == 0 {
curr_offset
.wrapping_add(instance.lane_length)
.wrapping_sub(1)
} else {
curr_offset.wrapping_sub(1)
};
let mut i = starting_index;
while i < instance.segment_length {
if curr_offset % instance.lane_length == 1 {
prev_offset = curr_offset.wrapping_sub(1);
}
let pseudo_rand: u64 = if data_independent_addressing {
let slot = (i % ADDRESSES_IN_BLOCK_U32) as usize;
if slot == 0 {
unsafe { next_addresses(&mut address_block, &mut input_block) };
}
address_block.0[slot]
} else {
unsafe { instance.block(prev_offset).0[0] }
};
let mut ref_lane = ((pseudo_rand >> 32) % u64::from(instance.lanes)) as u32;
if position.pass == 0 && position.slice == 0 {
ref_lane = position.lane;
}
position.index = i;
let ref_index = index_alpha(
instance,
&position,
(pseudo_rand & 0xFFFF_FFFF) as u32,
ref_lane == position.lane,
);
let ref_offset_u64 =
u64::from(instance.lane_length) * u64::from(ref_lane) + u64::from(ref_index);
debug_assert!(ref_offset_u64 < instance.memory_len() as u64);
let ref_offset = ref_offset_u64 as u32;
unsafe {
let prev = instance.block(prev_offset);
let reference = instance.block(ref_offset);
let curr = instance.block_mut(curr_offset);
fill_block(
prev.0.as_ptr(),
reference.0.as_ptr(),
curr.0.as_mut_ptr(),
with_xor,
);
}
i += 1;
curr_offset = curr_offset.wrapping_add(1);
prev_offset = prev_offset.wrapping_add(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fill_block::{Backend, detect, scalar};
use crate::params::{Algorithm, Params, Version};
fn sm(x: u64) -> u64 {
let x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
let x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
let x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
x ^ (x >> 31)
}
#[test]
fn fill_block_matches_scalar_over_random_triples() {
for round in 0..64u64 {
let mut prev = Block::ZERO;
let mut reference = Block::ZERO;
let mut next = Block::ZERO;
for i in 0..QWORDS_IN_BLOCK {
prev.0[i] = sm(round * 400 + i as u64);
reference.0[i] = sm(round * 400 + 128 + i as u64);
next.0[i] = sm(round * 400 + 256 + i as u64);
}
for with_xor in [false, true] {
let mut want = next;
scalar::fill_block(&prev, &reference, &mut want, with_xor);
let mut got = next;
unsafe {
fill_block(
prev.0.as_ptr(),
reference.0.as_ptr(),
got.0.as_mut_ptr(),
with_xor,
)
};
assert_eq!(got.0, want.0, "round {round}, with_xor {with_xor}");
}
}
}
#[test]
fn next_addresses_matches_scalar() {
let mut input = Block::ZERO;
for i in 0..QWORDS_IN_BLOCK {
input.0[i] = sm(i as u64);
}
for _ in 0..4 {
let mut want = Block::ZERO;
let mut got = Block::ZERO;
let mut input_want = input;
let mut input_got = input;
scalar::next_addresses(&mut want, &mut input_want);
unsafe { next_addresses(&mut got, &mut input_got) };
assert_eq!(got.0, want.0);
assert_eq!(input_got.0, input_want.0, "the counter must advance equally");
input = input_got;
}
}
#[test]
fn small_hashes_match_scalar() {
for (alg, ver) in [
(Algorithm::Argon2d, Version::V0x10),
(Algorithm::Argon2d, Version::V0x13),
(Algorithm::Argon2i, Version::V0x10),
(Algorithm::Argon2i, Version::V0x13),
(Algorithm::Argon2id, Version::V0x10),
(Algorithm::Argon2id, Version::V0x13),
] {
let params = Params::new(64, 3, 4, 32).expect("params");
let mut want = [0u8; 32];
let mut got = [0u8; 32];
unsafe {
crate::core::hash_traced(
Backend::Scalar,
alg,
ver,
¶ms,
b"pwd",
b"salt-value",
&[],
&[],
&mut want,
None,
)
.expect("scalar hash");
crate::core::hash_traced(
Backend::Wasm128,
alg,
ver,
¶ms,
b"pwd",
b"salt-value",
&[],
&[],
&mut got,
None,
)
.expect("wasm128 hash");
}
assert_eq!(got, want, "{alg:?} {ver:?}");
}
}
#[test]
fn detection_picks_wasm128_when_compiled_in() {
assert!(Backend::Wasm128.is_available());
assert_eq!(detect(), Backend::Wasm128);
}
}