use super::{K, N, Q};
use crate::{
low,
mid::sha3::{self, SqueezingSpongeObligation},
};
pub(super) fn sample_ntt(rho: &[u8; 32]) -> [i16; K * K * N] {
_sample_ntt::<false>(rho)
}
pub(super) fn sample_ntt_transposed(rho: &[u8; 32]) -> [i16; K * K * N] {
_sample_ntt::<true>(rho)
}
fn _sample_ntt<const TRANSPOSED: bool>(rho: &[u8; 32]) -> [i16; K * K * N] {
let mut r = [0; _];
let (output_8, output_tail) = r.split_at_mut(N * 8);
let inputs = match TRANSPOSED {
false => &[
[0, 0],
[1, 0],
[2, 0],
[0, 1],
[1, 1],
[2, 1],
[0, 2],
[1, 2],
],
true => &[
[0, 0],
[0, 1],
[0, 2],
[1, 0],
[1, 1],
[1, 2],
[2, 0],
[2, 1],
],
};
sample_poly_ntt_8x(rho, inputs, output_8.try_into().unwrap());
Shake128ForMlKem::new(&[rho, &[2, 2]]).sample_into(output_tail.try_into().unwrap());
r
}
fn sample_poly_ntt_8x(rho: &[u8; 32], inputs: &[[u8; 2]; 8], outputs: &mut [i16; N * 8]) {
let mut buf = [0; 40];
buf[..32].copy_from_slice(rho);
buf[34] = sha3::SHAKE_PAD_BYTE;
let inputs = inputs.map(|ij| {
let mut buf_ij = buf;
buf_ij[32..34].clone_from_slice(&ij);
buf_ij
});
low::mlkem768_sample_poly_ntt_8x(&inputs, outputs, _sample_poly_ntt_8x, _sample_poly_ntt_tail)
}
fn _sample_poly_ntt_8x(inputs: &[[u8; 40]; 8], outputs: &mut [i16; N * 8]) {
let (input_quads, _) = inputs.as_chunks::<4>();
let (output_quads, _) = outputs.as_chunks_mut::<{ N * 4 }>();
for (inputs, outputs) in input_quads.iter().zip(output_quads.iter_mut()) {
let sponge_4x = sha3::SqueezingSponge4xShake128::new(inputs);
let mut samples = [[0; sha3::SHAKE_128_R_BYTES * 3]; 4];
let tail_sponges = sponge_4x.squeeze(&mut samples);
for ((output, samples), tail_sponge) in outputs
.as_chunks_mut::<N>()
.0
.iter_mut()
.zip(samples.iter())
.zip(tail_sponges)
{
let tail = Shake128ForMlKem::sample(samples, output);
if !tail.is_empty() {
Shake128ForMlKem {
sponge: tail_sponge.restitute(),
}
.tail_case(tail);
}
}
}
}
fn _sample_poly_ntt_tail(tail_sponge: &mut [u64; 25], tail: &mut [i16]) {
Shake128ForMlKem {
sponge: SqueezingSpongeObligation(*tail_sponge).restitute(),
}
.tail_case(tail);
}
struct Shake128ForMlKem {
sponge: sha3::Shake128SqueezingSponge,
}
impl Shake128ForMlKem {
fn new(message: &[&[u8]]) -> Self {
Self {
sponge: sha3::Shake128Sponge::new_for_message(message),
}
}
fn sample_into(mut self, output: &mut [i16; 256]) {
let mut initial_bytes = [0; sha3::SHAKE_128_R_BYTES * 3];
self.sponge.squeeze(&mut initial_bytes);
let tail = Self::sample(&initial_bytes, output);
if !tail.is_empty() {
self.tail_case(tail);
}
}
#[must_use]
fn sample<'a>(
samples: &'_ [u8; sha3::SHAKE_128_R_BYTES * 3],
output: &'a mut [i16; 256],
) -> &'a mut [i16] {
let used = low::mlkem_rej_uniform_vartime(output, samples) as usize;
output.split_at_mut(used).1
}
fn tail_case(self, output: &mut [i16]) {
let tail_iterator = Shake128TwelveBitIterator::new(self.sponge).filter(|f| *f < Q);
for (out, coeff) in output.iter_mut().zip(tail_iterator) {
*out = coeff;
}
}
}
struct Shake128TwelveBitIterator {
sponge: sha3::Shake128SqueezingSponge,
samples: [i16; Self::SAMPLE_COUNT],
used: usize,
}
impl Shake128TwelveBitIterator {
fn new(sponge: sha3::Shake128SqueezingSponge) -> Self {
Self {
sponge,
samples: [0; Self::SAMPLE_COUNT],
used: Self::SAMPLE_COUNT,
}
}
const SAMPLE_COUNT: usize = sha3::SHAKE_128_R_BYTES / 3 * 2;
}
impl Iterator for Shake128TwelveBitIterator {
type Item = i16;
#[cold]
fn next(&mut self) -> Option<Self::Item> {
if self.used == self.samples.len() {
let mut bytes = [0u8; sha3::SHAKE_128_R_BYTES];
self.sponge.squeeze(&mut bytes);
let (chunks, _) = bytes.as_chunks::<3>();
for (buf, d) in chunks
.iter()
.zip(self.samples.as_chunks_mut::<2>().0.iter_mut())
{
d[0] = (u16::from_le_bytes([buf[0], buf[1]]) & 0xfff) as i16;
d[1] = (u16::from_le_bytes([buf[1], buf[2]]) >> 4) as i16;
}
self.used = 0;
}
let item = self.samples[self.used];
self.used += 1;
Some(item)
}
}