#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicU8, Ordering};
use crate::block::{Block, Instance, Position};
use crate::params::{ADDRESSES_IN_BLOCK, OWORDS_IN_BLOCK};
const ADDRESSES_IN_BLOCK_U32: u32 = ADDRESSES_IN_BLOCK as u32;
const SSSE3_UNKNOWN: u8 = 0xFF;
static SSSE3_CACHE: AtomicU8 = AtomicU8::new(SSSE3_UNKNOWN);
#[cfg(feature = "std")]
#[inline]
fn probe_ssse3() -> bool {
std::arch::is_x86_feature_detected!("ssse3")
}
#[cfg(not(feature = "std"))]
#[inline]
fn probe_ssse3() -> bool {
cfg!(target_feature = "ssse3")
}
#[cold]
#[inline(never)]
fn probe_and_cache_ssse3() -> bool {
let found = probe_ssse3();
SSSE3_CACHE.store(u8::from(found), Ordering::Relaxed);
found
}
#[inline]
fn have_ssse3() -> bool {
match SSSE3_CACHE.load(Ordering::Relaxed) {
0 => false,
1 => true,
_ => probe_and_cache_ssse3(),
}
}
const R24: [u8; 16] = [3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10];
const R16: [u8; 16] = [2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9];
#[inline(always)]
unsafe fn f_blamka(x: __m128i, y: __m128i) -> __m128i {
unsafe {
let z = _mm_mul_epu32(x, y);
_mm_add_epi64(_mm_add_epi64(x, y), _mm_add_epi64(z, z))
}
}
#[inline(always)]
unsafe fn rot32<const SSSE3: bool>(x: __m128i) -> __m128i {
unsafe {
if SSSE3 {
_mm_shuffle_epi32::<0b10_11_00_01>(x)
} else {
_mm_xor_si128(_mm_srli_epi64::<32>(x), _mm_slli_epi64::<32>(x))
}
}
}
#[inline(always)]
unsafe fn rot24<const SSSE3: bool>(x: __m128i) -> __m128i {
unsafe {
if SSSE3 {
_mm_shuffle_epi8(x, _mm_loadu_si128(R24.as_ptr().cast()))
} else {
_mm_xor_si128(_mm_srli_epi64::<24>(x), _mm_slli_epi64::<40>(x))
}
}
}
#[inline(always)]
unsafe fn rot16<const SSSE3: bool>(x: __m128i) -> __m128i {
unsafe {
if SSSE3 {
_mm_shuffle_epi8(x, _mm_loadu_si128(R16.as_ptr().cast()))
} else {
_mm_xor_si128(_mm_srli_epi64::<16>(x), _mm_slli_epi64::<48>(x))
}
}
}
#[inline(always)]
unsafe fn rot63<const SSSE3: bool>(x: __m128i) -> __m128i {
unsafe {
if SSSE3 {
_mm_xor_si128(_mm_srli_epi64::<63>(x), _mm_add_epi64(x, x))
} else {
_mm_xor_si128(_mm_srli_epi64::<63>(x), _mm_slli_epi64::<1>(x))
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn g1<const SSSE3: bool>(
a0: &mut __m128i,
b0: &mut __m128i,
c0: &mut __m128i,
d0: &mut __m128i,
a1: &mut __m128i,
b1: &mut __m128i,
c1: &mut __m128i,
d1: &mut __m128i,
) {
unsafe {
*a0 = f_blamka(*a0, *b0);
*a1 = f_blamka(*a1, *b1);
*d0 = _mm_xor_si128(*d0, *a0);
*d1 = _mm_xor_si128(*d1, *a1);
*d0 = rot32::<SSSE3>(*d0);
*d1 = rot32::<SSSE3>(*d1);
*c0 = f_blamka(*c0, *d0);
*c1 = f_blamka(*c1, *d1);
*b0 = _mm_xor_si128(*b0, *c0);
*b1 = _mm_xor_si128(*b1, *c1);
*b0 = rot24::<SSSE3>(*b0);
*b1 = rot24::<SSSE3>(*b1);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn g2<const SSSE3: bool>(
a0: &mut __m128i,
b0: &mut __m128i,
c0: &mut __m128i,
d0: &mut __m128i,
a1: &mut __m128i,
b1: &mut __m128i,
c1: &mut __m128i,
d1: &mut __m128i,
) {
unsafe {
*a0 = f_blamka(*a0, *b0);
*a1 = f_blamka(*a1, *b1);
*d0 = _mm_xor_si128(*d0, *a0);
*d1 = _mm_xor_si128(*d1, *a1);
*d0 = rot16::<SSSE3>(*d0);
*d1 = rot16::<SSSE3>(*d1);
*c0 = f_blamka(*c0, *d0);
*c1 = f_blamka(*c1, *d1);
*b0 = _mm_xor_si128(*b0, *c0);
*b1 = _mm_xor_si128(*b1, *c1);
*b0 = rot63::<SSSE3>(*b0);
*b1 = rot63::<SSSE3>(*b1);
}
}
#[inline(always)]
unsafe fn diagonalize<const SSSE3: bool>(
b0: &mut __m128i,
b1: &mut __m128i,
c0: &mut __m128i,
c1: &mut __m128i,
d0: &mut __m128i,
d1: &mut __m128i,
) {
unsafe {
if SSSE3 {
let t0 = _mm_alignr_epi8::<8>(*b1, *b0);
let t1 = _mm_alignr_epi8::<8>(*b0, *b1);
*b0 = t0;
*b1 = t1;
core::mem::swap(c0, c1);
let t0 = _mm_alignr_epi8::<8>(*d1, *d0);
let t1 = _mm_alignr_epi8::<8>(*d0, *d1);
*d0 = t1;
*d1 = t0;
} else {
let t0 = *d0;
let t1 = *b0;
core::mem::swap(c0, c1);
*d0 = _mm_unpackhi_epi64(*d1, _mm_unpacklo_epi64(t0, t0));
*d1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(*d1, *d1));
*b0 = _mm_unpackhi_epi64(*b0, _mm_unpacklo_epi64(*b1, *b1));
*b1 = _mm_unpackhi_epi64(*b1, _mm_unpacklo_epi64(t1, t1));
}
}
}
#[inline(always)]
unsafe fn undiagonalize<const SSSE3: bool>(
b0: &mut __m128i,
b1: &mut __m128i,
c0: &mut __m128i,
c1: &mut __m128i,
d0: &mut __m128i,
d1: &mut __m128i,
) {
unsafe {
if SSSE3 {
let t0 = _mm_alignr_epi8::<8>(*b0, *b1);
let t1 = _mm_alignr_epi8::<8>(*b1, *b0);
*b0 = t0;
*b1 = t1;
core::mem::swap(c0, c1);
let t0 = _mm_alignr_epi8::<8>(*d0, *d1);
let t1 = _mm_alignr_epi8::<8>(*d1, *d0);
*d0 = t1;
*d1 = t0;
} else {
core::mem::swap(c0, c1);
let t0 = *b0;
let t1 = *d0;
*b0 = _mm_unpackhi_epi64(*b1, _mm_unpacklo_epi64(*b0, *b0));
*b1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(*b1, *b1));
*d0 = _mm_unpackhi_epi64(*d0, _mm_unpacklo_epi64(*d1, *d1));
*d1 = _mm_unpackhi_epi64(*d1, _mm_unpacklo_epi64(t1, t1));
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn blake2_round<const SSSE3: bool>(
a0: __m128i,
a1: __m128i,
b0: __m128i,
b1: __m128i,
c0: __m128i,
c1: __m128i,
d0: __m128i,
d1: __m128i,
) -> [__m128i; 8] {
let (mut a0, mut a1, mut b0, mut b1) = (a0, a1, b0, b1);
let (mut c0, mut c1, mut d0, mut d1) = (c0, c1, d0, d1);
unsafe {
g1::<SSSE3>(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
g2::<SSSE3>(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
diagonalize::<SSSE3>(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
g1::<SSSE3>(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
g2::<SSSE3>(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
undiagonalize::<SSSE3>(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
}
[a0, a1, b0, b1, c0, c1, d0, d1]
}
#[inline(always)]
unsafe fn fill_block<const SSSE3: bool>(
state: &mut [__m128i; OWORDS_IN_BLOCK],
ref_block: *const Block,
next_block: *mut Block,
with_xor: bool,
) {
let refp = ref_block.cast::<__m128i>();
let nextp = next_block.cast::<__m128i>();
let mut block_xy: [MaybeUninit<__m128i>; OWORDS_IN_BLOCK] =
[const { MaybeUninit::uninit() }; OWORDS_IN_BLOCK];
unsafe {
if with_xor {
for i in 0..OWORDS_IN_BLOCK {
state[i] = _mm_xor_si128(state[i], _mm_loadu_si128(refp.add(i)));
block_xy[i] = MaybeUninit::new(_mm_xor_si128(
state[i],
_mm_loadu_si128(nextp.add(i).cast_const()),
));
}
} else {
for i in 0..OWORDS_IN_BLOCK {
state[i] = _mm_xor_si128(state[i], _mm_loadu_si128(refp.add(i)));
block_xy[i] = MaybeUninit::new(state[i]);
}
}
}
for i in 0..8 {
let base = 8 * i;
let r = unsafe {
blake2_round::<SSSE3>(
state[base],
state[base + 1],
state[base + 2],
state[base + 3],
state[base + 4],
state[base + 5],
state[base + 6],
state[base + 7],
)
};
for (k, value) in r.into_iter().enumerate() {
state[base + k] = value;
}
}
for i in 0..8 {
let r = unsafe {
blake2_round::<SSSE3>(
state[i],
state[8 + i],
state[16 + i],
state[24 + i],
state[32 + i],
state[40 + i],
state[48 + i],
state[56 + i],
)
};
for (k, value) in r.into_iter().enumerate() {
state[8 * k + i] = value;
}
}
unsafe {
for i in 0..OWORDS_IN_BLOCK {
state[i] = _mm_xor_si128(state[i], block_xy[i].assume_init());
_mm_storeu_si128(nextp.add(i), state[i]);
}
}
}
#[inline(always)]
unsafe fn next_addresses<const SSSE3: bool>(address_block: *mut Block, input_block: *mut Block) {
let zero = unsafe { _mm_setzero_si128() };
let mut zero_state = [zero; OWORDS_IN_BLOCK];
let mut zero2_state = [zero; OWORDS_IN_BLOCK];
unsafe {
(*input_block).0[6] = (*input_block).0[6].wrapping_add(1);
}
unsafe {
fill_block::<SSSE3>(
&mut zero_state,
input_block.cast_const(),
address_block,
false,
);
fill_block::<SSSE3>(
&mut zero2_state,
address_block.cast_const(),
address_block,
false,
);
}
}
#[inline(always)]
unsafe fn fill_segment_impl<const SSSE3: bool>(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::<SSSE3>(&raw mut address_block, &raw 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 state = unsafe {
let p = instance
.block_ptr(prev_offset)
.cast::<__m128i>()
.cast_const();
let mut state = [_mm_setzero_si128(); OWORDS_IN_BLOCK];
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm_loadu_si128(p.add(i));
}
state
};
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::<SSSE3>(&raw mut address_block, &raw mut input_block);
}
}
address_block.0[slot]
} else {
unsafe { (*instance.block_ptr(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 = crate::core::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 {
fill_block::<SSSE3>(
&mut state,
instance.block_ptr(ref_offset).cast_const(),
instance.block_ptr(curr_offset),
with_xor,
);
}
i += 1;
curr_offset = curr_offset.wrapping_add(1);
prev_offset = prev_offset.wrapping_add(1);
}
}
#[target_feature(enable = "sse2")]
pub unsafe fn fill_segment(instance: &Instance, position: Position) {
if have_ssse3() {
unsafe { fill_segment_ssse3(instance, position) }
} else {
unsafe { fill_segment_sse2_only(instance, position) }
}
}
#[target_feature(enable = "sse2")]
pub unsafe fn fill_segment_sse2_only(instance: &Instance, position: Position) {
unsafe { fill_segment_impl::<false>(instance, position) }
}
#[target_feature(enable = "sse2,ssse3")]
pub unsafe fn fill_segment_ssse3(instance: &Instance, position: Position) {
unsafe { fill_segment_impl::<true>(instance, position) }
}
#[cfg(test)]
pub(crate) mod test_support {
use crate::block::{Block, Instance, Position};
use crate::error::Error;
use crate::fill_block::{Backend, FillSegmentFn};
use crate::memory::{Arena, clear_internal_memory};
use crate::params::{
Algorithm, PREHASH_DIGEST_LENGTH, Params, QWORDS_IN_BLOCK, SYNC_POINTS, Version,
};
pub struct XorShift64Star(u64);
impl XorShift64Star {
pub const fn new(seed: u64) -> XorShift64Star {
XorShift64Star(if seed == 0 {
0x2545_F491_4F6C_DD1D
} else {
seed
})
}
pub fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
pub fn next_block(&mut self) -> Block {
let mut b = Block::ZERO;
for word in &mut b.0 {
*word = self.next_u64();
}
b
}
}
pub type FillBlockFn = unsafe fn(&Block, &Block, &mut Block, bool);
pub unsafe fn assert_fill_block_matches_scalar(
label: &str,
candidate: FillBlockFn,
rounds: u32,
seed: u64,
) {
let mut rng = XorShift64Star::new(seed);
let mut checked = 0u32;
for iteration in 0..rounds {
let prev = rng.next_block();
let reference = rng.next_block();
let original_next = rng.next_block();
for with_xor in [false, true] {
let mut want = original_next;
crate::fill_block::scalar::fill_block(&prev, &reference, &mut want, with_xor);
let mut got = original_next;
unsafe { candidate(&prev, &reference, &mut got, with_xor) };
for w in 0..QWORDS_IN_BLOCK {
assert_eq!(
got.0[w], want.0[w],
"{label}: iteration {iteration}, with_xor={with_xor}, word {w}: \
got {:#018x}, scalar says {:#018x}",
got.0[w], want.0[w]
);
}
checked += 1;
}
}
assert_eq!(checked, rounds * 2, "{label}: wrong number of comparisons");
}
pub unsafe fn assert_fill_blocks_agree(
label: &str,
left: FillBlockFn,
right: FillBlockFn,
rounds: u32,
seed: u64,
) {
let mut rng = XorShift64Star::new(seed);
for iteration in 0..rounds {
let prev = rng.next_block();
let reference = rng.next_block();
let original_next = rng.next_block();
for with_xor in [false, true] {
let mut a = original_next;
let mut b = original_next;
unsafe {
left(&prev, &reference, &mut a, with_xor);
right(&prev, &reference, &mut b, with_xor);
}
for w in 0..QWORDS_IN_BLOCK {
assert_eq!(
a.0[w], b.0[w],
"{label}: iteration {iteration}, with_xor={with_xor}, word {w}"
);
}
}
}
}
pub struct Vector {
pub line: u32,
pub version: Version,
pub t: u32,
pub m_log2: u32,
pub p: u32,
pub pwd: &'static str,
pub salt: &'static str,
pub hex: &'static str,
pub algorithm: Algorithm,
pub large_ram: bool,
}
#[rustfmt::skip]
pub const VECTORS: &[Vector] = &[
Vector { line: 77, version: Version::V0x10, t: 2, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 82, version: Version::V0x10, t: 2, m_log2: 20, p: 1, pwd: "password", salt: "somesalt", hex: "9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9", algorithm: Algorithm::Argon2i, large_ram: true },
Vector { line: 87, version: Version::V0x10, t: 2, m_log2: 18, p: 1, pwd: "password", salt: "somesalt", hex: "3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 91, version: Version::V0x10, t: 2, m_log2: 8, p: 1, pwd: "password", salt: "somesalt", hex: "fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 95, version: Version::V0x10, t: 2, m_log2: 8, p: 2, pwd: "password", salt: "somesalt", hex: "b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 99, version: Version::V0x10, t: 1, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 103, version: Version::V0x10, t: 4, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 107, version: Version::V0x10, t: 2, m_log2: 16, p: 1, pwd: "differentpassword", salt: "somesalt", hex: "e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 111, version: Version::V0x10, t: 2, m_log2: 16, p: 1, pwd: "password", salt: "diffsalt", hex: "79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 156, version: Version::V0x13, t: 2, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 161, version: Version::V0x13, t: 2, m_log2: 20, p: 1, pwd: "password", salt: "somesalt", hex: "d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41", algorithm: Algorithm::Argon2i, large_ram: true },
Vector { line: 166, version: Version::V0x13, t: 2, m_log2: 18, p: 1, pwd: "password", salt: "somesalt", hex: "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 170, version: Version::V0x13, t: 2, m_log2: 8, p: 1, pwd: "password", salt: "somesalt", hex: "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 174, version: Version::V0x13, t: 2, m_log2: 8, p: 2, pwd: "password", salt: "somesalt", hex: "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 178, version: Version::V0x13, t: 1, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 182, version: Version::V0x13, t: 4, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 186, version: Version::V0x13, t: 2, m_log2: 16, p: 1, pwd: "differentpassword", salt: "somesalt", hex: "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 190, version: Version::V0x13, t: 2, m_log2: 16, p: 1, pwd: "password", salt: "diffsalt", hex: "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271", algorithm: Algorithm::Argon2i, large_ram: false },
Vector { line: 233, version: Version::V0x13, t: 2, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 237, version: Version::V0x13, t: 2, m_log2: 18, p: 1, pwd: "password", salt: "somesalt", hex: "78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 241, version: Version::V0x13, t: 2, m_log2: 8, p: 1, pwd: "password", salt: "somesalt", hex: "9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 245, version: Version::V0x13, t: 2, m_log2: 8, p: 2, pwd: "password", salt: "somesalt", hex: "6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 249, version: Version::V0x13, t: 1, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 253, version: Version::V0x13, t: 4, m_log2: 16, p: 1, pwd: "password", salt: "somesalt", hex: "9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 257, version: Version::V0x13, t: 2, m_log2: 16, p: 1, pwd: "differentpassword", salt: "somesalt", hex: "0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde", algorithm: Algorithm::Argon2id, large_ram: false },
Vector { line: 261, version: Version::V0x13, t: 2, m_log2: 16, p: 1, pwd: "password", salt: "diffsalt", hex: "bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c", algorithm: Algorithm::Argon2id, large_ram: false },
];
fn hex32(hex: &str) -> [u8; 32] {
assert_eq!(hex.len(), 64, "tag must be 64 hex chars");
let bytes = hex.as_bytes();
let mut out = [0u8; 32];
for (i, byte) in out.iter_mut().enumerate() {
let hi = (bytes[2 * i] as char).to_digit(16).expect("hex digit");
let lo = (bytes[2 * i + 1] as char).to_digit(16).expect("hex digit");
*byte = (hi * 16 + lo) as u8;
}
out
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Group {
V0x10Argon2i,
V0x13Argon2i,
V0x13Argon2id,
All,
}
impl Group {
fn contains(self, v: &Vector) -> bool {
let is_v10 = v.version.as_u32() == Version::V0x10.as_u32();
let is_2id = matches!(v.algorithm, Algorithm::Argon2id);
match self {
Group::V0x10Argon2i => is_v10,
Group::V0x13Argon2i => !is_v10 && !is_2id,
Group::V0x13Argon2id => !is_v10 && is_2id,
Group::All => true,
}
}
}
pub unsafe fn check_official_vectors(backend: Backend, group: Group, include_large_ram: bool) {
let mut ran = 0u32;
for v in VECTORS {
if !group.contains(v) || (v.large_ram && !include_large_ram) {
continue;
}
let params = Params::new(1 << v.m_log2, v.t, v.p, 32)
.unwrap_or_else(|e| panic!("test.c:{} params rejected: {e}", v.line));
let mut out = [0u8; 32];
unsafe {
crate::core::hash_traced(
backend,
v.algorithm,
v.version,
¶ms,
v.pwd.as_bytes(),
v.salt.as_bytes(),
&[],
&[],
&mut out,
None,
)
}
.unwrap_or_else(|e| panic!("test.c:{} hash failed: {e}", v.line));
assert_eq!(
out,
hex32(v.hex),
"backend {backend}, test.c:{} (v={:#x} t={} m={} p={} {} pwd={:?} salt={:?})",
v.line,
v.version.as_u32(),
v.t,
1u32 << v.m_log2,
v.p,
v.algorithm.as_str(),
v.pwd,
v.salt,
);
ran += 1;
}
let expected = VECTORS
.iter()
.filter(|v| group.contains(v) && (include_large_ram || !v.large_ram))
.count() as u32;
assert_eq!(ran, expected, "not every vector in {group:?} ran");
assert!(ran > 0, "{group:?} selected no vectors");
}
pub fn skip_unless_available(backend: Backend) -> bool {
if backend.is_available() {
return false;
}
#[cfg(feature = "std")]
{
if let Ok(required) = std::env::var("ARGON2_REQUIRE_BACKEND")
&& required
.split(',')
.any(|name| name.trim() == backend.name())
{
panic!(
"ARGON2_REQUIRE_BACKEND lists {backend}, but \
Backend::{backend:?}.is_available() is false, so this test \
would have been silently skipped"
);
}
}
true
}
pub fn group_sizes() -> [usize; 3] {
[
Group::V0x10Argon2i,
Group::V0x13Argon2i,
Group::V0x13Argon2id,
]
.map(|g| VECTORS.iter().filter(|v| g.contains(v)).count())
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn hash_with_fill_segment_fn(
fill: FillSegmentFn,
algorithm: Algorithm,
version: Version,
params: &Params,
pwd: &[u8],
salt: &[u8],
out: &mut [u8],
) -> Result<(), Error> {
params.validate_for(pwd.len(), salt.len(), 0, 0)?;
if out.len() != params.output_len() {
return Err(Error::OutPtrMismatch);
}
let (memory_blocks, _segment_length, lane_length) = params.memory_layout();
let mut arena = Arena::new(memory_blocks as usize)?;
let mut blockhash =
crate::core::initial_hash(algorithm, version, params, pwd, salt, &[], &[])?;
let _h0: [u8; PREHASH_DIGEST_LENGTH] = {
let mut h = [0u8; PREHASH_DIGEST_LENGTH];
h.copy_from_slice(&blockhash[..PREHASH_DIGEST_LENGTH]);
h
};
let fill_first = crate::core::fill_first_blocks(
&mut blockhash,
arena.as_mut_slice(),
params.lanes(),
lane_length,
);
clear_internal_memory(&mut blockhash);
fill_first?;
let instance =
unsafe { Instance::new(arena.as_mut_ptr(), arena.len(), algorithm, version, params) };
for pass in 0..instance.passes {
for slice in 0..SYNC_POINTS {
for lane in 0..instance.lanes {
unsafe { fill(&instance, Position::new(pass, lane, slice, 0)) };
}
}
}
crate::core::finalize(&instance, out)
}
pub unsafe fn check_official_vectors_with_fill_fn(label: &str, fill: FillSegmentFn) {
let mut ran = 0u32;
for v in VECTORS {
if v.large_ram || v.m_log2 > 16 {
continue;
}
let params = Params::new(1 << v.m_log2, v.t, v.p, 32)
.unwrap_or_else(|e| panic!("test.c:{} params rejected: {e}", v.line));
let mut out = [0u8; 32];
unsafe {
hash_with_fill_segment_fn(
fill,
v.algorithm,
v.version,
¶ms,
v.pwd.as_bytes(),
v.salt.as_bytes(),
&mut out,
)
}
.unwrap_or_else(|e| panic!("test.c:{} hash failed: {e}", v.line));
assert_eq!(out, hex32(v.hex), "{label}, test.c:{}", v.line);
ran += 1;
}
assert!(ran >= 20, "{label}: only {ran} vectors ran");
}
}
#[cfg(test)]
mod tests {
use super::test_support::{
Group, XorShift64Star, assert_fill_block_matches_scalar, assert_fill_blocks_agree,
check_official_vectors, check_official_vectors_with_fill_fn, group_sizes,
};
use super::*;
use crate::fill_block::{Backend, backend, detect, fill_segment_fn};
use crate::params::QWORDS_IN_BLOCK;
unsafe fn fill_block_blocks<const SSSE3: bool>(
prev: &Block,
reference: &Block,
next: &mut Block,
with_xor: bool,
) {
unsafe {
let mut state = [_mm_setzero_si128(); OWORDS_IN_BLOCK];
let p = prev.as_ptr().cast::<__m128i>();
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm_loadu_si128(p.add(i));
}
fill_block::<SSSE3>(&mut state, reference, next, with_xor);
}
}
#[target_feature(enable = "sse2")]
unsafe fn fill_block_sse2_only(
prev: &Block,
reference: &Block,
next: &mut Block,
with_xor: bool,
) {
unsafe { fill_block_blocks::<false>(prev, reference, next, with_xor) }
}
#[target_feature(enable = "sse2,ssse3")]
unsafe fn fill_block_ssse3(prev: &Block, reference: &Block, next: &mut Block, with_xor: bool) {
unsafe { fill_block_blocks::<true>(prev, reference, next, with_xor) }
}
#[test]
fn sse2_only_fill_block_matches_scalar_over_2048_triples() {
assert!(
Backend::Sse2.is_available(),
"SSE2 is baseline on x86-64 and runtime-checked on x86"
);
unsafe {
assert_fill_block_matches_scalar(
"sse2-only",
fill_block_sse2_only,
2048,
0x0123_4567_89AB_CDEF,
);
}
}
#[test]
fn ssse3_fill_block_matches_scalar_over_2048_triples() {
if !super::probe_ssse3() {
return;
}
unsafe {
assert_fill_block_matches_scalar(
"ssse3",
fill_block_ssse3,
2048,
0x0123_4567_89AB_CDEF,
);
}
}
#[test]
fn sse2_and_ssse3_paths_agree() {
if !super::probe_ssse3() {
return;
}
unsafe {
assert_fill_blocks_agree(
"sse2-only vs ssse3",
fill_block_sse2_only,
fill_block_ssse3,
2048,
0xDEAD_BEEF_CAFE_F00D,
);
}
}
#[test]
fn all_zero_stays_all_zero() {
let mut next = Block::ZERO;
unsafe { fill_block_sse2_only(&Block::ZERO, &Block::ZERO, &mut next, false) };
assert_eq!(next, Block::ZERO);
if super::probe_ssse3() {
let mut next = Block::ZERO;
unsafe { fill_block_ssse3(&Block::ZERO, &Block::ZERO, &mut next, false) };
assert_eq!(next, Block::ZERO);
}
}
#[test]
fn aliasing_ref_and_next_is_safe_and_ignores_the_old_contents() {
let mut rng = XorShift64Star::new(0xA5A5_5A5A_1234_9876);
let prev = rng.next_block();
let reference = rng.next_block();
let mut want = Block::ZERO;
crate::fill_block::scalar::fill_block(&prev, &reference, &mut want, false);
let mut aliased = reference;
unsafe {
let mut state = [_mm_setzero_si128(); OWORDS_IN_BLOCK];
let p = prev.as_ptr().cast::<__m128i>();
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm_loadu_si128(p.add(i));
}
let ptr = &raw mut aliased;
fill_block::<false>(&mut state, ptr.cast_const(), ptr, false);
}
assert_eq!(aliased, want);
}
#[test]
fn every_input_word_reaches_the_output() {
let mut rng = XorShift64Star::new(0x1357_9BDF_2468_ACE0);
let prev = rng.next_block();
let reference = rng.next_block();
let base = {
let mut b = Block::ZERO;
unsafe { fill_block_sse2_only(&prev, &reference, &mut b, false) };
b
};
for w in 0..QWORDS_IN_BLOCK {
let mut flipped_prev = prev;
flipped_prev.0[w] ^= 1;
let mut got = Block::ZERO;
unsafe { fill_block_sse2_only(&flipped_prev, &reference, &mut got, false) };
assert_ne!(got, base, "flipping prev word {w} changed nothing");
}
}
#[test]
fn the_three_groups_partition_the_official_vector_set() {
assert_eq!(
group_sizes(),
[9, 9, 8],
"test.c has 9 v0x10-Argon2i, 9 v0x13-Argon2i and 8 v0x13-Argon2id vectors"
);
assert_eq!(
group_sizes().iter().sum::<usize>(),
super::test_support::VECTORS.len()
);
assert_eq!(super::test_support::VECTORS.len(), 26);
}
#[test]
fn official_vectors_sse2_forced_v0x10_argon2i() {
assert!(Backend::Sse2.is_available());
unsafe { check_official_vectors(Backend::Sse2, Group::V0x10Argon2i, false) };
}
#[test]
fn official_vectors_sse2_forced_v0x13_argon2i() {
assert!(Backend::Sse2.is_available());
unsafe { check_official_vectors(Backend::Sse2, Group::V0x13Argon2i, false) };
}
#[test]
fn official_vectors_sse2_forced_v0x13_argon2id() {
assert!(Backend::Sse2.is_available());
unsafe { check_official_vectors(Backend::Sse2, Group::V0x13Argon2id, false) };
}
#[test]
#[ignore = "TEST_LARGE_RAM: 1 GiB arena"]
fn official_vectors_with_the_sse2_backend_forced_including_large_ram() {
assert!(Backend::Sse2.is_available());
unsafe { check_official_vectors(Backend::Sse2, Group::All, true) };
}
#[test]
fn official_vectors_with_the_sse2_only_path_forced() {
unsafe {
check_official_vectors_with_fill_fn("sse2-only", fill_segment_sse2_only);
}
}
#[test]
fn official_vectors_with_the_ssse3_path_forced() {
if !super::probe_ssse3() {
return;
}
unsafe {
check_official_vectors_with_fill_fn("ssse3", fill_segment_ssse3);
}
}
#[test]
fn detection_selects_sse2_only_when_nothing_wider_is_available() {
assert_eq!(
Backend::Sse2.is_available(),
super::probe_sse2_for_test(),
"is_available() must agree with a direct probe"
);
let wider = Backend::Avx2.is_available() || Backend::Avx512.is_available();
if Backend::Sse2.is_available() && !wider {
assert_eq!(
detect(),
Backend::Sse2,
"SSE2 present and nothing wider, so SSE2 must win"
);
assert_eq!(
backend(),
Backend::Sse2,
"the cache must agree with detect()"
);
} else if !Backend::Sse2.is_available() {
assert_ne!(
detect(),
Backend::Sse2,
"SSE2 absent, so it must never be selected"
);
}
}
#[test]
fn dispatch_resolves_to_this_module() {
let f = fill_segment_fn(Backend::Sse2);
assert!(
core::ptr::fn_addr_eq(f, fill_segment as unsafe fn(&Instance, Position)),
"fill_segment_fn(Sse2) must be sse2::fill_segment"
);
assert!(!core::ptr::fn_addr_eq(
f,
crate::fill_block::scalar::fill_segment as unsafe fn(&Instance, Position)
));
}
#[test]
fn ssse3_cache_agrees_with_the_probe_and_is_stable() {
let first = have_ssse3();
assert_eq!(first, super::probe_ssse3());
assert_eq!(have_ssse3(), first);
assert_ne!(SSSE3_CACHE.load(Ordering::Relaxed), SSSE3_UNKNOWN);
assert_eq!(SSSE3_CACHE.load(Ordering::Relaxed), u8::from(first));
}
#[test]
fn ssse3_cache_recovers_from_an_unknown_byte() {
let truth = super::probe_ssse3();
SSSE3_CACHE.store(SSSE3_UNKNOWN, Ordering::Relaxed);
assert_eq!(have_ssse3(), truth);
SSSE3_CACHE.store(0xAB, Ordering::Relaxed);
assert_eq!(have_ssse3(), truth);
assert_eq!(SSSE3_CACHE.load(Ordering::Relaxed), u8::from(truth));
}
#[test]
fn rotations_match_scalar_rotate_right() {
for j in 0..8 {
assert_eq!(R24[j], ((j + 3) % 8) as u8, "R24 low half at {j}");
assert_eq!(
R24[j + 8],
(((j + 3) % 8) + 8) as u8,
"R24 high half at {j}"
);
assert_eq!(R16[j], ((j + 2) % 8) as u8, "R16 low half at {j}");
assert_eq!(
R16[j + 8],
(((j + 2) % 8) + 8) as u8,
"R16 high half at {j}"
);
}
let mut rng = XorShift64Star::new(0xFEED_FACE_DEAD_BEEF);
for _ in 0..256 {
let lo = rng.next_u64();
let hi = rng.next_u64();
unsafe {
let x = _mm_set_epi64x(hi as i64, lo as i64);
let mut out = [0u64; 2];
for (n, sse2, ssse3) in [
(32u32, rot32::<false>(x), rot32::<true>(x)),
(24, rot24::<false>(x), rot24::<true>(x)),
(16, rot16::<false>(x), rot16::<true>(x)),
(63, rot63::<false>(x), rot63::<true>(x)),
] {
_mm_storeu_si128(out.as_mut_ptr().cast(), sse2);
assert_eq!(out, [lo.rotate_right(n), hi.rotate_right(n)], "sse2 rot{n}");
if super::probe_ssse3() {
_mm_storeu_si128(out.as_mut_ptr().cast(), ssse3);
assert_eq!(
out,
[lo.rotate_right(n), hi.rotate_right(n)],
"ssse3 rot{n}"
);
}
}
}
}
}
#[test]
fn f_blamka_matches_the_scalar_reference() {
let mut rng = XorShift64Star::new(0x0BAD_C0DE_0BAD_C0DE);
for _ in 0..512 {
let (x0, x1) = (rng.next_u64(), rng.next_u64());
let (y0, y1) = (rng.next_u64(), rng.next_u64());
unsafe {
let r = f_blamka(
_mm_set_epi64x(x1 as i64, x0 as i64),
_mm_set_epi64x(y1 as i64, y0 as i64),
);
let mut out = [0u64; 2];
_mm_storeu_si128(out.as_mut_ptr().cast(), r);
assert_eq!(
out,
[
crate::fill_block::scalar::f_blamka(x0, y0),
crate::fill_block::scalar::f_blamka(x1, y1),
]
);
}
}
}
#[test]
fn diagonalize_round_trips_and_both_spellings_agree() {
let mut rng = XorShift64Star::new(0x5EED_5EED_5EED_5EED);
for _ in 0..256 {
let mut words = [0u64; 6];
for w in &mut words {
*w = rng.next_u64();
}
unsafe {
let load = |i: usize| _mm_set_epi64x(words[i] as i64, words[i] as i64 ^ 1);
let dump = |v: __m128i| {
let mut o = [0u64; 2];
_mm_storeu_si128(o.as_mut_ptr().cast(), v);
o
};
let (mut b0, mut b1) = (load(0), load(1));
let (mut c0, mut c1) = (load(2), load(3));
let (mut d0, mut d1) = (load(4), load(5));
let original = [b0, b1, c0, c1, d0, d1].map(dump);
diagonalize::<false>(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
let after_sse2 = [b0, b1, c0, c1, d0, d1].map(dump);
undiagonalize::<false>(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
assert_eq!(
[b0, b1, c0, c1, d0, d1].map(dump),
original,
"sse2 round trip"
);
if super::probe_ssse3() {
diagonalize::<true>(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
assert_eq!(
[b0, b1, c0, c1, d0, d1].map(dump),
after_sse2,
"the two DIAGONALIZE spellings must agree"
);
undiagonalize::<true>(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
assert_eq!(
[b0, b1, c0, c1, d0, d1].map(dump),
original,
"ssse3 round trip"
);
}
}
}
}
}
#[cfg(test)]
fn probe_sse2_for_test() -> bool {
#[cfg(feature = "std")]
{
std::arch::is_x86_feature_detected!("sse2")
}
#[cfg(not(feature = "std"))]
{
cfg!(target_feature = "sse2")
}
}