use std::
{
result,
error::Error,
iter::Flatten,
array::IntoIter,
slice::
{
self,
Iter,
IterMut,
},
ops::
{
Index,
IndexMut,
BitXorAssign,
},
fmt::
{
Display,
Formatter,
Result,
LowerHex,
},
};
use zeroize::Zeroize;
use wide::i64x4;
use rayon::prelude::{ ParallelSlice, ParallelIterator };
use crate::{ consts, gf };
#[cfg(feature = "constant-time")]
use subtle::
{
Choice,
ConstantTimeEq,
ConditionallySelectable,
};
#[derive(Clone, Debug, Zeroize)]
#[zeroize(drop)]
pub struct Grid <
const W: usize = { consts::DEFAULT_GRID_WIDTH },
const H: usize = { consts::DEFAULT_GRID_HEIGHT },
>([[i64; W]; H]);
#[derive(Debug, Clone, PartialEq)]
pub enum GridError
{
InvalidDimensions
{
width: usize,
height: usize,
},
InvalidByteLength
{
expected_mod: usize,
actual_len: usize,
},
InvalidKeyLength
{
expected_len: usize,
actual_len: usize,
},
InvalidUnicode
{
value: u32,
},
}
macro_rules! subcell {
($v0:ident, $v1:ident, $sum:ident, $delta:expr, $round_tweak:expr, $mask:expr) =>
{
$v0 = ($v0 ^ $round_tweak) & $mask;
for _ in 0..consts::SUBCELL_ROUNDS
{
$sum += $delta;
$v0 = ($v0 + ((($v1 << 4) ^ ($v1 >> 5)) + $v1 ^ $sum)) & $mask; $v1 = ($v1 + ((($v0 << 4) ^ ($v0 >> 5)) + $v0 ^ $sum)) & $mask; }
$v1 = ($v1 ^ $round_tweak) & $mask;
}
}
impl<const W: usize, const H: usize> Grid<W, H>
{
#[inline]
#[must_use]
pub fn new() -> result::Result<Self, GridError>
{
if W > 0 && (H == 4 || H == 8 || H == 16)
{
Ok(Self([[0i64; W]; H]))
} else
{
Err(GridError::InvalidDimensions { width: W, height: H })
}
}
#[must_use]
pub fn from_key(vec: &[i64]) -> result::Result<Self, GridError>
{
let grid_area = W * H;
if vec.len() < grid_area * 2
{
return Err(GridError::InvalidKeyLength
{
expected_len: grid_area * 2,
actual_len: vec.len(),
});
}
let mut key_grid = Self::new()?;
for i in 0..grid_area
{
let mut a = vec[i].wrapping_add(vec[i + grid_area]);
let mut b = vec[i] ^ vec[i + grid_area];
let rot_a = (b as u32) & 63;
let rot_b = (a as u32) & 63;
a = a.rotate_left(rot_a);
b = b.rotate_right(rot_b);
key_grid[i / W][i % W] = a ^ b ^ (i as i64);
}
Ok(key_grid)
}
#[must_use]
pub fn from_bytes(bytes: &[u8]) -> result::Result<Vec<Self>, GridError>
{
let matrix_size = W * H * 8;
if bytes.len() % matrix_size != 0
{
return Err(GridError::InvalidByteLength { expected_mod: matrix_size, actual_len: bytes.len() });
}
bytes.par_chunks(matrix_size).map(|chunk|
{
let mut grid = Grid::new()?;
for j in 0..H
{
for i in 0..W
{
let start = (j * W + i) * 8;
let slice = &chunk[start..start + 8];
grid[j][i] = i64::from_be_bytes(slice.try_into().unwrap());
}
}
Ok(grid)
}).collect()
}
#[must_use]
pub fn from_flat(vec: &[i64]) -> result::Result<Self, GridError>
{
let mut grid = Self::new()?;
for (i, &val) in vec.iter().take(W * H).enumerate()
{
grid[i / W][i % W] = val;
}
Ok(grid)
}
#[must_use]
pub fn to_flat(&self) -> Vec<i64>
{
self.iter().flat_map(|row| row.iter().copied()).collect()
}
#[inline(always)]
pub fn iter(&self) -> Iter<'_, [i64; W]>
{
self.0.iter()
}
#[inline(always)]
pub fn iter_mut(&mut self) -> IterMut<'_, [i64; W]>
{
self.0.iter_mut()
}
#[inline(always)]
pub fn width(&self) -> usize
{
W
}
#[inline(always)]
pub fn height(&self) -> usize
{
H
}
#[inline(always)]
pub fn xor_grids(&mut self, key_grid: &Grid<W, H>)
{
let self_data: &mut [i64] = unsafe
{
slice::from_raw_parts_mut(self.0.as_mut_ptr() as *mut i64, W * H)
};
let key_data: &[i64] = unsafe
{
slice::from_raw_parts(key_grid.0.as_ptr() as *const i64, W * H)
};
let mut chunks = self_data.chunks_exact_mut(4);
let mut key_chunks = key_data.chunks_exact(4);
for (self_chunk, key_chunk) in chunks.by_ref().zip(key_chunks.by_ref())
{
let mut self_arr = [0i64; 4];
self_arr.copy_from_slice(self_chunk);
let self_vec = i64x4::from(self_arr);
let mut key_arr = [0i64; 4];
key_arr.copy_from_slice(key_chunk);
let key_vec = i64x4::from(key_arr);
let result_vec = self_vec ^ key_vec;
let result_arr: [i64; 4] = result_vec.into();
self_chunk.copy_from_slice(&result_arr);
}
for (s, k) in chunks.into_remainder().iter_mut().zip(key_chunks.remainder())
{
*s ^= k;
}
}
#[inline(always)]
pub fn subcell(&mut self, round: usize)
{
let data: &mut [i64] = unsafe
{
slice::from_raw_parts_mut(self.0.as_mut_ptr() as *mut i64, W * H)
};
let mut chunks_iter = data.chunks_exact_mut(4);
let mask_simd = i64x4::splat(0xFFFF_FFFF); for chunk in &mut chunks_iter
{
let x = i64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
let mut v0 = x & mask_simd; let mut v1 = (x >> 32) & mask_simd;
let mut sum = i64x4::ZERO;
subcell!
(
v0,
v1,
sum,
i64x4::splat(consts::DELTA_32 as i64),
i64x4::splat(round as i64),
mask_simd
);
let res_vec: i64x4 = (v1 << 32) | v0;
let res_arr: [i64; 4] = res_vec.into();
chunk.copy_from_slice(&res_arr);
}
let mask_scalar = 0xFFFF_FFFF;
for cell in chunks_iter.into_remainder()
{
let x = *cell as u64;
let mut v0 = (x & mask_scalar) as u32;
let mut v1 = ((x >> 32) & mask_scalar) as u32;
let mut sum = 0u32;
subcell!
(
v0,
v1,
sum,
consts::DELTA_32,
round as u32,
mask_scalar as u32
);
*cell = (((v1 as u64) << 32) | (v0 as u64)) as i64;
}
}
#[inline(always)]
pub fn precalculate_shifts(&self) -> [usize; H]
{
let mut shifts = [0usize; H];
for (i, row) in self.iter().enumerate()
{
let hash_chunk = row.iter().fold(0i64, |acc, &x| acc ^ x);
#[cfg(feature = "constant-time")]
{
shifts[i] = ((hash_chunk as u64 as u128 * W as u128) >> 64) as usize;
}
#[cfg(not(feature = "constant-time"))]
{
shifts[i] = hash_chunk.rem_euclid(W as i64) as usize;
}
}
shifts
}
#[inline(always)]
pub fn shift_rows(&mut self, shifts: &[usize; H])
{
for (i, row) in self.iter_mut().enumerate()
{
#[cfg(not(feature = "constant-time"))]
if shifts[i] == 0 { continue; }
#[cfg(feature = "constant-time")]
{
let shift = shifts[i];
let mut tmp = *row;
let mut stride = 1usize;
let mut bit = 0usize;
while stride < W
{
let do_rotate = Choice::from(((shift >> bit) & 1) as u8);
let mut rotated = [0i64; W];
for j in 0..W
{
rotated[j] = tmp[(j + stride) % W];
}
for j in 0..W
{
tmp[j].conditional_assign(&rotated[j], do_rotate);
}
bit += 1;
stride <<= 1;
}
*row = tmp;
}
#[cfg(not(feature = "constant-time"))]
{
row.rotate_left(shifts[i]);
}
}
}
#[inline(always)]
pub fn mix_columns(&mut self)
{
let mut out = [[0u64; W]; H];
for k in 0..H
{
for r in 0..H
{
let coeff = match H
{
4 => consts::MDS_4[r][k],
8 => consts::MDS_8[r][k],
16 => consts::MDS_16[r][k],
_ => unreachable!("tf")
};
let acc = &mut out[r];
let src = &self.0[k]; let mut c = 0usize;
while c + 3 < W
{
let (p0, p1) = gf::mul_const2(src[c] as u64, src[c + 1] as u64, coeff);
let (p2, p3) = gf::mul_const2(src[c + 2] as u64, src[c + 3] as u64, coeff);
acc[c] ^= p0;
acc[c + 1] ^= p1;
acc[c + 2] ^= p2;
acc[c + 3] ^= p3;
c += 4;
}
while c + 1 < W
{
let (p0, p1) = gf::mul_const2(src[c] as u64, src[c + 1] as u64, coeff);
acc[c] ^= p0;
acc[c + 1] ^= p1;
c += 2;
}
if c < W
{
acc[c] ^= gf::mul(src[c] as u64, coeff);
}
}
}
for r in 0..H
{
for c in 0..W
{
self[r][c] = out[r][c] as i64;
}
}
}
#[inline(always)]
pub fn increment(&mut self, amount: u64)
{
let data: &mut [i64] = unsafe
{
slice::from_raw_parts_mut(self.0.as_mut_ptr() as *mut i64, W * H)
};
let mut carry = amount;
for cell in data.iter_mut()
{
let (result, overflow) = (*cell as u64).overflowing_add(carry);
*cell = result as i64;
#[cfg(not(feature = "constant-time"))]
{
if !overflow { return; }
carry = 1;
}
#[cfg(feature = "constant-time")]
{
carry = overflow as u64;
}
}
}
}
impl<const W: usize, const H: usize> IntoIterator for Grid<W, H>
{
type Item = i64;
type IntoIter = Flatten<IntoIter<[i64; W], H>>;
#[inline]
fn into_iter(self) -> Self::IntoIter
{
self.0.into_iter().flatten()
}
}
impl<const W: usize, const H: usize> Index<usize> for Grid<W, H>
{
type Output = [i64; W];
#[inline]
fn index(&self, y: usize) -> &Self::Output
{
&self.0[y]
}
}
impl<const W: usize, const H: usize> IndexMut<usize> for Grid<W, H>
{
#[inline]
fn index_mut(&mut self, y: usize) -> &mut Self::Output
{
&mut self.0[y]
}
}
impl<const W: usize, const H: usize> BitXorAssign<&Grid<W, H>> for Grid<W, H>
{
#[inline]
fn bitxor_assign(&mut self, rhs: &Grid<W, H>)
{
self.xor_grids(rhs);
}
}
#[cfg(feature = "constant-time")]
impl<const W: usize, const H: usize> ConstantTimeEq for Grid<W, H>
{
fn ct_eq(&self, other: &Self) -> Choice
{
let mut result = Choice::from(1);
for (row_a, row_b) in self.iter().zip(other.iter())
{
for (cell_a, cell_b) in row_a.iter().zip(row_b.iter())
{
result &= cell_a.ct_eq(cell_b);
}
}
result
}
}
impl<const W: usize, const H: usize> PartialEq for Grid<W, H>
{
fn eq(&self, other: &Self) -> bool
{
#[cfg(feature = "constant-time")]
{
self.ct_eq(other).into()
}
#[cfg(not(feature = "constant-time"))]
{
self.0 == other.0
}
}
}
impl<const W: usize, const H: usize> Display for Grid<W, H>
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result
{
let cells: Vec<Vec<[String; 4]>> = self.iter().map(|row|
{
row.iter().map(|val|
{
let s = val.to_string();
let chunk_size = (s.len() + 3) / 4;
let mut lines = [String::new(), String::new(), String::new(), String::new()];
for (i, chunk) in s.chars().collect::<Vec<_>>().chunks(chunk_size).enumerate()
{
lines[i] = chunk.iter().collect();
}
lines
}).collect()
}).collect();
let max_width = cells.iter()
.flat_map(|row| row.iter())
.flat_map(|lines| lines.iter())
.map(|line| line.len())
.max()
.unwrap_or(1);
let border = format!
(
"+{}+\n",
(0..self.width()).map(|_| "-".repeat(max_width + 2)).collect::<Vec<_>>().join("+")
);
for row in &cells
{
f.write_str(&border)?;
for line_idx in 0..4
{
for cell in row
{
write!(f, "| {:>width$} ", cell[line_idx], width = max_width)?;
}
writeln!(f, "|")?;
}
}
f.write_str(&border)
}
}
impl<const W: usize, const H: usize> LowerHex for Grid<W, H>
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result
{
for row in self.iter()
{
for cell in row
{
write!(f, "{:016x}", cell)?;
}
}
Ok(())
}
}
impl Display for GridError
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result
{
match self
{
GridError::InvalidDimensions { width, height } =>
{
if *width == 0
{
write!(f, "Invalid dimensions: expected width larger than 0")
} else
{
write!(f, "Invalid dimensions: expected height is 4, 8 or 16, got {height}")
}
},
GridError::InvalidByteLength { expected_mod, actual_len } =>
{
write!(f, "Invalid byte length: expected multiple of {expected_mod} bytes for this Grid, got {actual_len}")
},
GridError::InvalidKeyLength { expected_len, actual_len } =>
{
write!(f, "Invalid key length: expected length {expected_len}, got {actual_len}")
},
GridError::InvalidUnicode { value } =>
{
write!(f, "Invalid unicode scalar value: {value:#X} (possible wrong key)")
},
}
}
}
impl Error for GridError {}