#![cfg(feature = "hardened")]
use crate::constants::FIELD_MODULUS;
use crate::hash_functions::shake256::Xof;
use crate::polynomial::PolynomialRingElement;
use crate::simd::traits::{
COEFFICIENTS_IN_SIMD_UNIT,
Operations,
SIMD_UNITS_IN_RING_ELEMENT,
};
const SPLIT_DOMAIN: &[u8] = b"lib-q-ml-dsa/hardened-sk-split-v1";
#[inline(always)]
fn next_mod_field_coeff(xof: &mut impl Xof, buf: &mut [u8; 136], off: &mut usize) -> i32 {
if *off + 4 > buf.len() {
xof.squeeze(buf);
*off = 0;
}
let chunk: [u8; 4] = buf[*off..*off + 4].try_into().expect("length checked");
*off += 4;
let v = u32::from_le_bytes(chunk);
(v % FIELD_MODULUS as u32) as i32
}
#[inline(always)]
fn reduce_coeff(fe: i32) -> i32 {
let quotient = (fe + (1 << 22)) >> 23;
fe - quotient * FIELD_MODULUS
}
fn split_polynomial_additive<S: Operations>(
original: &mut PolynomialRingElement<S>,
share_b: &mut PolynomialRingElement<S>,
xof: &mut impl Xof,
buf: &mut [u8; 136],
off: &mut usize,
) {
crate::ntt::reduce::<S>(original);
let mut orig_coeffs = [0i32; COEFFICIENTS_IN_SIMD_UNIT];
let mut b_coeffs = [0i32; COEFFICIENTS_IN_SIMD_UNIT];
for unit_idx in 0..SIMD_UNITS_IN_RING_ELEMENT {
S::to_coefficient_array(&original.simd_units[unit_idx], &mut orig_coeffs);
for j in 0..COEFFICIENTS_IN_SIMD_UNIT {
let r = next_mod_field_coeff(xof, buf, off);
b_coeffs[j] = reduce_coeff(r);
orig_coeffs[j] = reduce_coeff(orig_coeffs[j] - r);
}
S::from_coefficient_array(&orig_coeffs, &mut original.simd_units[unit_idx]);
S::from_coefficient_array(&b_coeffs, &mut share_b.simd_units[unit_idx]);
}
}
pub(crate) fn split_signing_key_ntt_three<S, X>(
s1: &mut [PolynomialRingElement<S>],
s1_b: &mut [PolynomialRingElement<S>],
s2: &mut [PolynomialRingElement<S>],
s2_b: &mut [PolynomialRingElement<S>],
t0: &mut [PolynomialRingElement<S>],
t0_b: &mut [PolynomialRingElement<S>],
seed_for_signing: &[u8],
signing_randomness: &[u8],
) where
S: Operations,
X: Xof,
{
debug_assert_eq!(s1.len(), s1_b.len());
debug_assert_eq!(s2.len(), s2_b.len());
debug_assert_eq!(t0.len(), t0_b.len());
let mut xof = X::init();
xof.absorb(seed_for_signing);
xof.absorb(signing_randomness);
xof.absorb_final(SPLIT_DOMAIN);
let mut buf = [0u8; 136];
let mut off = buf.len();
for i in 0..s1.len() {
split_polynomial_additive(&mut s1[i], &mut s1_b[i], &mut xof, &mut buf, &mut off);
}
for i in 0..s2.len() {
split_polynomial_additive(&mut s2[i], &mut s2_b[i], &mut xof, &mut buf, &mut off);
}
for i in 0..t0.len() {
split_polynomial_additive(&mut t0[i], &mut t0_b[i], &mut xof, &mut buf, &mut off);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::COEFFICIENTS_IN_RING_ELEMENT;
use crate::hash_functions::portable::Shake256Xof;
use crate::simd::portable::PortableSIMDUnit;
fn mod_q_i64(x: i64) -> i32 {
let m = i64::from(FIELD_MODULUS);
let r = x.rem_euclid(m);
r as i32
}
#[test]
fn next_mod_field_coeff_chi_squared_binned() {
const BINS: usize = 256;
const SAMPLES: usize = 400_000;
let mut xof = Shake256Xof::init();
xof.absorb_final(b"next_mod_field_coeff_uniformity_v1");
let mut buf = [0u8; 136];
let mut off = buf.len();
let mut counts = [0usize; BINS];
for _ in 0..SAMPLES {
let v = next_mod_field_coeff(&mut xof, &mut buf, &mut off);
assert!((0..FIELD_MODULUS).contains(&v));
let bin = ((v as u64 * BINS as u64) / FIELD_MODULUS as u64) as usize;
assert!(bin < BINS);
counts[bin] += 1;
}
let expected = SAMPLES as f64 / BINS as f64;
let mut chi_sq = 0.0_f64;
for &c in &counts {
let diff = c as f64 - expected;
chi_sq += diff * diff / expected;
}
assert!(
chi_sq < 350.0,
"chi-squared {chi_sq} suggests gross non-uniformity (bins={BINS}, n={SAMPLES})"
);
}
#[test]
fn split_polynomial_additive_recovers_mod_q() {
let mut xof = Shake256Xof::init();
xof.absorb_final(b"split_poly_test_v1");
let mut buf = [0u8; 136];
let mut off = buf.len();
let mut a = PolynomialRingElement::<PortableSIMDUnit>::zero();
let mut b_share = PolynomialRingElement::<PortableSIMDUnit>::zero();
for u in 0..SIMD_UNITS_IN_RING_ELEMENT {
let mut coeffs = [0i32; COEFFICIENTS_IN_SIMD_UNIT];
for c in &mut coeffs {
*c = (u as i32 * 1000 + 17) % FIELD_MODULUS;
}
PortableSIMDUnit::from_coefficient_array(&coeffs, &mut a.simd_units[u]);
}
crate::ntt::reduce::<PortableSIMDUnit>(&mut a);
let mut saved = [0i32; COEFFICIENTS_IN_RING_ELEMENT];
for u in 0..SIMD_UNITS_IN_RING_ELEMENT {
let mut coeffs = [0i32; COEFFICIENTS_IN_SIMD_UNIT];
PortableSIMDUnit::to_coefficient_array(&a.simd_units[u], &mut coeffs);
for j in 0..COEFFICIENTS_IN_SIMD_UNIT {
saved[u * COEFFICIENTS_IN_SIMD_UNIT + j] = coeffs[j];
}
}
split_polynomial_additive(&mut a, &mut b_share, &mut xof, &mut buf, &mut off);
for u in 0..SIMD_UNITS_IN_RING_ELEMENT {
let mut ca = [0i32; COEFFICIENTS_IN_SIMD_UNIT];
let mut cb = [0i32; COEFFICIENTS_IN_SIMD_UNIT];
PortableSIMDUnit::to_coefficient_array(&a.simd_units[u], &mut ca);
PortableSIMDUnit::to_coefficient_array(&b_share.simd_units[u], &mut cb);
for j in 0..COEFFICIENTS_IN_SIMD_UNIT {
let idx = u * COEFFICIENTS_IN_SIMD_UNIT + j;
let sum = mod_q_i64(i64::from(ca[j]) + i64::from(cb[j]));
let want = mod_q_i64(i64::from(saved[idx]));
assert_eq!(sum, want, "coeff index {idx}");
}
}
}
}