use crate::ntt::multiply_ntts_acc;
use crate::types::Z;
use crate::Q;
use sha3::digest::core_api::{
Buffer, ExtendableOutputCore, FixedOutputCore, UpdateCore, XofReaderCore,
};
use sha3::digest::{ExtendableOutput, Update, XofReader};
use sha3::{Digest, Sha3_256, Sha3_512Core, Shake128, Shake256Core};
use zeroize::Zeroize;
macro_rules! ensure {
($cond:expr, $msg:literal $(,)?) => {
if !$cond {
return Err($msg);
}
};
}
pub(crate) use ensure;
pub(crate) fn add_poly(poly_a: &mut [Z; 256], poly_b: &[Z; 256]) {
for (a, b) in poly_a.iter_mut().zip(poly_b) {
*a = a.add(*b);
}
}
pub(crate) fn add_vecs<const K: usize>(vec_a: &mut [[Z; 256]; K], vec_b: &[[Z; 256]; K]) {
for (a, b) in vec_a.iter_mut().zip(vec_b) {
add_poly(a, b);
}
}
pub(crate) fn mul_mat_vec<const K: usize>(
a_hat: &[[[Z; 256]; K]; K], u_hat: &[[Z; 256]; K], w_hat: &mut [[Z; 256]; K],
) {
for i in 0..K {
w_hat[i].fill(Z::default());
#[allow(clippy::needless_range_loop)] for j in 0..K {
multiply_ntts_acc(&mut w_hat[i], &a_hat[i][j], &u_hat[j]);
}
}
}
pub(crate) fn mul_mat_t_vec<const K: usize>(
a_hat: &[[[Z; 256]; K]; K], u_hat: &[[Z; 256]; K], y_hat: &mut [[Z; 256]; K],
) {
#[allow(clippy::needless_range_loop)] for i in 0..K {
y_hat[i].fill(Z::default());
#[allow(clippy::needless_range_loop)] for j in 0..K {
multiply_ntts_acc(&mut y_hat[i], &a_hat[j][i], &u_hat[j]); }
}
}
pub(crate) fn dot_t_prod<const K: usize>(
u_hat: &[[Z; 256]; K], v_hat: &[[Z; 256]; K], z_hat: &mut [Z; 256],
) {
z_hat.fill(Z::default());
for j in 0..K {
multiply_ntts_acc(z_hat, &u_hat[j], &v_hat[j]);
}
}
pub(crate) fn prf<const ETA_64: usize>(s: &[u8; 32], b: u8, out: &mut [u8; ETA_64]) {
shake256_wiped(&[s, &[b]], out);
scrub_hash_stack();
}
#[inline(never)]
fn shake256_wiped(inputs: &[&[u8]], out: &mut [u8]) {
let mut core = Shake256Core::default();
let mut buffer = Buffer::<Shake256Core>::default();
for i in inputs {
buffer.digest_blocks(i, |blocks| core.update_blocks(blocks));
}
let mut reader = core.finalize_xof_core(&mut buffer);
buffer.pad_with_zeros().as_mut_slice().zeroize();
for chunk in out.chunks_mut(136) {
let mut block = reader.read_block();
chunk.copy_from_slice(&block[..chunk.len()]);
block.as_mut_slice().zeroize();
}
}
#[inline(never)]
fn scrub_hash_stack() {
let mut area = [0u64; 256];
area.zeroize();
let _ = core::hint::black_box(&area);
}
#[must_use]
pub(crate) fn xof(rho: &[u8; 32], i: u8, j: u8) -> impl XofReader {
let mut hasher = Shake128::default();
hasher.update(rho);
hasher.update(&[i]);
hasher.update(&[j]);
hasher.finalize_xof()
}
pub(crate) fn g(bytes: &[&[u8]], a: &mut [u8; 32], b: &mut [u8; 32]) {
sha3_512_wiped(bytes, a, b);
scrub_hash_stack();
}
#[inline(never)]
fn sha3_512_wiped(bytes: &[&[u8]], a: &mut [u8; 32], b: &mut [u8; 32]) {
let mut core = Sha3_512Core::default();
let mut buffer = Buffer::<Sha3_512Core>::default();
for x in bytes {
buffer.digest_blocks(x, |blocks| core.update_blocks(blocks));
}
let mut digest = sha3::digest::Output::<Sha3_512Core>::default();
core.finalize_fixed_core(&mut buffer, &mut digest);
buffer.pad_with_zeros().as_mut_slice().zeroize();
a.copy_from_slice(&digest[0..32]);
b.copy_from_slice(&digest[32..64]);
digest.as_mut_slice().zeroize();
}
#[must_use]
pub(crate) fn h(bytes: &[u8]) -> [u8; 32] {
let mut hasher = Sha3_256::new();
Digest::update(&mut hasher, bytes);
let digest = hasher.finalize();
digest.into()
}
pub(crate) fn j(z: &[u8; 32], ct: &[u8], out: &mut [u8; 32]) {
shake256_wiped(&[z, ct], out);
scrub_hash_stack();
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn compress_vector(d: u32, inout: &mut [Z]) {
const M: u32 = (1u64 << 36).div_ceil(Q as u64) as u32;
for x_ref in &mut *inout {
let y = (x_ref.get_u32() << d) + (u32::from(Q) >> 1);
let result = (u64::from(y) * u64::from(M)) >> 36;
x_ref.set_u16(result as u16);
}
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn decompress_vector(d: u32, inout: &mut [Z]) {
for y_ref in &mut *inout {
let qy = u32::from(Q) * y_ref.get_u32() + (1 << (d - 1)); y_ref.set_u16((qy >> d) as u16);
}
}
#[cfg(test)]
mod tests {
use crate::helpers::{compress_vector, decompress_vector};
use crate::types::Z;
use crate::Q;
fn spec_compress(d: u32, x: u32) -> u32 {
((x << (d + 1)) + u32::from(Q)) / (2 * u32::from(Q)) % (1 << d)
}
fn spec_decompress(d: u32, y: u32) -> u32 { (2 * u32::from(Q) * y + (1 << d)) >> (d + 1) }
fn zs() -> [Z; 4096] { core::array::from_fn(|i| Z(u16::try_from(i).unwrap())) }
fn decompress(d: u32, y: u16) -> u32 {
let mut a = [Z(y)];
decompress_vector(d, &mut a);
a[0].get_u32()
}
fn compress(d: u32, x: u16) -> u32 {
let mut a = [Z(x)];
compress_vector(d, &mut a);
a[0].get_u32() % (1 << d)
}
#[test]
fn decompress_exhaustive() {
for d in 1..=11 {
let mut a = zs();
let v = &mut a[..1 << d];
decompress_vector(d, v);
for (y, z) in v.iter().enumerate() {
let y = u32::try_from(y).unwrap();
assert_eq!(z.get_u32(), spec_decompress(d, y), "Decompress_{d}({y})");
}
}
assert_eq!(decompress(4, 1), 208);
assert_eq!(decompress(2, 1), 832);
assert_eq!(decompress(11, 1024), 1665);
}
#[test]
fn compress_exhaustive() {
for d in 1..=11 {
let mut a = zs();
let v = &mut a[..usize::from(Q)];
compress_vector(d, v);
for (x, z) in v.iter().enumerate() {
let x = u32::try_from(x).unwrap();
assert_eq!(z.get_u32() % (1 << d), spec_compress(d, x), "Compress_{d}({x})");
}
}
assert_eq!(compress(1, 832), 0);
assert_eq!(compress(1, 833), 1);
assert_eq!(compress(1, 2496), 1);
assert_eq!(compress(1, 2497), 0);
}
#[test]
fn compress_decompress_roundtrip() {
for d in 1..=11 {
let mut a = zs();
let v = &mut a[..1 << d];
decompress_vector(d, v);
compress_vector(d, v);
for (y, z) in v.iter().enumerate() {
let y = u32::try_from(y).unwrap();
assert_eq!(z.get_u32() % (1 << d), y, "Compress_{d}(Decompress_{d}({y}))");
}
}
}
}