const PREALLOC_CAP: usize = 4096;
const PERIOD_CAP: usize = 512;
pub const MAX_POLY_DEGREE: usize = 4;
pub const MAX_RECUR_ORDER: usize = 4;
const LFSR_MAX_BYTES: usize = 512;
const FCSR_MAX_BYTES: usize = 256;
const RLE_MAX_TOTAL: usize = 1 << 28;
pub const MAX_GEN_NODES: u32 = 256;
pub const MAX_GEN_DEPTH: u32 = 32;
const DECODE_MAX_DEPTH: u32 = 32;
const DEFAULT_MAX_ELEMENTS: usize = 1 << 28;
pub const T_INTS: u8 = 19; pub const T_INTS_AFFINE: u8 = 32; pub const T_INTS_DELTA: u8 = 39; pub const T_INTS_DOD: u8 = 40; pub const T_INTS_FOR: u8 = 41; pub const T_INTS_RLE: u8 = 42; pub const T_INTS_DICT: u8 = 43; pub const T_INTS_POLY: u8 = 50; pub const T_GEN: u8 = 51; pub const T_BYTES: u8 = 53; pub const T_INTS_GEOMETRIC: u8 = 61; pub const T_INTS_PERIODIC: u8 = 62; pub const T_INTS_SPARSE: u8 = 67; pub const T_INTS_LRECUR: u8 = 82; pub const T_INTS_LFSR: u8 = 83; pub const T_INTS_FCSR: u8 = 84;
#[inline]
pub fn write_uvarint(mut x: u64, out: &mut Vec<u8>) {
while x >= 0x80 {
out.push((x as u8) | 0x80);
x >>= 7;
}
out.push(x as u8);
}
#[inline]
pub fn read_uvarint(buf: &[u8], pos: &mut usize) -> Option<u64> {
let mut result = 0u64;
let mut shift = 0u32;
loop {
let b = *buf.get(*pos)?;
*pos += 1;
if shift >= 64 {
return None; }
result |= u64::from(b & 0x7f) << shift;
if b & 0x80 == 0 {
return Some(result);
}
shift += 7;
}
}
#[inline]
pub fn zigzag(x: i64) -> u64 {
((x << 1) ^ (x >> 63)) as u64
}
#[inline]
pub fn unzigzag(x: u64) -> i64 {
((x >> 1) as i64) ^ -((x & 1) as i64)
}
pub fn uvarint_byte_len(x: u64) -> usize {
(((64 - x.leading_zeros()).max(1) + 6) / 7) as usize
}
pub fn leb128_encode<I: Iterator<Item = i64> + Clone>(out: &mut Vec<u8>, vals: I, n: usize) {
let signed = vals.clone().any(|x| x < 0);
write_uvarint(((n as u64) << 1) | signed as u64, out);
out.reserve(n * 2);
if signed {
for x in vals {
write_uvarint(zigzag(x), out);
}
} else {
for x in vals {
write_uvarint(x as u64, out);
}
}
}
pub fn bitpack(vals: &[u64], width: u8) -> Vec<u8> {
if width == 0 {
return Vec::new();
}
let total_bits = vals.len().saturating_mul(width as usize);
let mut out = vec![0u8; total_bits.div_ceil(8)];
let mut bitpos = 0usize;
for &val in vals {
let mut bits = val;
let mut remaining = width as usize;
while remaining > 0 {
let byte = bitpos / 8;
let off = bitpos % 8;
let take = remaining.min(8 - off);
let mask = (1u64 << take) - 1;
out[byte] |= ((bits & mask) as u8) << off;
bits >>= take;
bitpos += take;
remaining -= take;
}
}
out
}
pub fn bitunpack(bytes: &[u8], count: usize, width: u8) -> Option<Vec<u64>> {
if width == 0 || width > 64 {
return None;
}
let total_bits = count.checked_mul(width as usize)?;
if bytes.len() < total_bits.div_ceil(8) {
return None;
}
let mut out = Vec::with_capacity(count.min(PREALLOC_CAP));
let mut bitpos = 0usize;
for _ in 0..count {
let mut val = 0u64;
let mut got = 0usize;
while got < width as usize {
let byte = bitpos / 8;
let off = bitpos % 8;
let take = (width as usize - got).min(8 - off);
let mask = (1u64 << take) - 1;
val |= (((bytes[byte] >> off) as u64) & mask) << got;
got += take;
bitpos += take;
}
out.push(val);
}
Some(out)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GenCmp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GenExpr {
Index,
Const(i64),
Add(Box<GenExpr>, Box<GenExpr>),
Sub(Box<GenExpr>, Box<GenExpr>),
Mul(Box<GenExpr>, Box<GenExpr>),
Div(Box<GenExpr>, Box<GenExpr>),
Mod(Box<GenExpr>, Box<GenExpr>),
Select { op: GenCmp, lhs: Box<GenExpr>, rhs: Box<GenExpr>, then: Box<GenExpr>, els: Box<GenExpr> },
}
pub fn gen_eval(e: &GenExpr, i: i64) -> i64 {
match e {
GenExpr::Index => i,
GenExpr::Const(c) => *c,
GenExpr::Add(a, b) => gen_eval(a, i).wrapping_add(gen_eval(b, i)),
GenExpr::Sub(a, b) => gen_eval(a, i).wrapping_sub(gen_eval(b, i)),
GenExpr::Mul(a, b) => gen_eval(a, i).wrapping_mul(gen_eval(b, i)),
GenExpr::Div(a, b) => {
let d = gen_eval(b, i);
if d == 0 { 0 } else { gen_eval(a, i).wrapping_div(d) }
}
GenExpr::Mod(a, b) => {
let d = gen_eval(b, i);
if d == 0 { 0 } else { gen_eval(a, i).wrapping_rem(d) }
}
GenExpr::Select { op, lhs, rhs, then, els } => {
let (l, r) = (gen_eval(lhs, i), gen_eval(rhs, i));
let c = match op {
GenCmp::Eq => l == r,
GenCmp::Ne => l != r,
GenCmp::Lt => l < r,
GenCmp::Le => l <= r,
GenCmp::Gt => l > r,
GenCmp::Ge => l >= r,
};
if c { gen_eval(then, i) } else { gen_eval(els, i) }
}
}
}
pub fn serialize_gen(e: &GenExpr, out: &mut Vec<u8>) {
match e {
GenExpr::Index => out.push(0),
GenExpr::Const(c) => {
out.push(1);
write_uvarint(zigzag(*c), out);
}
GenExpr::Add(a, b) => { out.push(2); serialize_gen(a, out); serialize_gen(b, out); }
GenExpr::Sub(a, b) => { out.push(3); serialize_gen(a, out); serialize_gen(b, out); }
GenExpr::Mul(a, b) => { out.push(4); serialize_gen(a, out); serialize_gen(b, out); }
GenExpr::Div(a, b) => { out.push(5); serialize_gen(a, out); serialize_gen(b, out); }
GenExpr::Mod(a, b) => { out.push(6); serialize_gen(a, out); serialize_gen(b, out); }
GenExpr::Select { op, lhs, rhs, then, els } => {
out.push(7);
out.push(*op as u8);
serialize_gen(lhs, out);
serialize_gen(rhs, out);
serialize_gen(then, out);
serialize_gen(els, out);
}
}
}
pub fn deserialize_gen(buf: &[u8], pos: &mut usize, budget: &mut u32, depth: u32) -> Option<GenExpr> {
if depth > MAX_GEN_DEPTH || *budget == 0 {
return None;
}
*budget -= 1;
let tag = *buf.get(*pos)?;
*pos += 1;
Some(match tag {
0 => GenExpr::Index,
1 => GenExpr::Const(unzigzag(read_uvarint(buf, pos)?)),
2..=6 => {
let a = Box::new(deserialize_gen(buf, pos, budget, depth + 1)?);
let b = Box::new(deserialize_gen(buf, pos, budget, depth + 1)?);
match tag {
2 => GenExpr::Add(a, b),
3 => GenExpr::Sub(a, b),
4 => GenExpr::Mul(a, b),
5 => GenExpr::Div(a, b),
_ => GenExpr::Mod(a, b),
}
}
7 => {
let op = match *buf.get(*pos)? {
0 => GenCmp::Eq,
1 => GenCmp::Ne,
2 => GenCmp::Lt,
3 => GenCmp::Le,
4 => GenCmp::Gt,
5 => GenCmp::Ge,
_ => return None,
};
*pos += 1;
let lhs = Box::new(deserialize_gen(buf, pos, budget, depth + 1)?);
let rhs = Box::new(deserialize_gen(buf, pos, budget, depth + 1)?);
let then = Box::new(deserialize_gen(buf, pos, budget, depth + 1)?);
let els = Box::new(deserialize_gen(buf, pos, budget, depth + 1)?);
GenExpr::Select { op, lhs, rhs, then, els }
}
_ => return None,
})
}
pub fn detect_affine(v: &[i64]) -> Option<(i64, i64)> {
if v.len() < 2 {
return None;
}
let base = v[0];
let stride = v[1].wrapping_sub(v[0]);
for (i, &x) in v.iter().enumerate() {
if base.wrapping_add((i as i64).wrapping_mul(stride)) != x {
return None;
}
}
Some((base, stride))
}
pub fn detect_geometric(v: &[i64]) -> Option<(i64, i64)> {
if v.len() < 3 {
return None;
}
let base = v[0];
if base == 0 || v[1].checked_rem(base)? != 0 {
return None;
}
let ratio = v[1].checked_div(base)?;
if ratio == 0 || ratio == 1 {
return None;
}
let mut cur = base;
for &x in v {
if cur != x {
return None;
}
cur = cur.wrapping_mul(ratio);
}
Some((base, ratio))
}
pub fn detect_period(v: &[i64]) -> Option<usize> {
let n = v.len();
if n < 4 {
return None;
}
let cap = (n / 2).min(PERIOD_CAP);
'p: for p in 2..=cap {
for i in p..n {
if v[i] != v[i - p] {
continue 'p;
}
}
return Some(p);
}
None
}
pub fn detect_sparse(v: &[i64]) -> Option<(i64, Vec<(usize, i64)>)> {
if v.len() < 8 {
return None;
}
let mut cand = v[0];
let mut count: i64 = 0;
for &x in v {
if count == 0 {
cand = x;
count = 1;
} else if x == cand {
count += 1;
} else {
count -= 1;
}
}
let occ = v.iter().filter(|&&x| x == cand).count();
if v.len() - occ > v.len() / 4 {
return None; }
let exceptions: Vec<(usize, i64)> =
v.iter().enumerate().filter(|(_, &x)| x != cand).map(|(i, &x)| (i, x)).collect();
Some((cand, exceptions))
}
pub fn detect_poly_generator(v: &[i64]) -> Option<(u8, Vec<i64>)> {
if v.len() < 3 {
return None;
}
let mut levels: Vec<Vec<i64>> = Vec::with_capacity(MAX_POLY_DEGREE + 1);
levels.push(v.to_vec());
for d in 0..MAX_POLY_DEGREE {
let prev = &levels[d];
if prev.len() < 2 {
break;
}
let mut next = Vec::with_capacity(prev.len() - 1);
for w in prev.windows(2) {
next.push(w[1].checked_sub(w[0])?); }
if next.len() >= 2 && next.iter().all(|&x| x == next[0]) {
let degree = (d + 1) as u8;
let mut seeds: Vec<i64> = levels.iter().map(|lvl| lvl[0]).collect();
seeds.push(next[0]);
if reconstruct_poly(&seeds, v.len()) == v {
return Some((degree, seeds));
}
return None;
}
levels.push(next);
}
None
}
pub fn reconstruct_poly(seeds: &[i64], n: usize) -> Vec<i64> {
let mut diffs = seeds.to_vec();
let mut out = Vec::with_capacity(n.min(PREALLOC_CAP));
for _ in 0..n {
out.push(diffs[0]);
for j in 0..diffs.len().saturating_sub(1) {
diffs[j] = diffs[j].wrapping_add(diffs[j + 1]);
}
}
out
}
pub fn detect_linear_recurrence(v: &[i64]) -> Option<(Vec<i64>, Vec<i64>)> {
let n = v.len();
for k in 1..=MAX_RECUR_ORDER.min(n / 2) {
if let Some(coeffs) = solve_recurrence(v, k) {
if reconstruct_recurrence(&coeffs, &v[..k], n) == v {
return Some((coeffs, v[..k].to_vec()));
}
}
}
None
}
fn solve_recurrence(v: &[i64], k: usize) -> Option<Vec<i64>> {
let mut m = vec![vec![0i128; k]; k];
let mut b = vec![0i128; k];
for r in 0..k {
for j in 0..k {
m[r][j] = v[k + r - 1 - j] as i128; }
b[r] = v[k + r] as i128;
}
let det_m = det_i128(&m)?;
if det_m == 0 {
return None;
}
let mut coeffs = Vec::with_capacity(k);
for j in 0..k {
let mut mj = m.clone();
for (r, br) in b.iter().enumerate() {
mj[r][j] = *br;
}
let det_j = det_i128(&mj)?;
if det_j % det_m != 0 {
return None; }
let c = det_j / det_m;
if c < i64::MIN as i128 || c > i64::MAX as i128 {
return None;
}
coeffs.push(c as i64);
}
Some(coeffs)
}
fn det_i128(m: &[Vec<i128>]) -> Option<i128> {
let k = m.len();
match k {
1 => Some(m[0][0]),
2 => m[0][0].checked_mul(m[1][1])?.checked_sub(m[0][1].checked_mul(m[1][0])?),
_ => {
let mut acc: i128 = 0;
for j in 0..k {
let minor: Vec<Vec<i128>> =
(1..k).map(|r| (0..k).filter(|&c| c != j).map(|c| m[r][c]).collect()).collect();
let cof = m[0][j].checked_mul(det_i128(&minor)?)?;
acc = if j % 2 == 0 { acc.checked_add(cof)? } else { acc.checked_sub(cof)? };
}
Some(acc)
}
}
}
fn reconstruct_recurrence(coeffs: &[i64], seeds: &[i64], n: usize) -> Vec<i64> {
let k = coeffs.len();
if seeds.len() < k {
return seeds[..seeds.len().min(n)].to_vec();
}
let mut out = Vec::with_capacity(n.min(PREALLOC_CAP));
out.extend_from_slice(&seeds[..k.min(n)]);
for i in k..n {
let mut acc = 0i64;
for j in 0..k {
acc = acc.wrapping_add(coeffs[j].wrapping_mul(out[i - 1 - j]));
}
out.push(acc);
}
out.truncate(n);
out
}
pub trait FieldElem: Copy + PartialEq {
fn zero() -> Self;
fn one() -> Self;
fn add(self, o: Self) -> Self;
fn sub(self, o: Self) -> Self;
fn mul(self, o: Self) -> Self;
fn inv(self) -> Self;
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Gf2(pub bool);
impl FieldElem for Gf2 {
fn zero() -> Self { Gf2(false) }
fn one() -> Self { Gf2(true) }
fn add(self, o: Self) -> Self { Gf2(self.0 ^ o.0) }
fn sub(self, o: Self) -> Self { Gf2(self.0 ^ o.0) }
fn mul(self, o: Self) -> Self { Gf2(self.0 && o.0) }
fn inv(self) -> Self { self } }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Gf256(pub u8);
fn gf256_xtime(a: u8) -> u8 {
(a << 1) ^ (if a & 0x80 != 0 { 0x1B } else { 0 })
}
fn gf256_tables() -> &'static (Vec<u8>, Vec<u8>) {
static T: std::sync::OnceLock<(Vec<u8>, Vec<u8>)> = std::sync::OnceLock::new();
T.get_or_init(|| {
let mut exp = vec![0u8; 512];
let mut log = vec![0u8; 256];
let mut x = 1u8;
for i in 0..255usize {
exp[i] = x;
log[x as usize] = i as u8;
x ^= gf256_xtime(x); }
for i in 255..512 {
exp[i] = exp[i - 255];
}
(log, exp)
})
}
impl FieldElem for Gf256 {
fn zero() -> Self { Gf256(0) }
fn one() -> Self { Gf256(1) }
fn add(self, o: Self) -> Self { Gf256(self.0 ^ o.0) }
fn sub(self, o: Self) -> Self { Gf256(self.0 ^ o.0) }
fn mul(self, o: Self) -> Self {
if self.0 == 0 || o.0 == 0 {
return Gf256(0);
}
let (log, exp) = gf256_tables();
Gf256(exp[log[self.0 as usize] as usize + log[o.0 as usize] as usize])
}
fn inv(self) -> Self {
let (log, exp) = gf256_tables();
Gf256(exp[255 - log[self.0 as usize] as usize])
}
}
pub fn berlekamp_massey_field<F: FieldElem>(s: &[F]) -> (usize, Vec<F>) {
let n = s.len();
let mut c = vec![F::zero(); n + 1];
let mut b = vec![F::zero(); n + 1];
c[0] = F::one();
b[0] = F::one();
let mut l = 0usize;
let mut m = 1usize;
let mut b_disc = F::one(); for nn in 0..n {
let mut d = s[nn];
for i in 1..=l {
d = d.add(c[i].mul(s[nn - i]));
}
if d == F::zero() {
m += 1;
} else {
let coef = d.mul(b_disc.inv());
let t = c.clone();
for i in 0..=n {
if i + m <= n && b[i] != F::zero() {
c[i + m] = c[i + m].sub(coef.mul(b[i]));
}
}
if 2 * l <= nn {
l = nn + 1 - l;
b = t;
b_disc = d;
m = 1;
} else {
m += 1;
}
}
}
let taps = if l == 0 { Vec::new() } else { c[1..=l].to_vec() };
(l, taps)
}
pub fn lfsr_generate_field<F: FieldElem>(taps: &[F], seed: &[F], total: usize) -> Vec<F> {
let l = taps.len();
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&seed[..l.min(seed.len()).min(total)]);
for i in l..total {
let mut acc = F::zero();
for j in 0..l {
acc = acc.add(taps[j].mul(out[i - 1 - j]));
}
out.push(F::zero().sub(acc));
}
out.truncate(total);
out
}
pub fn berlekamp_massey_gf2(s: &[bool]) -> (usize, Vec<bool>) {
let elems: Vec<Gf2> = s.iter().map(|&b| Gf2(b)).collect();
let (l, taps) = berlekamp_massey_field(&elems);
(l, taps.into_iter().map(|g| g.0).collect())
}
pub fn lfsr_generate(taps: &[bool], seed: &[bool], total: usize) -> Vec<bool> {
let t: Vec<Gf2> = taps.iter().map(|&b| Gf2(b)).collect();
let sd: Vec<Gf2> = seed.iter().map(|&b| Gf2(b)).collect();
lfsr_generate_field(&t, &sd, total).into_iter().map(|g| g.0).collect()
}
fn detect_lfsr_bytes(v: &[i64]) -> Option<(usize, Vec<bool>, Vec<bool>)> {
let bits = bytes_to_bits(v);
let (l, taps) = berlekamp_massey_gf2(&bits);
if l == 0 || l >= bits.len() {
return None;
}
let seed = bits[..l].to_vec();
if lfsr_generate(&taps, &seed, bits.len()) != bits {
return None; }
Some((l, taps, seed))
}
pub fn bytes_to_bits(v: &[i64]) -> Vec<bool> {
let mut bits = Vec::with_capacity(v.len().saturating_mul(8));
for &x in v {
let byte = x as u8;
for j in 0..8 {
bits.push((byte >> j) & 1 == 1);
}
}
bits
}
pub fn bits_to_bytes(bits: &[bool]) -> Vec<i64> {
bits.chunks(8)
.map(|chunk| {
let mut byte = 0u8;
for (j, &bit) in chunk.iter().enumerate() {
if bit {
byte |= 1 << j;
}
}
byte as i64
})
.collect()
}
fn bigint_gcd_local(a: &crate::numeric::BigInt, b: &crate::numeric::BigInt) -> crate::numeric::BigInt {
let (mut a, mut b) = (a.abs(), b.abs());
while !b.is_zero() {
let r = a.div_rem(&b).map(|(_, r)| r).unwrap_or_else(crate::numeric::BigInt::zero);
a = b;
b = r;
}
a
}
fn bigint_bitlen(x: &crate::numeric::BigInt) -> usize {
let (_, bytes) = x.to_le_bytes();
for (i, &b) in bytes.iter().enumerate().rev() {
if b != 0 {
return i * 8 + (8 - b.leading_zeros() as usize);
}
}
0
}
pub fn two_adic_reconstruct(bits: &[bool]) -> Option<(crate::numeric::BigInt, crate::numeric::BigInt)> {
use crate::numeric::BigInt;
let n = bits.len();
if n == 0 {
return None;
}
let two = BigInt::from_i64(2);
let mut alpha = BigInt::zero();
let mut pow2 = BigInt::from_i64(1);
for &bit in bits {
if bit {
alpha = alpha.add(&pow2);
}
pow2 = pow2.mul(&two);
}
let n_big = pow2; let (mut r0, mut t0) = (n_big.clone(), BigInt::zero());
let (mut r1, mut t1) = (alpha, BigInt::from_i64(1));
while !r1.is_zero() && r1.mul(&r1) > n_big {
let (quot, rem) = r0.div_rem(&r1)?;
let t2 = t0.sub(".mul(&t1));
r0 = r1;
t0 = t1;
r1 = rem;
t1 = t2;
}
let (mut p, mut q) = (r1, t1);
if q.is_zero() {
return None;
}
if q.is_negative() {
p = BigInt::zero().sub(&p);
q = BigInt::zero().sub(&q);
}
let g = bigint_gcd_local(&p, &q);
if !g.is_zero() && g != BigInt::from_i64(1) {
p = p.div_rem(&g).map(|(quot, _)| quot)?;
q = q.div_rem(&g).map(|(quot, _)| quot)?;
}
if !q.is_odd() {
return None; }
Some((p, q))
}
pub fn fcsr_generate(p: &crate::numeric::BigInt, q: &crate::numeric::BigInt, n: usize) -> Vec<bool> {
use crate::numeric::BigInt;
let two = BigInt::from_i64(2);
let mut r = p.clone();
let mut bits = Vec::with_capacity(n);
for _ in 0..n {
let bit = r.is_odd();
bits.push(bit);
if bit {
r = r.sub(q);
}
r = r.div_rem(&two).map(|(quot, _)| quot).unwrap_or_else(BigInt::zero);
}
bits
}
pub fn two_adic_complexity(bits: &[bool]) -> usize {
match two_adic_reconstruct(bits) {
Some((p, q)) => bigint_bitlen(&p).max(bigint_bitlen(&q)),
None => bits.len(),
}
}
pub fn maximal_order_complexity(bits: &[bool]) -> usize {
let n = bits.len();
if n == 0 {
return 0;
}
let consistent = |l: usize| -> bool {
if l >= n {
return true;
}
let mut succ: std::collections::HashMap<&[bool], bool> = std::collections::HashMap::new();
for i in 0..(n - l) {
match succ.insert(&bits[i..i + l], bits[i + l]) {
Some(prev) if prev != bits[i + l] => return false,
_ => {}
}
}
true
};
let (mut lo, mut hi) = (0usize, n);
while lo < hi {
let mid = (lo + hi) / 2;
if consistent(mid) {
hi = mid;
} else {
lo = mid + 1;
}
}
lo
}
fn monomials(l: usize, d: usize) -> Vec<Vec<usize>> {
fn combos(start: usize, l: usize, k: usize, cur: &mut Vec<usize>, out: &mut Vec<Vec<usize>>) {
if cur.len() == k {
out.push(cur.clone());
return;
}
for v in start..l {
cur.push(v);
combos(v + 1, l, k, cur, out);
cur.pop();
}
}
let mut out = vec![Vec::new()];
for k in 1..=d.min(l) {
combos(0, l, k, &mut Vec::new(), &mut out);
}
out
}
fn eval_monomial(mono: &[usize], window: &[bool]) -> bool {
mono.iter().all(|&v| window[v])
}
fn solve_gf2_system(rows: &[(Vec<u64>, bool)], ncols: usize) -> Option<Vec<bool>> {
let words = ncols.div_ceil(64).max(1);
let mut mat: Vec<(Vec<u64>, bool)> = rows.to_vec();
let mut pivot_row_of_col = vec![usize::MAX; ncols];
let mut r = 0usize;
for c in 0..ncols {
let (w, bit) = (c / 64, 1u64 << (c % 64));
let Some(pr) = (r..mat.len()).find(|&i| mat[i].0[w] & bit != 0) else {
continue;
};
mat.swap(r, pr);
for i in 0..mat.len() {
if i != r && mat[i].0[w] & bit != 0 {
for k in 0..words {
mat[i].0[k] ^= mat[r].0[k];
}
mat[i].1 ^= mat[r].1;
}
}
pivot_row_of_col[c] = r;
r += 1;
}
if mat.iter().any(|(coeff, rhs)| *rhs && coeff.iter().all(|&w| w == 0)) {
return None;
}
let mut c = vec![false; ncols];
for col in 0..ncols {
if pivot_row_of_col[col] != usize::MAX {
c[col] = mat[pivot_row_of_col[col]].1;
}
}
Some(c)
}
pub fn detect_algebraic_recurrence(bits: &[bool], l: usize, d: usize) -> Option<Vec<bool>> {
if l == 0 || bits.len() <= l {
return None;
}
let monos = monomials(l, d);
let m = monos.len();
let words = m.div_ceil(64).max(1);
let mut rows: Vec<(Vec<u64>, bool)> = Vec::with_capacity(bits.len() - l);
for i in l..bits.len() {
let window: Vec<bool> = (0..l).map(|v| bits[i - 1 - v]).collect();
let mut coeff = vec![0u64; words];
for (mi, mono) in monos.iter().enumerate() {
if eval_monomial(mono, &window) {
coeff[mi / 64] |= 1u64 << (mi % 64);
}
}
rows.push((coeff, bits[i]));
}
let coeffs = solve_gf2_system(&rows, m)?;
if algebraic_generate(l, d, &coeffs, &bits[..l], bits.len()) != bits {
return None; }
Some(coeffs)
}
pub fn algebraic_generate(l: usize, d: usize, coeffs: &[bool], seed: &[bool], total: usize) -> Vec<bool> {
let monos = monomials(l, d);
let mut out: Vec<bool> = seed.iter().take(l).copied().collect();
for i in l..total {
let window: Vec<bool> = (0..l).map(|v| out[i - 1 - v]).collect();
let mut bit = false;
for (mi, mono) in monos.iter().enumerate() {
if coeffs.get(mi).copied().unwrap_or(false) && eval_monomial(mono, &window) {
bit ^= true;
}
}
out.push(bit);
}
out.truncate(total);
out
}
pub fn algebraic_complexity(bits: &[bool], max_degree: usize) -> Option<(usize, usize)> {
for l in 1..=(bits.len() / 2) {
if let Some(coeffs) = detect_algebraic_recurrence(bits, l, max_degree) {
let used = coeffs.iter().filter(|&&c| c).count();
return Some((l, used.max(1)));
}
}
None
}
#[derive(Clone, Debug, PartialEq)]
pub struct CorrelationAttack {
pub register_len: usize,
pub init_state: Vec<bool>,
pub agreement: f64,
pub bias: f64,
pub samples: usize,
}
pub fn correlation_attack(keystream: &[bool], taps: &[bool]) -> Option<CorrelationAttack> {
let l = taps.len();
let n = keystream.len();
if l == 0 || l > 20 || n <= l {
return None;
}
let mut best: Option<CorrelationAttack> = None;
for code in 1u64..(1u64 << l) {
let seed: Vec<bool> = (0..l).map(|k| (code >> k) & 1 == 1).collect();
let stream = lfsr_generate(taps, &seed, n);
let agree = stream.iter().zip(keystream).filter(|(a, b)| a == b).count();
let bias = (agree as f64 / n as f64 - 0.5).abs();
if best.as_ref().is_none_or(|b| bias > b.bias) {
best = Some(CorrelationAttack {
register_len: l,
init_state: seed,
agreement: agree as f64 / n as f64,
bias,
samples: n,
});
}
}
best
}
pub fn spurious_bias_floor(register_len: usize, samples: usize) -> f64 {
if samples == 0 {
return 1.0;
}
((register_len as f64) * std::f64::consts::LN_2 / (2.0 * samples as f64)).sqrt()
}
pub fn fast_walsh_hadamard(a: &mut [i64]) {
let n = a.len();
debug_assert!(n.is_power_of_two(), "Walsh–Hadamard length must be a power of two");
let mut len = 1;
while len < n {
let mut i = 0;
while i < n {
for j in i..i + len {
let (x, y) = (a[j], a[j + len]);
a[j] = x + y;
a[j + len] = x - y;
}
i += 2 * len;
}
len <<= 1;
}
}
pub fn walsh_spectrum(truth: &[bool]) -> Option<Vec<i64>> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let mut a: Vec<i64> = truth.iter().map(|&b| if b { -1 } else { 1 }).collect();
fast_walsh_hadamard(&mut a);
Some(a)
}
pub fn best_linear_approximation(truth: &[bool], skip_zero: bool) -> Option<(usize, f64)> {
let spec = walsh_spectrum(truth)?;
let denom = 2.0 * truth.len() as f64; spec.iter()
.enumerate()
.skip(usize::from(skip_zero))
.max_by_key(|(_, &c)| c.unsigned_abs())
.map(|(w, &c)| (w, c.unsigned_abs() as f64 / denom))
}
pub fn nonlinearity(truth: &[bool]) -> Option<u64> {
let spec = walsh_spectrum(truth)?;
let max_abs = spec.iter().map(|&c| c.unsigned_abs()).max()?;
Some((truth.len() as u64) / 2 - max_abs / 2)
}
pub fn correlation_immunity_order(truth: &[bool]) -> Option<usize> {
let spec = walsh_spectrum(truth)?;
let n = truth.len().trailing_zeros() as usize;
for m in 1..=n {
let vanishes = spec
.iter()
.enumerate()
.filter(|(w, _)| w.count_ones() as usize == m)
.all(|(_, &c)| c == 0);
if !vanishes {
return Some(m - 1);
}
}
Some(n)
}
pub fn anf(truth: &[bool]) -> Option<Vec<bool>> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros();
let mut a = truth.to_vec();
for i in 0..n {
let step = 1usize << i;
let mut j = 0;
while j < a.len() {
for k in j..j + step {
let lo = a[k];
a[k + step] ^= lo;
}
j += step << 1;
}
}
Some(a)
}
pub fn algebraic_degree(truth: &[bool]) -> Option<usize> {
let a = anf(truth)?;
Some(a.iter().enumerate().filter(|(_, &c)| c).map(|(m, _)| (m as u64).count_ones() as usize).max().unwrap_or(0))
}
pub fn autocorrelation(truth: &[bool]) -> Option<Vec<i64>> {
let mut w = walsh_spectrum(truth)?;
for c in w.iter_mut() {
*c *= *c;
}
fast_walsh_hadamard(&mut w);
let scale = truth.len() as i64;
for c in w.iter_mut() {
*c /= scale;
}
Some(w)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AnnihilatorWitness {
pub n_vars: usize,
pub degree: usize,
pub coeffs: Vec<bool>,
pub annihilates_complement: bool,
}
fn gf2_kernel_basis(rows: &[Vec<u64>], ncols: usize) -> Vec<Vec<bool>> {
let words = ncols.div_ceil(64).max(1);
let mut mat: Vec<Vec<u64>> = rows.to_vec();
let mut pivot_row_of_col = vec![usize::MAX; ncols];
let mut pivot_cols: Vec<usize> = Vec::new();
let mut r = 0usize;
for c in 0..ncols {
let (w, bit) = (c / 64, 1u64 << (c % 64));
let Some(pr) = (r..mat.len()).find(|&i| mat[i][w] & bit != 0) else {
continue;
};
mat.swap(r, pr);
for i in 0..mat.len() {
if i != r && mat[i][w] & bit != 0 {
for k in 0..words {
mat[i][k] ^= mat[r][k];
}
}
}
pivot_row_of_col[c] = r;
pivot_cols.push(c);
r += 1;
}
(0..ncols)
.filter(|&c| pivot_row_of_col[c] == usize::MAX)
.map(|free| {
let mut c = vec![false; ncols];
c[free] = true;
for &pc in &pivot_cols {
let row = &mat[pivot_row_of_col[pc]];
if (row[free / 64] >> (free % 64)) & 1 == 1 {
c[pc] = true;
}
}
c
})
.collect()
}
fn annihilator_basis(n: usize, d: usize, zero_set: &[usize]) -> Vec<Vec<bool>> {
let monos = monomials(n, d);
let m = monos.len();
let words = m.div_ceil(64).max(1);
let rows: Vec<Vec<u64>> = zero_set
.iter()
.map(|&x| {
let window: Vec<bool> = (0..n).map(|i| (x >> i) & 1 == 1).collect();
let mut coeff = vec![0u64; words];
for (mi, mono) in monos.iter().enumerate() {
if eval_monomial(mono, &window) {
coeff[mi / 64] |= 1u64 << (mi % 64);
}
}
coeff
})
.collect();
gf2_kernel_basis(&rows, m)
}
fn annihilator(n: usize, d: usize, zero_set: &[usize]) -> Option<Vec<bool>> {
annihilator_basis(n, d, zero_set).into_iter().next()
}
pub fn algebraic_immunity(truth: &[bool]) -> Option<(usize, AnnihilatorWitness)> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let support: Vec<usize> = (0..truth.len()).filter(|&x| truth[x]).collect();
let zeros: Vec<usize> = (0..truth.len()).filter(|&x| !truth[x]).collect();
for d in 0..=n {
if let Some(g) = annihilator(n, d, &support) {
return Some((d, AnnihilatorWitness { n_vars: n, degree: d, coeffs: g, annihilates_complement: false }));
}
if let Some(g) = annihilator(n, d, &zeros) {
return Some((d, AnnihilatorWitness { n_vars: n, degree: d, coeffs: g, annihilates_complement: true }));
}
}
None
}
fn eval_anf(coeffs: &[bool], n_vars: usize, degree: usize, x: usize) -> bool {
let window: Vec<bool> = (0..n_vars).map(|i| (x >> i) & 1 == 1).collect();
monomials(n_vars, degree)
.iter()
.enumerate()
.filter(|(mi, _)| coeffs.get(*mi).copied().unwrap_or(false))
.fold(false, |acc, (_, mono)| acc ^ eval_monomial(mono, &window))
}
pub fn verify_annihilator(truth: &[bool], w: &AnnihilatorWitness) -> bool {
if w.coeffs.iter().all(|&c| !c) || monomials(w.n_vars, w.degree).len() != w.coeffs.len() {
return false;
}
(0..truth.len()).all(|x| {
let must_vanish = if w.annihilates_complement { !truth[x] } else { truth[x] };
!must_vanish || !eval_anf(&w.coeffs, w.n_vars, w.degree, x)
})
}
pub fn algebraic_filter_attack(keystream: &[bool], taps: &[bool], filter_truth: &[bool]) -> Option<Vec<bool>> {
let l = taps.len();
let n = keystream.len();
if l == 0 || l > 64 || !filter_truth.len().is_power_of_two() {
return None;
}
let m = filter_truth.len().trailing_zeros() as usize;
if m == 0 || n < l + m {
return None;
}
let (ai, _) = algebraic_immunity(filter_truth)?;
if ai == 0 {
return None; }
let g_monos = monomials(m, ai);
let support: Vec<usize> = (0..filter_truth.len()).filter(|&x| filter_truth[x]).collect();
let zeros: Vec<usize> = (0..filter_truth.len()).filter(|&x| !filter_truth[x]).collect();
let ann_when_one = annihilator_basis(m, ai, &support);
let ann_when_zero = annihilator_basis(m, ai, &zeros);
let need = n + m;
let mut r: Vec<u64> = Vec::with_capacity(need);
for k in 0..need {
if k < l {
r.push(1u64 << k);
} else {
let mut acc = 0u64;
for (j, &t) in taps.iter().enumerate() {
if t {
acc ^= r[k - 1 - j];
}
}
r.push(acc);
}
}
let s_monos = monomials(l, ai);
let ncols = s_monos.len();
let col_of: std::collections::HashMap<u64, usize> = s_monos
.iter()
.enumerate()
.map(|(i, mono)| (mono.iter().fold(0u64, |b, &v| b | (1u64 << v)), i))
.collect();
let words = ncols.div_ceil(64).max(1);
let expand = |g: &[bool], t: usize| -> Option<(Vec<u64>, bool)> {
let mut acc: std::collections::HashSet<u64> = std::collections::HashSet::new();
for (gi, mono) in g_monos.iter().enumerate() {
if !g[gi] {
continue;
}
let mut poly: std::collections::HashSet<u64> = std::collections::HashSet::from([0u64]);
for &i in mono {
let form = r[t + i];
let mut next: std::collections::HashSet<u64> = std::collections::HashSet::new();
for &pm in &poly {
let mut bits = form;
while bits != 0 {
let v = bits.trailing_zeros();
bits &= bits - 1;
let term = pm | (1u64 << v);
if !next.insert(term) {
next.remove(&term); }
}
}
poly = next;
}
for pm in poly {
if !acc.insert(pm) {
acc.remove(&pm);
}
}
}
let mut coeff = vec![0u64; words];
let mut rhs = false;
for mask in acc {
if mask == 0 {
rhs = true;
} else {
coeff[*col_of.get(&mask)? / 64] |= 1u64 << (col_of[&mask] % 64);
}
}
Some((coeff, rhs))
};
let mut rows: Vec<(Vec<u64>, bool)> = Vec::new();
let mut pin = vec![0u64; words];
pin[0] = 1;
rows.push((pin, true));
for t in 0..n.saturating_sub(m - 1) {
let anns = if keystream[t] { &ann_when_one } else { &ann_when_zero };
for g in anns {
rows.push(expand(g, t)?);
}
}
let sol = solve_gf2_system(&rows, ncols)?;
let state: Vec<bool> = (0..l)
.map(|i| col_of.get(&(1u64 << i)).map(|&ci| sol[ci]).unwrap_or(false))
.collect();
let seq = lfsr_generate(taps, &state, need);
let regen: Vec<bool> = (0..n)
.map(|t| {
let idx = (0..m).fold(0usize, |a, i| a | (usize::from(seq[t + i]) << i));
filter_truth[idx]
})
.collect();
(regen == keystream).then_some(state)
}
pub fn fast_correlation_attack(keystream: &[bool], taps: &[bool], max_iters: usize) -> Option<Vec<bool>> {
let (n, l) = (keystream.len(), taps.len());
if l == 0 || n <= 2 * l {
return None;
}
let mut base: Vec<usize> = vec![0];
for (j, &t) in taps.iter().enumerate() {
if t {
base.push(1 + j);
}
}
let mut checks: Vec<Vec<usize>> = Vec::new();
let mut scale = 1usize;
for _ in 0..4 {
let span = base.iter().map(|&o| o * scale).max().unwrap_or(0);
if span >= n {
break;
}
for i in span..n {
checks.push(base.iter().map(|&o| i - o * scale).collect());
}
scale *= 2;
}
if checks.is_empty() {
return None;
}
let mut per_bit = vec![0usize; n];
for c in &checks {
for &p in c {
per_bit[p] += 1;
}
}
let mut y = keystream.to_vec();
for _ in 0..max_iters {
let mut votes = vec![0usize; n];
let mut all_satisfied = true;
for c in &checks {
if c.iter().fold(false, |a, &p| a ^ y[p]) {
all_satisfied = false;
for &p in c {
votes[p] += 1;
}
}
}
if all_satisfied {
break;
}
let mut flipped = false;
for i in 0..n {
if per_bit[i] > 0 && votes[i] * 2 > per_bit[i] {
y[i] ^= true;
flipped = true;
}
}
if !flipped {
break; }
}
let state = y[..l].to_vec();
let regen = lfsr_generate(taps, &state, n);
if regen != y {
return None;
}
let agree = regen.iter().zip(keystream).filter(|(a, b)| a == b).count() as f64 / n as f64;
(agree > 0.6).then_some(state)
}
pub fn shrinking_generator(
a_taps: &[bool],
a_seed: &[bool],
s_taps: &[bool],
s_seed: &[bool],
out_len: usize,
) -> Vec<bool> {
let clocks = out_len.saturating_mul(4) + 64; let a = lfsr_generate(a_taps, a_seed, clocks);
let s = lfsr_generate(s_taps, s_seed, clocks);
let mut out = Vec::with_capacity(out_len);
for i in 0..clocks {
if a[i] {
out.push(s[i]);
if out.len() == out_len {
break;
}
}
}
out
}
pub fn attack_shrinking_generator(
output: &[bool],
a_taps: &[bool],
s_taps: &[bool],
) -> Option<(Vec<bool>, Vec<bool>)> {
let (la, ls) = (a_taps.len(), s_taps.len());
let m = output.len();
if la == 0 || la > 22 || ls == 0 || ls > 64 || m < ls {
return None;
}
let clocks = m.saturating_mul(4) + 64;
let mut r: Vec<u64> = Vec::with_capacity(clocks);
for k in 0..clocks {
if k < ls {
r.push(1u64 << k);
} else {
let mut acc = 0u64;
for (j, &t) in s_taps.iter().enumerate() {
if t {
acc ^= r[k - 1 - j];
}
}
r.push(acc);
}
}
let words = ls.div_ceil(64).max(1);
for code in 1u64..(1u64 << la) {
let a_seed: Vec<bool> = (0..la).map(|k| (code >> k) & 1 == 1).collect();
let a = lfsr_generate(a_taps, &a_seed, clocks);
let mut positions = Vec::with_capacity(m);
for (i, &bit) in a.iter().enumerate() {
if bit {
positions.push(i);
if positions.len() == m {
break;
}
}
}
if positions.len() < m {
continue; }
let rows: Vec<(Vec<u64>, bool)> = positions
.iter()
.enumerate()
.map(|(k, &pos)| {
let mut coeff = vec![0u64; words];
coeff[0] = r[pos];
(coeff, output[k])
})
.collect();
if let Some(sol) = solve_gf2_system(&rows, ls) {
let s_seed: Vec<bool> = sol[..ls].to_vec();
if shrinking_generator(a_taps, &a_seed, s_taps, &s_seed, m) == output {
return Some((a_seed, s_seed));
}
}
}
None
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PolySolveResult {
Solved(Vec<bool>),
Refuted,
Undetermined,
}
fn monomial_masks(n: usize, deg: usize) -> Vec<u64> {
monomials(n, deg).iter().map(|mono| mono.iter().fold(0u64, |b, &v| b | (1u64 << v))).collect()
}
fn eval_mask(mask: u64, x: &[bool]) -> bool {
let mut m = mask;
let mut v = true;
while m != 0 {
v &= x[m.trailing_zeros() as usize];
m &= m - 1;
}
v
}
fn verify_poly_system(eqs: &[Vec<u64>], x: &[bool]) -> bool {
eqs.iter().all(|eq| !eq.iter().fold(false, |a, &m| a ^ eval_mask(m, x)))
}
pub fn solve_polynomial_system_gf2(eqs: &[Vec<u64>], n_vars: usize, max_degree: usize) -> PolySolveResult {
for d in 1..=max_degree {
let cols = monomial_masks(n_vars, d);
let col_of: std::collections::HashMap<u64, usize> =
cols.iter().enumerate().map(|(i, &m)| (m, i)).collect();
let ncols = cols.len();
let words = ncols.div_ceil(64).max(1);
let mut rows: Vec<(Vec<u64>, bool)> = Vec::new();
let mut pin = vec![0u64; words];
let c0 = col_of[&0];
pin[c0 / 64] |= 1u64 << (c0 % 64);
rows.push((pin, true));
for eq in eqs {
let deg_eq = eq.iter().map(|m| m.count_ones() as usize).max().unwrap_or(0);
if deg_eq > d {
continue; }
for mu in monomial_masks(n_vars, d - deg_eq) {
let mut acc: std::collections::HashSet<u64> = std::collections::HashSet::new();
for &m in eq {
let prod = m | mu;
if !acc.insert(prod) {
acc.remove(&prod); }
}
if acc.is_empty() {
continue;
}
let mut coeff = vec![0u64; words];
let mut rhs = false;
for mask in acc {
if mask == 0 {
rhs = true;
} else if let Some(&ci) = col_of.get(&mask) {
coeff[ci / 64] |= 1u64 << (ci % 64);
}
}
rows.push((coeff, rhs));
}
}
match solve_gf2_system(&rows, ncols) {
None => return PolySolveResult::Refuted,
Some(sol) => {
let x: Vec<bool> = (0..n_vars)
.map(|i| col_of.get(&(1u64 << i)).map(|&ci| sol[ci]).unwrap_or(false))
.collect();
if verify_poly_system(eqs, &x) {
return PolySolveResult::Solved(x);
}
}
}
}
PolySolveResult::Undetermined
}
fn write_bigint(x: &crate::numeric::BigInt, out: &mut Vec<u8>) {
let (neg, mut bytes) = x.to_le_bytes();
while bytes.last() == Some(&0) {
bytes.pop();
}
out.push(neg as u8);
write_uvarint(bytes.len() as u64, out);
out.extend_from_slice(&bytes);
}
fn read_bigint(buf: &[u8], pos: &mut usize) -> Option<crate::numeric::BigInt> {
let neg = *buf.get(*pos)? != 0;
*pos += 1;
let len = read_uvarint(buf, pos)? as usize;
let bytes = buf.get(*pos..pos.checked_add(len)?)?;
*pos += len;
Some(crate::numeric::BigInt::from_le_bytes(neg, bytes))
}
fn detect_fcsr_bytes(v: &[i64]) -> Option<(crate::numeric::BigInt, crate::numeric::BigInt)> {
let bits = bytes_to_bits(v);
let (p, q) = two_adic_reconstruct(&bits)?;
if fcsr_generate(&p, &q, bits.len()) != bits {
return None;
}
Some((p, q))
}
pub fn detect_modular_affine(v: &[i64]) -> Option<GenExpr> {
const MAX_PERIOD: usize = 16;
if v.len() < 4 {
return None;
}
for p in 2..=MAX_PERIOD.min(v.len() / 2) {
let a = v[0];
let b = v[1].wrapping_sub(v[0]);
if b != 0 && (0..v.len()).all(|i| v[i] == a.wrapping_add(b.wrapping_mul((i % p) as i64))) {
return Some(GenExpr::Add(
Box::new(GenExpr::Const(a)),
Box::new(GenExpr::Mul(
Box::new(GenExpr::Const(b)),
Box::new(GenExpr::Mod(Box::new(GenExpr::Index), Box::new(GenExpr::Const(p as i64)))),
)),
));
}
}
None
}
pub fn delta_encode(out: &mut Vec<u8>, v: &[i64]) {
out.push(T_INTS_DELTA);
write_uvarint(v.len() as u64, out);
if let Some(&first) = v.first() {
write_uvarint(zigzag(first), out);
let mut prev = first;
for &x in &v[1..] {
write_uvarint(zigzag(x.wrapping_sub(prev)), out);
prev = x;
}
}
}
pub fn dod_encode(out: &mut Vec<u8>, v: &[i64]) {
out.push(T_INTS_DOD);
write_uvarint(v.len() as u64, out);
if v.is_empty() {
return;
}
write_uvarint(zigzag(v[0]), out);
if v.len() == 1 {
return;
}
let mut prev_delta = v[1].wrapping_sub(v[0]);
write_uvarint(zigzag(prev_delta), out);
let mut prev = v[1];
for &x in &v[2..] {
let d = x.wrapping_sub(prev);
write_uvarint(zigzag(d.wrapping_sub(prev_delta)), out);
prev_delta = d;
prev = x;
}
}
pub fn for_encode(out: &mut Vec<u8>, v: &[i64]) {
out.push(T_INTS_FOR);
write_uvarint(v.len() as u64, out);
let min = v.iter().copied().min().unwrap_or(0);
write_uvarint(zigzag(min), out);
if v.is_empty() {
out.push(0);
return;
}
let max = v.iter().copied().max().unwrap();
let range = (max as u64).wrapping_sub(min as u64);
let width = if range == 0 { 0 } else { (64 - range.leading_zeros()) as u8 };
out.push(width);
if width > 0 {
let residuals: Vec<u64> = v.iter().map(|&x| (x as u64).wrapping_sub(min as u64)).collect();
out.extend_from_slice(&bitpack(&residuals, width));
}
}
pub fn rle_encode(out: &mut Vec<u8>, v: &[i64]) {
let mut runs: Vec<(i64, u64)> = Vec::new();
for &x in v {
match runs.last_mut() {
Some(last) if last.0 == x => last.1 += 1,
_ => runs.push((x, 1)),
}
}
out.push(T_INTS_RLE);
write_uvarint(runs.len() as u64, out);
for (val, len) in runs {
write_uvarint(zigzag(val), out);
write_uvarint(len, out);
}
}
pub fn dict_encode(v: &[i64]) -> Vec<u8> {
let mut dict: Vec<i64> = Vec::new();
let mut index_of: std::collections::HashMap<i64, u64> = std::collections::HashMap::new();
let mut indices: Vec<u64> = Vec::with_capacity(v.len());
for &x in v {
let idx = *index_of.entry(x).or_insert_with(|| {
dict.push(x);
(dict.len() - 1) as u64
});
indices.push(idx);
}
let mut out = vec![T_INTS_DICT];
write_uvarint(dict.len() as u64, &mut out);
for &d in &dict {
write_uvarint(zigzag(d), &mut out);
}
write_uvarint(v.len() as u64, &mut out);
let iw = if dict.len() <= 1 { 0 } else { (64 - ((dict.len() - 1) as u64).leading_zeros()) as u8 };
out.push(iw);
if iw > 0 {
out.extend_from_slice(&bitpack(&indices, iw));
}
out
}
pub fn consider(best: &mut Vec<u8>, cand: Vec<u8>) {
if cand.len() < best.len() {
*best = cand;
}
}
pub fn emit_best_int_column(v: &[i64], out: &mut Vec<u8>) {
let mut best = Vec::new();
best.push(T_INTS);
leb128_encode(&mut best, v.iter().copied(), v.len());
if let Some((base, stride)) = detect_affine(v) {
let mut c = vec![T_INTS_AFFINE];
write_uvarint(zigzag(base), &mut c);
write_uvarint(zigzag(stride), &mut c);
write_uvarint(v.len() as u64, &mut c);
consider(&mut best, c);
}
if let Some((base, ratio)) = detect_geometric(v) {
let mut c = vec![T_INTS_GEOMETRIC];
write_uvarint(zigzag(base), &mut c);
write_uvarint(zigzag(ratio), &mut c);
write_uvarint(v.len() as u64, &mut c);
consider(&mut best, c);
}
if let Some(p) = detect_period(v) {
let mut c = vec![T_INTS_PERIODIC];
write_uvarint(v.len() as u64, &mut c);
emit_best_int_column(&v[..p], &mut c);
consider(&mut best, c);
}
if let Some((dom, exc)) = detect_sparse(v) {
let mut c = vec![T_INTS_SPARSE];
write_uvarint(zigzag(dom), &mut c);
write_uvarint(v.len() as u64, &mut c);
write_uvarint(exc.len() as u64, &mut c);
let mut prev = 0usize;
for (i, x) in &exc {
write_uvarint((i - prev) as u64, &mut c);
prev = *i;
write_uvarint(zigzag(*x), &mut c);
}
consider(&mut best, c);
}
if let Some((degree, seeds)) = detect_poly_generator(v) {
let mut c = vec![T_INTS_POLY, degree];
write_uvarint(v.len() as u64, &mut c);
for &s in &seeds {
write_uvarint(zigzag(s), &mut c);
}
consider(&mut best, c);
}
if let Some((coeffs, seeds)) = detect_linear_recurrence(v) {
let mut c = vec![T_INTS_LRECUR, coeffs.len() as u8];
write_uvarint(v.len() as u64, &mut c);
for &x in &coeffs {
write_uvarint(zigzag(x), &mut c);
}
for &s in &seeds {
write_uvarint(zigzag(s), &mut c);
}
consider(&mut best, c);
}
if let Some(expr) = detect_modular_affine(v) {
let mut c = vec![T_GEN];
serialize_gen(&expr, &mut c);
write_uvarint(v.len() as u64, &mut c);
consider(&mut best, c);
}
let mut delta = Vec::new();
delta_encode(&mut delta, v);
consider(&mut best, delta);
let mut dod = Vec::new();
dod_encode(&mut dod, v);
consider(&mut best, dod);
if !v.is_empty() && v.iter().all(|&x| (0..256).contains(&x)) {
let mut b = vec![T_BYTES];
write_uvarint(v.len() as u64, &mut b);
b.extend(v.iter().map(|&x| x as u8));
consider(&mut best, b);
}
let mut for_c = Vec::new();
for_encode(&mut for_c, v);
consider(&mut best, for_c);
let mut rle = Vec::new();
rle_encode(&mut rle, v);
consider(&mut best, rle);
consider(&mut best, dict_encode(v));
if !v.is_empty()
&& v.len() <= LFSR_MAX_BYTES
&& best.len() >= v.len()
&& v.iter().all(|&x| (0..256).contains(&x))
{
if let Some((l, taps, seed)) = detect_lfsr_bytes(v) {
let mut c = vec![T_INTS_LFSR];
write_uvarint(v.len() as u64, &mut c);
write_uvarint(l as u64, &mut c);
let tap_vals: Vec<u64> = taps.iter().map(|&b| b as u64).collect();
c.extend_from_slice(&bitpack(&tap_vals, 1));
let seed_vals: Vec<u64> = seed.iter().map(|&b| b as u64).collect();
c.extend_from_slice(&bitpack(&seed_vals, 1));
consider(&mut best, c);
}
}
if !v.is_empty()
&& v.len() <= FCSR_MAX_BYTES
&& best.len() >= v.len()
&& v.iter().all(|&x| (0..256).contains(&x))
{
if let Some((p, q)) = detect_fcsr_bytes(v) {
let mut c = vec![T_INTS_FCSR];
write_uvarint(v.len() as u64, &mut c);
write_bigint(&p, &mut c);
write_bigint(&q, &mut c);
consider(&mut best, c);
}
}
out.extend_from_slice(&best);
}
pub fn describe_int_seq(v: &[i64]) -> Vec<u8> {
let mut out = Vec::new();
emit_best_int_column(v, &mut out);
out
}
#[inline]
fn bounded(n: u64, max_elements: usize) -> Option<usize> {
let n = n as usize;
(n <= max_elements).then_some(n)
}
pub fn decode_int_column_body(
tag: u8,
buf: &[u8],
pos: &mut usize,
max_elements: usize,
depth: u32,
) -> Option<Vec<i64>> {
Some(match tag {
T_INTS => {
let header = read_uvarint(buf, pos)?;
let signed = header & 1 == 1;
let n = bounded(header >> 1, max_elements)?;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
for _ in 0..n {
let u = read_uvarint(buf, pos)?;
v.push(if signed { unzigzag(u) } else { u as i64 });
}
v
}
T_INTS_AFFINE => {
let base = unzigzag(read_uvarint(buf, pos)?);
let stride = unzigzag(read_uvarint(buf, pos)?);
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
for i in 0..n {
v.push(base.wrapping_add((i as i64).wrapping_mul(stride)));
}
v
}
T_INTS_GEOMETRIC => {
let base = unzigzag(read_uvarint(buf, pos)?);
let ratio = unzigzag(read_uvarint(buf, pos)?);
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
let mut cur = base;
for _ in 0..n {
v.push(cur);
cur = cur.wrapping_mul(ratio);
}
v
}
T_INTS_PERIODIC => {
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
if depth >= DECODE_MAX_DEPTH {
return None;
}
let block_tag = *buf.get(*pos)?;
*pos += 1;
let block = decode_int_column_body(block_tag, buf, pos, max_elements, depth + 1)?;
let p = block.len();
if p == 0 {
return None; }
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
for i in 0..n {
v.push(block[i % p]);
}
v
}
T_INTS_SPARSE => {
let dom = unzigzag(read_uvarint(buf, pos)?);
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
let num_exc = bounded(read_uvarint(buf, pos)?, max_elements)?;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
v.resize(n, dom);
let mut idx = 0usize;
for _ in 0..num_exc {
idx = idx.checked_add(read_uvarint(buf, pos)? as usize)?;
let val = unzigzag(read_uvarint(buf, pos)?);
*v.get_mut(idx)? = val; }
v
}
T_INTS_POLY => {
let degree = *buf.get(*pos)? as usize;
*pos += 1;
if degree > MAX_POLY_DEGREE {
return None;
}
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
let mut seeds = Vec::with_capacity(degree + 1);
for _ in 0..=degree {
seeds.push(unzigzag(read_uvarint(buf, pos)?));
}
reconstruct_poly(&seeds, n)
}
T_INTS_LRECUR => {
let k = *buf.get(*pos)? as usize;
*pos += 1;
if k == 0 || k > MAX_RECUR_ORDER {
return None; }
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
let mut coeffs = Vec::with_capacity(k);
for _ in 0..k {
coeffs.push(unzigzag(read_uvarint(buf, pos)?));
}
let mut seeds = Vec::with_capacity(k);
for _ in 0..k {
seeds.push(unzigzag(read_uvarint(buf, pos)?));
}
reconstruct_recurrence(&coeffs, &seeds, n)
}
T_INTS_LFSR => {
let n = bounded(read_uvarint(buf, pos)?, max_elements)?; let l = read_uvarint(buf, pos)? as usize;
let total_bits = n.checked_mul(8)?;
if l > total_bits {
return None;
}
let nbytes = l.div_ceil(8);
let tap_bytes = buf.get(*pos..pos.checked_add(nbytes)?)?;
*pos += nbytes;
let taps: Vec<bool> = bitunpack(tap_bytes, l, 1)?.into_iter().map(|x| x == 1).collect();
let seed_bytes = buf.get(*pos..pos.checked_add(nbytes)?)?;
*pos += nbytes;
let seed: Vec<bool> = bitunpack(seed_bytes, l, 1)?.into_iter().map(|x| x == 1).collect();
bits_to_bytes(&lfsr_generate(&taps, &seed, total_bits))
}
T_INTS_FCSR => {
let n = bounded(read_uvarint(buf, pos)?, max_elements)?; let p = read_bigint(buf, pos)?;
let q = read_bigint(buf, pos)?;
if !q.is_odd() {
return None; }
let total_bits = n.checked_mul(8)?;
bits_to_bytes(&fcsr_generate(&p, &q, total_bits))
}
T_GEN => {
let mut budget = MAX_GEN_NODES;
let expr = deserialize_gen(buf, pos, &mut budget, 0)?;
let n = bounded(read_uvarint(buf, pos)?, max_elements)?;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
for i in 0..n {
v.push(gen_eval(&expr, i as i64));
}
v
}
T_BYTES => {
let n = read_uvarint(buf, pos)? as usize;
let raw = buf.get(*pos..pos.checked_add(n)?)?;
*pos += n;
raw.iter().map(|&b| b as i64).collect()
}
T_INTS_DELTA => {
let n = read_uvarint(buf, pos)? as usize;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
if n > 0 {
let mut cur = unzigzag(read_uvarint(buf, pos)?);
v.push(cur);
for _ in 1..n {
cur = cur.wrapping_add(unzigzag(read_uvarint(buf, pos)?));
v.push(cur);
}
}
v
}
T_INTS_DOD => {
let n = read_uvarint(buf, pos)? as usize;
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
if n > 0 {
let first = unzigzag(read_uvarint(buf, pos)?);
v.push(first);
if n > 1 {
let mut prev_delta = unzigzag(read_uvarint(buf, pos)?);
let mut prev = first.wrapping_add(prev_delta);
v.push(prev);
for _ in 2..n {
prev_delta = prev_delta.wrapping_add(unzigzag(read_uvarint(buf, pos)?));
prev = prev.wrapping_add(prev_delta);
v.push(prev);
}
}
}
v
}
T_INTS_FOR => {
let n = read_uvarint(buf, pos)? as usize;
let min = unzigzag(read_uvarint(buf, pos)?);
let width = *buf.get(*pos)?;
*pos += 1;
if width > 64 {
return None;
}
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
if width == 0 {
for _ in 0..n {
v.push(min);
}
} else {
let nbytes = n.checked_mul(width as usize)?.div_ceil(8);
let bytes = buf.get(*pos..pos.checked_add(nbytes)?)?;
*pos += nbytes;
for r in bitunpack(bytes, n, width)? {
v.push(r.wrapping_add(min as u64) as i64);
}
}
v
}
T_INTS_RLE => {
let runs = read_uvarint(buf, pos)? as usize;
let mut v: Vec<i64> = Vec::new();
for _ in 0..runs {
let val = unzigzag(read_uvarint(buf, pos)?);
let len = read_uvarint(buf, pos)? as usize;
if v.len().checked_add(len)? > RLE_MAX_TOTAL {
return None;
}
v.resize(v.len() + len, val);
}
v
}
T_INTS_DICT => {
let d = read_uvarint(buf, pos)? as usize;
let mut dict = Vec::with_capacity(d.min(PREALLOC_CAP));
for _ in 0..d {
dict.push(unzigzag(read_uvarint(buf, pos)?));
}
let n = read_uvarint(buf, pos)? as usize;
let iw = *buf.get(*pos)?;
*pos += 1;
if iw > 64 {
return None;
}
let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
if iw == 0 {
if n > 0 {
let val = *dict.first()?;
v.resize(n, val);
}
} else {
let nbytes = n.checked_mul(iw as usize)?.div_ceil(8);
let bytes = buf.get(*pos..pos.checked_add(nbytes)?)?;
*pos += nbytes;
for ix in bitunpack(bytes, n, iw)? {
v.push(*dict.get(ix as usize)?);
}
}
v
}
_ => return None,
})
}
pub fn decode_int_seq(bytes: &[u8]) -> Option<Vec<i64>> {
let mut pos = 0usize;
let tag = *bytes.get(pos)?;
pos += 1;
let v = decode_int_column_body(tag, bytes, &mut pos, DEFAULT_MAX_ELEMENTS, 0)?;
(pos == bytes.len()).then_some(v)
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrips(v: &[i64]) {
let enc = describe_int_seq(v);
let dec = decode_int_seq(&enc);
assert_eq!(dec.as_deref(), Some(v), "round-trip failed for {v:?} (enc {enc:?})");
}
#[test]
fn affine_round_trips_and_beats_varint() {
let v: Vec<i64> = (0..1000).map(|i| 10 + 7 * i).collect();
roundtrips(&v);
assert!(describe_int_seq(&v).len() < 20, "affine must ship as a generator, not data");
}
#[test]
fn geometric_round_trips() {
let v: Vec<i64> = (0..40).map(|i| 3i64.wrapping_mul(2i64.wrapping_pow(i))).collect();
roundtrips(&v);
}
#[test]
fn polynomial_round_trips() {
let v: Vec<i64> = (0..500).map(|i| i * i - 3 * i + 5).collect();
roundtrips(&v);
assert!(describe_int_seq(&v).len() < 30, "poly must ship as finite-difference seeds");
}
#[test]
fn linear_recurrence_ships_the_generator() {
let mut fib = vec![0i64, 1];
while fib.len() < 60 {
let n = fib.len();
fib.push(fib[n - 1].wrapping_add(fib[n - 2]));
}
roundtrips(&fib);
assert!(describe_int_seq(&fib).len() < 15, "Fibonacci ships as (order, coeffs, seeds), not data");
let mut lucas = vec![2i64, 1];
while lucas.len() < 60 {
let n = lucas.len();
lucas.push(lucas[n - 1].wrapping_add(lucas[n - 2]));
}
roundtrips(&lucas);
assert!(describe_int_seq(&lucas).len() < 15);
let mut pell = vec![0i64, 1];
while pell.len() < 50 {
let n = pell.len();
pell.push(2i64.wrapping_mul(pell[n - 1]).wrapping_add(pell[n - 2]));
}
roundtrips(&pell);
assert!(describe_int_seq(&pell).len() < 15);
let mut r3 = vec![1i64, 2, 3];
while r3.len() < 40 {
let n = r3.len();
r3.push(r3[n - 1].wrapping_add(r3[n - 2]).wrapping_sub(r3[n - 3]));
}
roundtrips(&r3);
assert!(describe_int_seq(&r3).len() < 18);
}
#[test]
fn berlekamp_massey_recovers_the_shortest_lfsr() {
let taps = vec![true, false, true];
let seed = vec![true, false, false];
let seq = lfsr_generate(&taps, &seed, 40);
let (l, recovered) = berlekamp_massey_gf2(&seq);
assert_eq!(l, 3, "a length-3 LFSR has linear complexity 3");
assert_eq!(lfsr_generate(&recovered, &seq[..l], seq.len()), seq);
let mut st = 0x1234_5678u64;
let rnd: Vec<bool> = (0..200)
.map(|_| {
st = st.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
z & 1 == 1
})
.collect();
let (lr, _) = berlekamp_massey_gf2(&rnd);
assert!((80..=120).contains(&lr), "high-quality random complexity ≈ n/2, got {lr}");
let bytes = vec![0x41i64, 0x00, 0xFF, 0x7E];
assert_eq!(bits_to_bytes(&bytes_to_bits(&bytes)), bytes);
}
#[test]
fn lfsr_keystream_bytes_compress_to_the_register() {
let taps = vec![false, false, true, false, false, false, true]; let seed = vec![true, false, true, true, false, false, true];
let bits = lfsr_generate(&taps, &seed, 200 * 8);
let bytes = bits_to_bytes(&bits);
roundtrips(&bytes);
let enc = describe_int_seq(&bytes);
assert_eq!(enc[0], T_INTS_LFSR, "the keystream ships as its LFSR register (tag), got {}", enc[0]);
assert!(enc.len() < 20, "200 bytes collapse to the 7-bit register, got {} bytes", enc.len());
}
#[test]
fn berlekamp_massey_over_gf256_recovers_a_word_lfsr() {
assert_eq!(Gf256(0x53).mul(Gf256(0xCA)), Gf256(0x53).mul(Gf256(0xCA)));
assert_eq!(Gf256(0x53).mul(Gf256(0x53).inv()), Gf256::one());
assert_eq!(Gf256(0xFF).mul(Gf256(0xFF).inv()), Gf256::one());
let taps = vec![Gf256(0x02), Gf256(0x8d), Gf256(0x1f)];
let seed = vec![Gf256(0x41), Gf256(0x9c), Gf256(0x07)];
let seq = lfsr_generate_field(&taps, &seed, 60);
let (l, recovered) = berlekamp_massey_field(&seq);
assert_eq!(l, 3, "order-3 word-LFSR has GF(256) linear complexity 3, got {l}");
assert_eq!(lfsr_generate_field(&recovered, &seq[..l], seq.len()), seq, "recovered register regenerates it");
}
#[test]
fn two_adic_complexity_detects_fcsr_keystreams() {
use crate::numeric::BigInt;
let bits = fcsr_generate(&BigInt::from_i64(3), &BigInt::from_i64(19), 120);
let (rp, rq) = two_adic_reconstruct(&bits).expect("a clean 2-adic rational");
assert_eq!(fcsr_generate(&rp, &rq, bits.len()), bits, "the recovered FCSR regenerates the keystream");
let tac = two_adic_complexity(&bits);
assert!(tac < 12, "a small FCSR has low 2-adic complexity, got {tac}");
assert!(berlekamp_massey_gf2(&bits).0 > tac, "the FCSR is simpler 2-adically than linearly");
let mut st = 0xABCD_1234u64;
let rnd: Vec<bool> = (0..200)
.map(|_| {
st = st.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
z & 1 == 1
})
.collect();
assert!(two_adic_complexity(&rnd) > 60, "random ≈ n/2 2-adic complexity, got {}", two_adic_complexity(&rnd));
}
#[test]
fn fcsr_keystream_bytes_compress_to_the_rational() {
use crate::numeric::BigInt;
let bits = fcsr_generate(&BigInt::from_i64(7), &BigInt::from_i64(1_000_003), 100 * 8);
let bytes = bits_to_bytes(&bits);
roundtrips(&bytes);
let enc = describe_int_seq(&bytes);
assert_eq!(enc[0], T_INTS_FCSR, "the FCSR keystream ships as its rational p/q (tag), got {}", enc[0]);
assert!(enc.len() < 20, "100 bytes collapse to a small rational, got {} bytes", enc.len());
}
#[test]
fn maximal_order_complexity_catches_nonlinear_feedback() {
let order = 6;
let period = 1usize << order;
let mut db: Vec<bool> = vec![false; order];
let mut seen: std::collections::HashSet<Vec<bool>> = std::collections::HashSet::new();
seen.insert(db[..order].to_vec());
while db.len() < period {
let mut w: Vec<bool> = db[db.len() - (order - 1)..].to_vec();
w.push(true);
if seen.contains(&w) {
w.pop();
w.push(false);
}
seen.insert(w.clone());
db.push(*w.last().unwrap());
}
let bits: Vec<bool> = [db.as_slice(), db.as_slice(), db.as_slice()].concat(); let moc = maximal_order_complexity(&bits);
let lin = berlekamp_massey_gf2(&bits).0;
assert!((5..=7).contains(&moc), "the order-6 De Bruijn register has MOC ≈ 6, got {moc}");
assert!(lin > moc, "nonlinear feedback fools linear complexity (BM {lin} > MOC {moc})");
assert!(two_adic_complexity(&bits) > moc, "nonlinear feedback fools 2-adic complexity too");
let mut st = 0x2468_ace0_1357_9bdfu64;
let rnd: Vec<bool> = (0..300)
.map(|_| {
st = st.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
z & 1 == 1
})
.collect();
assert!(maximal_order_complexity(&rnd) <= berlekamp_massey_gf2(&rnd).0, "MOC ≤ linear complexity");
}
#[test]
fn algebraic_recurrence_recovers_low_degree_nonlinear_feedback() {
let seed: Vec<bool> = (0..8).map(|k| (0x9E37u64 >> k) & 1 == 1).collect();
let mut bits = seed.clone();
while bits.len() < 600 {
let i = bits.len();
let s = |k: usize| bits[i - k];
bits.push(s(1) ^ s(5) ^ s(6) ^ s(8) ^ (s(1) & s(6)));
}
let coeffs = detect_algebraic_recurrence(&bits, 8, 2).expect("recovers the degree-2 feedback");
assert_eq!(
algebraic_generate(8, 2, &coeffs, &bits[..8], bits.len()),
bits,
"the recovered ANF regenerates the whole nonlinear sequence"
);
assert_eq!(coeffs.len(), 37, "degree-2 ANF over 8 vars has 37 monomials");
assert!(coeffs.len() < (1usize << 8), "the ANF is far sparser than the full truth table");
let lin = berlekamp_massey_gf2(&bits).0;
assert!(lin > 200, "the nonlinear term drives linear complexity to 246, got {lin}");
assert!(two_adic_complexity(&bits) > 8, "and 2-adic complexity is high too — carry tools miss it");
assert!(detect_algebraic_recurrence(&bits, 8, 1).is_none(), "no affine order-8 recurrence fits");
let (l, m) = algebraic_complexity(&bits, 2).expect("degree-2 algebraic complexity exists");
assert!(l <= 8, "the algebraic recovery finds an order-≤8 register, got {l}");
assert!(m <= 37, "and describes it in at most its 37 ANF coefficients, got {m}");
}
#[test]
fn algebraic_recovery_is_a_strict_generalization_of_berlekamp_massey() {
let taps = vec![true, false, false, true, false, true]; let seed = vec![true, false, true, true, false, false];
let bits = lfsr_generate(&taps, &seed, 200);
let coeffs = detect_algebraic_recurrence(&bits, 6, 1).expect("degree-1 recovery = Berlekamp–Massey");
assert_eq!(
algebraic_generate(6, 1, &coeffs, &bits[..6], bits.len()),
bits,
"the degree-1 ANF regenerates the linear keystream"
);
assert!(berlekamp_massey_gf2(&bits).0 <= 6, "an LFSR keystream has linear complexity ≤ its order");
}
#[test]
fn correlation_attack_breaks_the_geffe_combiner_register_by_register() {
let n = 2000;
let taps1 = [false, false, true, false, false, false, true]; let taps2 = [false, false, true, false, true]; let taps3 = [false, false, false, false, true, false, false, false, true]; let seed1 = [true, false, true, true, false, false, true];
let seed2 = [true, true, false, false, true];
let seed3 = [true, false, false, true, false, true, true, false, true];
let x1 = lfsr_generate(&taps1, &seed1, n);
let x2 = lfsr_generate(&taps2, &seed2, n);
let x3 = lfsr_generate(&taps3, &seed3, n);
let z: Vec<bool> = (0..n).map(|i| if x2[i] { x1[i] } else { x3[i] }).collect();
let a1 = correlation_attack(&z, &taps1).expect("x1 register attackable");
assert_eq!(a1.init_state, seed1.to_vec(), "recovers x1's exact initial state");
assert!((a1.agreement - 0.75).abs() < 0.05, "x1 leaks at ~¾ agreement, got {}", a1.agreement);
assert!(a1.bias > 3.0 * spurious_bias_floor(7, n), "x1's correlation clears the spurious floor");
assert_eq!(lfsr_generate(&taps1, &a1.init_state, n), x1, "the witness regenerates the x1 register");
let a3 = correlation_attack(&z, &taps3).expect("x3 register attackable");
assert_eq!(a3.init_state, seed3.to_vec(), "recovers x3's exact initial state");
assert!((a3.agreement - 0.75).abs() < 0.05, "x3 leaks at ~¾ agreement, got {}", a3.agreement);
assert!(a3.bias > 3.0 * spurious_bias_floor(9, n), "x3's correlation clears the spurious floor");
assert_eq!(lfsr_generate(&taps3, &a3.init_state, n), x3, "the witness regenerates the x3 register");
let a2 = correlation_attack(&z, &taps2).expect("x2 register scanned");
assert!(a2.bias < 3.0 * spurious_bias_floor(5, n), "x2 does not measurably leak, bias {}", a2.bias);
assert!(a1.bias > 3.0 * a2.bias, "the leaking registers are starkly separated from the protected one");
}
#[test]
fn walsh_spectrum_sees_the_linear_approximation_correlation_is_blind_to() {
let f: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| (x >> i) & 1 == 1;
b(0) ^ b(1) ^ (b(2) & b(3))
})
.collect();
let spec = walsh_spectrum(&f).expect("well-formed truth table");
for m in [1usize, 2, 4, 8] {
assert_eq!(spec[m], 0, "weight-1 mask {m} vanishes — Rung E is blind here");
}
assert_eq!(correlation_immunity_order(&f), Some(1), "f is first-order correlation-immune");
let (mask, bias) = best_linear_approximation(&f, true).expect("a best approximation exists");
assert_eq!(bias, 0.25, "the exploitable approximation has bias ¼");
assert!(mask.count_ones() >= 2, "and it lives at weight ≥ 2 — beyond E's single-register view");
assert_eq!(nonlinearity(&f), Some(4), "nonlinearity 2³ − 8/2 = 4");
let g: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| (x >> i) & 1 == 1;
(b(0) & b(1)) ^ (b(2) & b(3))
})
.collect();
let spec_g = walsh_spectrum(&g).expect("well-formed");
assert!(spec_g.iter().all(|&c| c.unsigned_abs() == 4), "bent ⇒ perfectly flat spectrum");
assert_eq!(nonlinearity(&g), Some(6), "bent nonlinearity 2³ − 2¹ = 6 (maximal for n=4)");
assert_eq!(best_linear_approximation(&g, true).unwrap().1, 0.125, "no approximation beats 1/8");
}
#[test]
fn anf_is_its_own_inverse_and_reads_the_degree() {
for n in 1..=5usize {
for seed in 0..8u64 {
let f: Vec<bool> = (0..1u64 << n)
.map(|i| {
let mut z = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i.wrapping_add(seed * 131 + 1));
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
(z ^ (z >> 27)) & 1 == 1
})
.collect();
let a = anf(&f).expect("well-formed");
assert_eq!(anf(&a).as_deref(), Some(&f[..]), "anf∘anf = identity (n={n}, seed={seed})");
}
}
let n = 4;
let parity: Vec<bool> = (0..1usize << n).map(|x| (x as u32).count_ones() % 2 == 1).collect();
assert_eq!(algebraic_degree(&parity), Some(1), "parity is affine");
let bent: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1 << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3))
})
.collect();
assert_eq!(algebraic_degree(&bent), Some(2), "x0x1 ⊕ x2x3 is quadratic");
assert_eq!(algebraic_degree(&vec![true; 8]), Some(0), "a constant has degree 0");
}
#[test]
fn autocorrelation_reads_the_derivative_symmetry() {
let n = 4;
let parity: Vec<bool> = (0..1usize << n).map(|x| (x as u32).count_ones() % 2 == 1).collect();
let rp = autocorrelation(&parity).expect("well-formed");
assert_eq!(rp[0], 16, "r(0) = 2ⁿ always");
assert!(rp.iter().all(|&c| c.unsigned_abs() == 16), "a linear function is flat: |r(a)| = 2ⁿ ∀a");
let bent: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1 << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3))
})
.collect();
let rb = autocorrelation(&bent).expect("well-formed");
assert_eq!(rb[0], 16);
assert!(rb[1..].iter().all(|&c| c == 0), "bent ⇒ no nonzero linear structure");
let f: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1 << i) != 0;
((b(0) && b(1)) ^ b(2)) ^ b(3)
})
.collect();
let rf = autocorrelation(&f).expect("well-formed");
assert_eq!(rf[1 << 3], -16, "flipping x3 always flips f ⇒ r(e3) = −2ⁿ (a linear structure)");
}
#[test]
fn walsh_found_approximation_breaks_a_correlation_immune_combiner() {
let n = 4000;
let taps1 = [false, false, true, false, false, false, true]; let taps2 = [false, false, true, false, true]; let taps3 = [false, false, false, false, true, false, false, false, true]; let taps4 = [false, false, false, false, false, false, false, false, true, false, true]; let a1 = lfsr_generate(&taps1, &[true, false, true, true, false, false, true], n);
let a2 = lfsr_generate(&taps2, &[true, true, false, false, true], n);
let a3 = lfsr_generate(&taps3, &[true, false, false, true, false, true, true, false, true], n);
let a4 = lfsr_generate(&taps4, &[true, false, true, false, false, true, true, false, false, true, false], n);
let z: Vec<bool> = (0..n).map(|i| a1[i] ^ a2[i] ^ (a3[i] & a4[i])).collect();
let agree1 = z.iter().zip(&a1).filter(|(x, y)| x == y).count() as f64 / n as f64;
assert!((agree1 - 0.5).abs() < 0.04, "single register a1 is invisible to first-order correlation, {agree1}");
let combined: Vec<bool> = (0..n).map(|i| a1[i] ^ a2[i]).collect();
let agree_r = z.iter().zip(&combined).filter(|(x, y)| x == y).count() as f64 / n as f64;
assert!((agree_r - 0.75).abs() < 0.04, "the weight-2 approximation leaks at ¾ — E could not see it, {agree_r}");
}
#[test]
fn fast_correlation_attack_decodes_a_noisy_lfsr_without_exhaustive_search() {
let mut taps = vec![false; 17];
taps[13] = true; taps[16] = true; let seed: Vec<bool> = (0..17).map(|k| (0xACE1u64 >> k) & 1 == 1).collect();
let n = 4000;
let a = lfsr_generate(&taps, &seed, n);
let mut st = 0x1234_5678_9abc_def0u64;
let z: Vec<bool> = a
.iter()
.map(|&bit| {
st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
bit ^ ((st >> 40) % 100 < 12)
})
.collect();
let agree = a.iter().zip(&z).filter(|(x, y)| x == y).count() as f64 / n as f64;
assert!((agree - 0.88).abs() < 0.04, "the channel is genuinely ~12% noisy, got {agree}");
let state = fast_correlation_attack(&z, &taps, 400).expect("the decoder recovers the register");
assert_eq!(state, seed, "fast correlation recovers the exact initial state — no 2¹⁷ search");
let mut st2 = 0xdead_beef_0bad_c0deu64;
let pure: Vec<bool> = (0..n)
.map(|_| {
st2 = st2.wrapping_mul(6364136223846793005).wrapping_add(1);
(st2 >> 40) & 1 == 1
})
.collect();
assert!(fast_correlation_attack(&pure, &taps, 400).is_none(), "pure noise leaks no register — the ceiling");
}
#[test]
fn shrinking_generator_falls_to_a_clock_guess_the_linear_rungs_cannot_touch() {
let a_taps = [false, false, true, false, false, false, true]; let s_taps = [false, false, false, false, true, false, false, false, true]; let a_seed = [true, false, true, true, false, false, true];
let s_seed = [true, true, false, true, false, true, true, false, true];
let m = 300;
let output = shrinking_generator(&a_taps, &a_seed, &s_taps, &s_seed, m);
assert!(
berlekamp_massey_gf2(&output).0 > 40,
"the shrinking generator's output has high linear complexity, got {}",
berlekamp_massey_gf2(&output).0
);
let (a_rec, s_rec) = attack_shrinking_generator(&output, &a_taps, &s_taps).expect("the generator falls");
assert_eq!(
shrinking_generator(&a_taps, &a_rec, &s_taps, &s_rec, m),
output,
"the recovered pair regenerates the keystream — the certified break"
);
assert_eq!(
shrinking_generator(&a_taps, &a_rec, &s_taps, &s_rec, 1000),
shrinking_generator(&a_taps, &a_seed, &s_taps, &s_seed, 1000),
"and stays identical to the true generator well past the attacked length — a full break"
);
}
#[test]
fn xl_solves_a_quadratic_system_by_degree_escalation() {
let eqs: Vec<Vec<u64>> = vec![
vec![0b0011], vec![0b0101, 0b0000], vec![0b0110], vec![0b1100], vec![0b0001, 0b0100], vec![0b0010, 0b1000], ];
assert_eq!(
solve_polynomial_system_gf2(&eqs, 4, 1),
PolySolveResult::Undetermined,
"degree 1 cannot represent the quadratic constraints"
);
assert_eq!(
solve_polynomial_system_gf2(&eqs, 4, 2),
PolySolveResult::Solved(vec![true, false, true, false]),
"XL recovers the unique root"
);
}
#[test]
fn xl_refutes_an_unsatisfiable_system_linearization_misses() {
let eqs: Vec<Vec<u64>> = vec![
vec![0b11, 0b00], vec![0b01], ];
assert_eq!(
solve_polynomial_system_gf2(&eqs, 2, 1),
PolySolveResult::Undetermined,
"at degree 1 the quadratic contradiction is invisible"
);
assert_eq!(
solve_polynomial_system_gf2(&eqs, 2, 2),
PolySolveResult::Refuted,
"XL manufactures x0·x1 = 0, refuting the system"
);
}
#[test]
fn algebraic_immunity_computes_and_verifies_known_values() {
let b = |x: usize, i: usize| (x >> i) & 1 == 1;
let affine: Vec<bool> = (0..8).map(|x| b(x, 0) ^ b(x, 1) ^ b(x, 2)).collect();
let (ai, w) = algebraic_immunity(&affine).expect("well-formed");
assert_eq!(ai, 1, "affine functions have AI 1");
assert!(verify_annihilator(&affine, &w), "the affine annihilator re-checks");
let maj3: Vec<bool> = (0..8).map(|x| (x as u32).count_ones() >= 2).collect();
let (ai, w) = algebraic_immunity(&maj3).expect("well-formed");
assert_eq!(ai, 2, "majority-3 has AI 2 (the maximum for n=3)");
assert!(verify_annihilator(&maj3, &w), "the degree-2 annihilator re-checks");
let and3: Vec<bool> = (0..8).map(|x| b(x, 0) & b(x, 1) & b(x, 2)).collect();
assert_eq!(algebraic_immunity(&and3).unwrap().0, 1, "a single product term has AI 1");
let (_, w) = algebraic_immunity(&maj3).unwrap();
let mut zeroed = w.clone();
zeroed.coeffs = vec![false; zeroed.coeffs.len()];
assert!(!verify_annihilator(&maj3, &zeroed), "the zero function is not a valid annihilator");
let mut flipped = w.clone();
flipped.annihilates_complement = !flipped.annihilates_complement;
assert!(!verify_annihilator(&maj3, &flipped), "a nonzero g cannot vanish on both sides");
}
#[test]
fn algebraic_immunity_never_exceeds_half_n() {
let mut st = 0x00C0_FFEE_1234_5678u64;
for _ in 0..24 {
let truth: Vec<bool> = (0..16)
.map(|_| {
st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(st >> 33) & 1 == 1
})
.collect();
let (ai, w) = algebraic_immunity(&truth).expect("well-formed");
assert!(ai <= 2, "AI ≤ ⌈4/2⌉ = 2, got {ai}");
assert!(verify_annihilator(&truth, &w), "every witness re-checks");
}
}
#[test]
fn algebraic_filter_attack_recovers_the_filter_generator_state() {
let taps = [false, false, false, false, false, false, true, false, false, true]; let s0 = [true, false, true, true, false, false, true, false, true, true];
let filter_truth: Vec<bool> = (0..8).map(|x| (x as u32).count_ones() >= 2).collect(); let m = 3;
let n = 400;
let seq = lfsr_generate(&taps, &s0, n + m);
let keystream: Vec<bool> = (0..n)
.map(|t| {
let idx = (0..m).fold(0usize, |a, i| a | (usize::from(seq[t + i]) << i));
filter_truth[idx]
})
.collect();
let recovered = algebraic_filter_attack(&keystream, &taps, &filter_truth)
.expect("the algebraic attack recovers the state");
assert_eq!(recovered, s0.to_vec(), "the recovered initial state IS the secret key");
}
#[test]
fn periodic_round_trips() {
let block = [4i64, 1, 1, 5, 9, 2, 6];
let v: Vec<i64> = (0..300).map(|i| block[i % block.len()]).collect();
roundtrips(&v);
}
#[test]
fn modular_affine_round_trips() {
let v: Vec<i64> = (0..200).map(|i| 100 + 3 * ((i % 8) as i64)).collect();
roundtrips(&v);
}
#[test]
fn sparse_round_trips() {
let mut v = vec![42i64; 500];
v[17] = -1;
v[300] = 999;
v[499] = 7;
roundtrips(&v);
}
#[test]
fn delta_and_dod_shapes_round_trip() {
let monotone: Vec<i64> = (0..200).map(|i| i * i / 7 + i).collect();
roundtrips(&monotone);
let jittered: Vec<i64> = (0..200).map(|i| 1_000_000 + 60 * i + (i % 3)).collect();
roundtrips(&jittered);
}
#[test]
fn runs_and_low_cardinality_round_trip() {
let mut runs = vec![5i64; 100];
runs.extend(std::iter::repeat(9).take(80));
runs.extend(std::iter::repeat(-2).take(120));
roundtrips(&runs);
let categorical: Vec<i64> = (0..600).map(|i| [10, 20, 30][i % 3]).collect();
roundtrips(&categorical);
}
#[test]
fn byte_column_round_trips() {
let v: Vec<i64> = (0..500).map(|i| ((i * 37) % 256) as i64).collect();
roundtrips(&v);
}
#[test]
fn edge_cases_round_trip() {
roundtrips(&[]);
roundtrips(&[42]);
roundtrips(&[-1]);
roundtrips(&[i64::MIN, i64::MAX, 0, -1, 1]);
roundtrips(&[7, 7, 7, 7]);
}
#[test]
fn pseudorandom_round_trips_and_is_never_larger_than_varint() {
let mut state = 0x1234_5678_9abc_def0u64;
let v: Vec<i64> = (0..400)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 1) as i64
})
.collect();
roundtrips(&v);
let mut baseline = vec![T_INTS];
leb128_encode(&mut baseline, v.iter().copied(), v.len());
assert!(describe_int_seq(&v).len() <= baseline.len(), "never worse than the varint baseline");
}
}