use num_bigint::BigUint;
use num_traits::One;
use crate::engine::{
encode_len_u32, ff1, num_radix, num_radix_reference, prf_with_key, str_radix,
str_radix_reference, D_C_THRESHOLD,
};
#[test]
fn num_radix_decodes_big_endian() {
assert_eq!(num_radix(10, &[1, 2, 3]), BigUint::from(123u32));
assert_eq!(num_radix(10, &[0, 0, 1]), BigUint::from(1u32));
assert_eq!(num_radix(256, &[255, 255]), BigUint::from(65_535u32));
}
#[test]
fn str_radix_encodes_big_endian_and_truncates() {
assert_eq!(str_radix(&BigUint::from(123u32), 10, 3), vec![1, 2, 3]);
assert_eq!(str_radix(&BigUint::from(1u32), 10, 3), vec![0, 0, 1]);
assert_eq!(str_radix(&BigUint::from(1_234u32), 10, 3), vec![2, 3, 4]);
}
#[test]
fn round_trip_conversion() {
for &radix in &[2u32, 10, 36, 256, 65_535] {
for len in [1usize, 2, 7, 64, 130] {
let numerals: Vec<u16> = (0..len).map(|i| (i % radix as usize) as u16).collect();
let value = num_radix(radix, &numerals);
assert_eq!(
str_radix(&value, radix, len),
numerals,
"radix {radix} len {len}"
);
}
}
}
#[test]
fn b_derivation_uses_v_not_u() {
let v = 3usize;
#[allow(clippy::manual_div_ceil)]
let b = ((BigUint::from(256u32).pow(v as u32) - BigUint::one()).bits() + 7) / 8;
assert_eq!(b, 3);
let u = 2usize;
#[allow(clippy::manual_div_ceil)]
let b_wrong = ((BigUint::from(256u32).pow(u as u32) - BigUint::one()).bits() + 7) / 8;
assert_eq!(b_wrong, 2, "sanity: the two derivations must disagree here");
}
#[test]
fn padding_formula_matches_python() {
for t in [0usize, 1, 3, 11, 12, 13, 16, 27, 32] {
for b in [4usize, 9, 20] {
let py_pad = (-(t as isize) - b as isize - 1).rem_euclid(16) as usize;
assert_eq!((t + py_pad + 1 + b) % 16, 0, "t={t} b={b}");
}
}
}
#[test]
fn radix_bounds_representable() {
assert_eq!(num_radix(2, &[1, 0, 1, 1]), BigUint::from(11u32));
assert_eq!(
num_radix(65_535, &[65_534, 1]),
BigUint::from(65534u64 * 65535 + 1)
);
}
#[test]
fn prf_structural_properties() {
let key = [7u8; 32];
let data = [1u8; 48]; let tag1 = prf_with_key(&key, &data).expect("valid key");
let tag2 = prf_with_key(&key, &data).expect("valid key");
assert_eq!(tag1.len(), 16, "CBC-MAC tag is one block");
assert_eq!(tag1, tag2, "PRF is deterministic for fixed inputs");
let other_key = [8u8; 32];
let tag3 = prf_with_key(&other_key, &data).expect("valid key");
assert_ne!(tag1, tag3);
let mut flipped = data;
flipped[0] ^= 1;
let tag4 = prf_with_key(&key, &flipped).expect("valid key");
assert_ne!(tag1, tag4);
}
#[test]
fn ff1_round_trip_self_consistency() {
for key_len in [16usize, 24, 32] {
let key: Vec<u8> = (0..key_len).map(|i| (i * 7 + 3) as u8).collect();
for (radix, n) in [
(10u32, 6usize),
(10, 7),
(2, 20),
(36, 10),
(256, 5),
(10, 60),
(65_535, 13),
] {
let x: Vec<u16> = (0..n)
.map(|i| ((i as u32 * 7919 + 17) % radix) as u16)
.collect();
let ct = ff1(&key, radix, &x, b"kat", true)
.unwrap_or_else(|e| panic!("encrypt failed: {e}"));
assert_eq!(ct.len(), n, "length preservation");
assert!(ct.iter().all(|&c| (c as u32) < radix), "range preservation");
let pt = ff1(&key, radix, &ct, b"kat", false)
.unwrap_or_else(|e| panic!("decrypt failed: {e}"));
assert_eq!(pt, x, "round trip, radix {radix} n {n} key_len {key_len}");
}
}
}
#[test]
fn encode_len_u32_is_big_endian_at_the_boundary() {
assert_eq!(encode_len_u32(0), Ok([0, 0, 0, 0]));
assert_eq!(encode_len_u32(10), Ok([0, 0, 0, 10]));
assert_eq!(
encode_len_u32(u32::MAX as usize),
Ok([0xff, 0xff, 0xff, 0xff])
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn encode_len_u32_rejects_rather_than_wraps() {
assert!(encode_len_u32(u32::MAX as usize + 1).is_err());
assert!(encode_len_u32(1usize << 40).is_err());
}
const REPRESENTATIVE_RADICES: [u32; 5] = [2, 10, 36, 256, 65_535];
const EQUIVALENCE_LENGTHS: [usize; 12] = [0, 1, 2, 63, 64, 65, 128, 129, 131, 257, 1000, 2049];
fn sample_numerals(radix: u32, length: usize, seed: u64) -> Vec<u16> {
let mut state = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
(0..length)
.map(|i| {
if i < length / 8 {
0
} else if i >= length - length / 8 {
(radix - 1) as u16
} else {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
((state >> 33) % radix as u64) as u16
}
})
.collect()
}
#[test]
fn d_c_threshold_mirrors_the_python_reference() {
assert_eq!(D_C_THRESHOLD, 64);
}
#[test]
fn num_radix_equals_reference_at_representative_radices() {
for &radix in &REPRESENTATIVE_RADICES {
for &length in &EQUIVALENCE_LENGTHS {
let numerals = sample_numerals(radix, length, (radix as u64) << 16 | length as u64);
assert_eq!(
num_radix(radix, &numerals),
num_radix_reference(radix, &numerals),
"radix {radix} length {length}"
);
}
}
}
#[test]
fn str_radix_equals_reference_at_representative_radices() {
for &radix in &REPRESENTATIVE_RADICES {
for &length in &EQUIVALENCE_LENGTHS {
let numerals = sample_numerals(radix, length, (length as u64) << 16 | radix as u64);
let value = num_radix_reference(radix, &numerals);
let expected = str_radix_reference(&value, radix, length);
assert_eq!(expected, numerals, "reference self-check, radix {radix}");
assert_eq!(
str_radix(&value, radix, length),
expected,
"radix {radix} length {length}"
);
}
}
}
#[test]
fn every_supported_radix_is_equivalent_above_the_threshold() {
let length = D_C_THRESHOLD + 1;
for radix in 2u32..65_536 {
let numerals = sample_numerals(radix, length, radix as u64);
let value = num_radix_reference(radix, &numerals);
assert_eq!(num_radix(radix, &numerals), value, "NUM radix {radix}");
assert_eq!(
str_radix(&value, radix, length),
str_radix_reference(&value, radix, length),
"STR radix {radix}"
);
}
}
#[test]
fn every_power_of_two_radix_is_equivalent_at_every_chunk_alignment() {
for k in 1u32..16 {
let radix = 1u32 << k;
for length in 65usize..=81 {
let numerals = sample_numerals(radix, length, (k as u64) << 8 | length as u64);
let value = num_radix_reference(radix, &numerals);
assert_eq!(
num_radix(radix, &numerals),
value,
"NUM k {k} length {length}"
);
assert_eq!(
str_radix(&value, radix, length),
str_radix_reference(&value, radix, length),
"STR k {k} length {length}"
);
}
}
}
#[test]
fn str_radix_truncation_contract_matches_reference() {
for &radix in &REPRESENTATIVE_RADICES {
for length in [65usize, 129, 257] {
let base = BigUint::from(radix);
let over = base.pow(length as u32) * BigUint::from(12_345u32) + BigUint::from(678u32);
let far = base.pow((2 * length) as u32) - BigUint::one();
for value in [over, far] {
assert_eq!(
str_radix(&value, radix, length),
str_radix_reference(&value, radix, length),
"radix {radix} length {length}"
);
}
}
}
}