use crate::conversion::{bit_unpack, coeff_from_half_byte, coeff_from_three_bytes};
use crate::helpers::{bit_length, is_in_range};
use crate::types::{R, R0, T, T0};
use sha3::digest::core_api::{Buffer, ExtendableOutputCore, UpdateCore, XofReaderCore};
use sha3::digest::{ExtendableOutput, Update, XofReader};
use sha3::{Shake128, Shake256, Shake256Core};
use zeroize::{Zeroize, Zeroizing};
pub(crate) fn h256_xof(v: &[&[u8]]) -> impl XofReader {
let mut hasher = Shake256::default();
for b in v {
hasher.update(b);
}
hasher.finalize_xof()
}
#[inline(never)]
pub(crate) 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)]
pub(crate) fn scrub_hash_stack() {
let mut area = [0u64; 256];
area.zeroize();
let _ = core::hint::black_box(&area);
}
pub(crate) fn g128_xof(v: &[&[u8]]) -> impl XofReader {
let mut hasher = Shake128::default();
for b in v {
hasher.update(b);
}
hasher.finalize_xof()
}
pub(crate) fn sample_in_ball<const CTEST: bool>(tau: i32, rho: &[u8]) -> R {
let tau = usize::try_from(tau).expect("Alg 29: try_from fail");
let mut c = R0;
let mut h_ctx = h256_xof(&[rho]);
let mut h = [0u8; 8];
h_ctx.read(&mut h);
for i in (256 - tau)..=255 {
let mut j = [i.to_le_bytes()[0]]; if !CTEST {
h_ctx.read(&mut j);
}
while usize::from(j[0]) > i {
h_ctx.read(&mut j);
}
c.0[i] = c.0[usize::from(j[0])];
let index = i + tau - 256;
let bite = h[index / 8];
let shifted = bite >> (index & 0x07);
c.0[usize::from(j[0])] = 1 - 2 * i32::from(shifted & 0x01);
}
debug_assert!(
c.0.iter().map(|&e| usize::from(e != 0)).sum::<usize>() == tau,
"Alg 29: bad hamming weight (a)"
);
debug_assert!(
c.0.iter().map(|&e| e & 1).sum::<i32>() == tau.try_into().expect("cannot fail"),
"Alg 29: bad hamming weight (b)"
);
c
}
pub(crate) fn rej_ntt_poly<const CTEST: bool>(rhos: &[&[u8]]) -> T {
debug_assert_eq!(rhos.iter().map(|&i| i.len()).sum::<usize>(), 272 / 8, "Alg 30: bad rho size");
let mut a_hat = T0;
let mut j = 0;
let mut xof = g128_xof(rhos);
while j < 256 {
let mut h5 = [0u8; 3];
xof.read(&mut h5); let a_hat_j = coeff_from_three_bytes::<CTEST>(h5);
if let Ok(res) = a_hat_j {
a_hat.0[j] = res;
j += 1;
}
}
a_hat
}
#[inline(never)]
pub(crate) fn rej_bounded_poly<const CTEST: bool>(eta: i32, rhos: &[&[u8]]) -> R {
debug_assert_eq!(rhos.iter().map(|&i| i.len()).sum::<usize>(), 528 / 8, "Alg 31: bad rho size");
let mut a = R0;
let mut j = 0;
let mut core = Shake256Core::default();
let mut buffer = Buffer::<Shake256Core>::default();
for r in rhos {
buffer.digest_blocks(r, |blocks| core.update_blocks(blocks));
}
let mut reader = core.finalize_xof_core(&mut buffer);
buffer.pad_with_zeros().as_mut_slice().zeroize();
let mut block = reader.read_block();
let mut pos = 0;
while j < 256 {
if pos == block.len() {
block.as_mut_slice().zeroize();
block = reader.read_block();
pos = 0;
}
let z = block[pos];
pos += 1;
let z0 = coeff_from_half_byte::<CTEST>(eta, z & 0x0f);
let z1 = coeff_from_half_byte::<CTEST>(eta, z >> 4);
if let Ok(z0) = z0 {
a.0[j] = z0;
j += 1;
}
if let Ok(z1) = z1 {
if j < 256 {
a.0[j] = z1;
j += 1;
}
}
}
block.as_mut_slice().zeroize();
a
}
#[allow(clippy::cast_possible_truncation)] pub(crate) fn expand_a<const CTEST: bool, const K: usize, const L: usize>(
rho: &[u8; 32],
) -> [[T; L]; K] {
let cap_a_hat: [[T; L]; K] = core::array::from_fn(|r| {
core::array::from_fn(|s| rej_ntt_poly::<CTEST>(&[&rho[..], &[s as u8], &[r as u8]]))
});
cap_a_hat
}
#[allow(clippy::cast_possible_truncation)] pub(crate) fn expand_s<const CTEST: bool, const K: usize, const L: usize>(
eta: i32, rho: &[u8; 64],
) -> ([R; L], [R; K]) {
let s1: [R; L] = core::array::from_fn(|r| {
let s = rej_bounded_poly::<CTEST>(eta, &[rho, &[r as u8], &[0]]);
scrub_hash_stack();
s
});
let s2: [R; K] = core::array::from_fn(|r| {
let s = rej_bounded_poly::<CTEST>(eta, &[rho, &[(r + L) as u8], &[0]]);
scrub_hash_stack();
s
});
debug_assert!(s1.iter().all(|r| is_in_range(r, eta, eta)), "Alg 33: s1 out of range");
debug_assert!(s2.iter().all(|r| is_in_range(r, eta, eta)), "Alg 33: s2 out of range");
(s1, s2)
}
pub(crate) fn expand_mask<const L: usize>(gamma1: i32, rho: &[u8; 64], mu: u16) -> [R; L] {
let mut y = [R0; L];
let mut v = Zeroizing::new([0u8; 32 * 20]);
let c = 1 + bit_length(gamma1 - 1); debug_assert!((c == 18) || (c == 20), "Alg 34: illegal c");
for r in 0..u16::try_from(L).expect("Alg 34: try_from1 fail") {
let n = mu + r;
shake256_wiped(&[rho, &n.to_le_bytes()], &mut v[..]);
scrub_hash_stack();
y[r as usize] =
bit_unpack(&v[0..32 * c], gamma1 - 1, gamma1).expect("Alg 34: try_from2 fail");
}
debug_assert!(
y.iter().all(|r| is_in_range(r, gamma1 - 1, gamma1)),
"Alg 34: s coeff out of range"
);
y
}