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;
#[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]
pub fn new() -> result::Result<Self, GridError>
{
let area = W * H;
if area > 1 && W > 1
{
Ok(Self([[0i64; W]; H]))
} else
{
Err(GridError::InvalidDimensions { width: W, height: H })
}
}
pub fn from_key(vec: &[i64]) -> result::Result<Self, GridError>
{
let grid_area = W * H;
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 = (i & 63) as u32;
a = a.rotate_left(rot);
b = b.rotate_right(rot);
key_grid[i / W][i % W] = a ^ b ^ (i as i64);
}
Ok(key_grid)
}
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()
}
#[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 mut new_row = [0i64; W];
for s in 0..W
{
let is_match: Choice = (s as u64).ct_eq(&(shifts[i] as u64));
for dst in 0..W
{
new_row[dst].conditional_assign(&row[(dst + s) % W], is_match);
}
}
*row = new_row;
}
#[cfg(not(feature = "constant-time"))]
{
row.rotate_left(shifts[i]);
}
}
}
#[inline(always)]
pub fn precalculate_rotations(&self, round_index: usize) -> [usize; W]
{
const MASK: usize = consts::MC_COEFFICIENTS.len() - 1;
let mut rotations = [0usize; W];
for col in 0..W
{
let mut key_sum: i64 = 0;
for row in 0..H
{
key_sum = key_sum.wrapping_add(self[row][col]);
}
rotations[col] = ((key_sum as u64)
.wrapping_mul(col as u64)
.wrapping_mul(round_index as u64)
.wrapping_mul(consts::DELTA_64)) as usize & MASK;
}
rotations
}
#[inline(always)]
pub fn mix_columns(&mut self, rotations: &[usize; W])
{
const MASK: usize = consts::MC_COEFFICIENTS.len() - 1;
let mut col_in = [0i64; H];
let mut col_out = [0i64; H];
for col in 0..W
{
for r in 0..H
{
col_in[r] = self[r][col];
}
let rotation = rotations[col];
for row in 0..H
{
let mut sum_vec = i64x4::ZERO;
let mut chunks = col_in.chunks_exact(4);
let mut k_base = 0;
for chunk in chunks.by_ref()
{
let mut arr = [0i64; 4];
arr.copy_from_slice(chunk);
let vec_in = i64x4::from(arr);
let mut c_arr = [0i64; 4];
for i in 0..4
{
c_arr[i] = consts::MC_COEFFICIENTS[(k_base + i + row + rotation) & MASK];
}
let vec_coeff = i64x4::from(c_arr);
sum_vec += vec_in * vec_coeff;
k_base += 4;
}
let arr_res: [i64; 4] = sum_vec.into();
let mut scalar_sum = arr_res.iter().fold(0i64, |acc, &x| acc.wrapping_add(x));
for (i, &val) in chunks.remainder().iter().enumerate()
{
let coeff = consts::MC_COEFFICIENTS[(k_base + i + row + rotation) & MASK];
scalar_sum = scalar_sum.wrapping_add(val.wrapping_mul(coeff))
}
col_out[row] = scalar_sum;
}
for r in 0..H
{
self[r][col] = col_out[r];
}
}
}
#[inline(always)]
pub fn increment(&mut self, amount: &mut 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;
}
}
#[cfg(feature = "constant-time")]
{
*amount = carry;
}
}
}
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 <= 1
{
write!(f, "Invalid dimensions: expected width larger than 1, got {width}")
} else
{
write!(f, "Invalid dimensions: expected area larger than 1, got {width}x{height} ({})", width * 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 {}