use crate::helpers::full_reduce32;
use crate::types::{Zq, R};
use crate::{D, Q};
pub(crate) fn power2round<const K: usize>(r: &[R; K]) -> ([R; K], [R; K]) {
debug_assert!(
r.iter().flat_map(|row| row.0).all(|element| (0..Q).contains(&element)),
"power2round input"
);
let r_1: [R; K] = core::array::from_fn(|k| {
R(core::array::from_fn(|n| (r[k].0[n] + (1 << (D - 1)) - 1) >> D))
});
let r_0: [R; K] =
core::array::from_fn(|k| R(core::array::from_fn(|n| r[k].0[n] - (r_1[k].0[n] << D))));
debug_assert!(
{
let mut result = true;
for k in 0..K {
for n in 0..256 {
result &= r[k].0[n] == ((r_1[k].0[n] << D) + r_0[k].0[n]);
}
}
result
},
"Alg 35: fails"
);
(r_1, r_0)
}
pub(crate) fn decompose(gamma2: i32, r: Zq) -> (Zq, Zq) {
let rp = full_reduce32(r);
let mut xr1;
if gamma2 & (1 << 17) == 0 {
xr1 = (rp + 127) >> 7;
xr1 = (xr1 * 11275 + (1 << 23)) >> 24;
xr1 ^= ((43 - xr1) >> 31) & xr1;
} else {
xr1 = (rp + 127) >> 7;
xr1 = (xr1 * 1025 + (1 << 21)) >> 22;
xr1 &= 15;
}
let xr0 = rp - xr1 * 2 * gamma2;
let xr0 = xr0 - ((((Q - 1) / 2 - xr0) >> 31) & Q);
debug_assert_eq!(r.rem_euclid(Q), (xr1 * 2 * gamma2 + xr0).rem_euclid(Q), "Alg 36: fails");
(xr1, xr0)
}
pub(crate) fn high_bits(gamma2: i32, r: Zq) -> Zq {
let (r1, _r0) = decompose(gamma2, r);
r1
}
pub(crate) fn low_bits(gamma2: i32, r: Zq) -> Zq {
let (_r1, r0) = decompose(gamma2, r);
r0
}
pub(crate) fn make_hint(gamma2: i32, z: Zq, r: Zq) -> bool {
let r1 = high_bits(gamma2, r);
let v1 = high_bits(gamma2, r + z);
r1 != v1
}
pub(crate) fn use_hint(gamma2: i32, h: Zq, r: Zq) -> Zq {
let (r1, r0) = decompose(gamma2, r);
if h == 0 {
return r1;
}
if gamma2 & (1 << 17) == 0 {
if r0 > 0 {
if r1 == 43 {
return 0;
}
return r1 + 1;
} if r1 == 0 {
return 43;
}
r1 - 1
} else {
if r0 > 0 {
return (r1 + 1) & 15;
} (r1 - 1) & 15
}
}
#[cfg(test)]
mod tests {
use super::*;
const GAMMA2S: [i32; 2] = [(Q - 1) / 88, (Q - 1) / 32];
fn mod_pm(m: i32, alpha: i32) -> i32 {
let r = m.rem_euclid(alpha);
if r > alpha / 2 {
r - alpha
} else {
r
}
}
fn spec_decompose(gamma2: i32, r: i32) -> (i32, i32) {
let rp = r.rem_euclid(Q);
let r0 = mod_pm(rp, 2 * gamma2);
if rp - r0 == Q - 1 {
(0, r0 - 1)
} else {
((rp - r0) / (2 * gamma2), r0)
}
}
fn spec_use_hint(gamma2: i32, h: i32, r: i32) -> i32 {
let m = (Q - 1) / (2 * gamma2);
let (r1, r0) = spec_decompose(gamma2, r);
if h == 1 && r0 > 0 {
return (r1 + 1).rem_euclid(m);
}
if h == 1 && r0 <= 0 {
return (r1 - 1).rem_euclid(m);
}
r1
}
#[test]
fn decompose_and_use_hint_exhaustive() {
for gamma2 in GAMMA2S {
for r in 0..Q {
let (r1, r0) = spec_decompose(gamma2, r);
assert_eq!(decompose(gamma2, r), (r1, r0), "Decompose({r}), gamma2 {gamma2}");
assert_eq!(
decompose(gamma2, r - Q),
(r1, r0),
"Decompose({r} - q), gamma2 {gamma2}"
);
assert_eq!(high_bits(gamma2, r), r1, "HighBits({r}), gamma2 {gamma2}");
assert_eq!(low_bits(gamma2, r), r0, "LowBits({r}), gamma2 {gamma2}");
for h in 0..=1 {
let expected = spec_use_hint(gamma2, h, r);
assert_eq!(
use_hint(gamma2, h, r),
expected,
"UseHint({h}, {r}), gamma2 {gamma2}"
);
}
}
}
assert_eq!(decompose(GAMMA2S[0], 8_285_184), (43, 95_232));
assert_eq!(decompose(GAMMA2S[0], 8_285_185), (0, -95_232));
assert_eq!(use_hint(GAMMA2S[0], 1, 0), 43);
assert_eq!(use_hint(GAMMA2S[1], 1, 0), 15);
}
#[test]
fn power2round_exhaustive() {
for start in (0..Q).step_by(256) {
let r = [R(core::array::from_fn(|n| (start + i32::try_from(n).unwrap()).min(Q - 1)))];
let (r_1, r_0) = power2round(&r);
for ((&x, &x1), &x0) in r[0].0.iter().zip(&r_1[0].0).zip(&r_0[0].0) {
let e0 = mod_pm(x, 1 << D);
assert_eq!((x1, x0), ((x - e0) / (1 << D), e0), "Power2Round({x})");
}
}
}
#[test]
fn make_hint_bin_edges() {
for gamma2 in GAMMA2S {
for k in 0..(Q - 1) / (2 * gamma2) {
let edge = k * 2 * gamma2 + gamma2; for r in edge - 2..=edge + 2 {
for z in -2..=2 {
let expected =
spec_decompose(gamma2, r).0 != spec_decompose(gamma2, r + z).0;
assert_eq!(make_hint(gamma2, z, r), expected, "MakeHint({z}, {r})");
assert_eq!(make_hint(gamma2, Q + z, r), expected, "MakeHint(q + {z}, {r})");
}
}
}
}
}
}