use crate::{math::igamc, result::TestResult};
pub fn linear_complexity(bits: &[u8], m: usize) -> TestResult {
if !(500..=5000).contains(&m) {
return TestResult::insufficient("nist::linear_complexity", "M must be in [500, 5000]");
}
let n = bits.len();
let num_blocks = n / m;
if num_blocks < 200 {
return TestResult::insufficient(
"nist::linear_complexity",
"n too small — need ≥ 200 blocks",
);
}
let pow_neg1_m = if m.is_multiple_of(2) {
1.0_f64
} else {
-1.0_f64
};
let mu = m as f64 / 2.0 + (9.0 + pow_neg1_m) / 36.0
- (m as f64 / 3.0 + 2.0 / 9.0) / 2f64.powi(m as i32);
let pi = [
0.010417, 0.031250, 0.125000, 0.500000, 0.250000, 0.062500, 0.020833,
];
let mut nu = [0usize; 7];
let sign = if m.is_multiple_of(2) { 1.0 } else { -1.0 };
for block in bits.chunks_exact(m).take(num_blocks) {
let l = berlekamp_massey(block) as f64;
let t = sign * (l - mu) + 2.0 / 9.0;
let idx = if t <= -2.5 {
0
} else if t <= -1.5 {
1
} else if t <= -0.5 {
2
} else if t <= 0.5 {
3
} else if t <= 1.5 {
4
} else if t <= 2.5 {
5
} else {
6
};
nu[idx] += 1;
}
let chi_sq: f64 = nu
.iter()
.zip(pi.iter())
.map(|(&count, &p)| {
let exp = num_blocks as f64 * p;
(count as f64 - exp).powi(2) / exp
})
.sum();
let p_value = igamc(3.0, chi_sq / 2.0);
TestResult::with_note(
"nist::linear_complexity",
p_value,
format!("n={n}, M={m}, N={num_blocks}, χ²={chi_sq:.4}"),
)
}
pub fn berlekamp_massey(s: &[u8]) -> usize {
let big_n = s.len();
let mut c = vec![0u8; big_n + 1];
let mut b = vec![0u8; big_n + 1];
c[0] = 1;
b[0] = 1;
let mut l = 0usize;
let mut m: i64 = -1;
for n in 0..big_n {
let mut d = s[n];
for i in 1..=l {
d ^= c[i] & s[n - i];
}
if d == 0 {
continue;
}
let t = c.clone();
let shift = (n as i64 - m) as usize;
for i in shift..=big_n {
c[i] ^= b[i - shift];
}
if 2 * l <= n {
l = n + 1 - l;
b = t;
m = n as i64;
}
}
l
}