use itertools::Itertools;
use num_bigint::BigUint;
use num_traits::Zero;
use crate::{utils, BooleanFunctionImpl, BooleanFunctionType, SmallBooleanFunction};
pub const BOOLEAN_FUNCTIONS_3_VAR_AFFINE_EQ_CLASSES: [SmallBooleanFunction; 3] = [
SmallBooleanFunction::from_truth_table_unchecked(0xaa, 3),
SmallBooleanFunction::from_truth_table_unchecked(0xab, 3),
SmallBooleanFunction::from_truth_table_unchecked(0xac, 3),
];
pub const BOOLEAN_FUNCTIONS_4_VAR_AFFINE_EQ_CLASSES: [SmallBooleanFunction; 8] = [
SmallBooleanFunction::from_truth_table_unchecked(0xaa55, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xab55, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xbb55, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xaba5, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xaaff, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xaba4, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xab12, 4),
SmallBooleanFunction::from_truth_table_unchecked(0xac90, 4), ];
pub const BOOLEAN_FUNCTIONS_5_VAR_AFFINE_EQ_CLASSES: [SmallBooleanFunction; 48] = [
SmallBooleanFunction::from_truth_table_unchecked(0xaa55aa55, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaa55ab55, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaa55bb55, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaa5dbb55, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaaddbb55, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaa5dbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x2a5dbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaaddbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x2a5dbf51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x6a5dbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x2addbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xa8ddbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaeddda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x0a5dbf51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8addda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xa8dd9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x88ddbb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x88ddbb11, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8c5dda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xa89d9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8eddda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xaefdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x025dbf51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x88ddda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x88dd9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xceddda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x0eddda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x425dbf51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8cddda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x88dddb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x289d9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x86fdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x88dddb71, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xcefdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x0efdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x288d9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8cfdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8cdddb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x8ccdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x289d9b41, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x488ddb51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xccfdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x688d9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x288d9b41, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x288d1b41, 5),
SmallBooleanFunction::from_truth_table_unchecked(0xdcfdda51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x68ad9b51, 5),
SmallBooleanFunction::from_truth_table_unchecked(0x688ddb51, 5),
];
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AffineEquivalenceFactors {
pub d: Vec<u32>,
pub a: u32,
pub b: u32,
pub c: bool,
}
pub fn compute_affine_equivalence(f: &impl BooleanFunctionImpl, g: &impl BooleanFunctionImpl) -> Option<AffineEquivalenceFactors> {
if f.variables_count() != g.variables_count() || f.variables_count() == 0 {
return None;
}
let g_0_connecting = g.get_1_local_neighbor(0);
let possible_a = (0u32..=f.get_max_input_value())
.into_iter()
.filter(|a| {
let f_a_connecting = f.get_1_local_neighbor(*a);
f_a_connecting.algebraic_degree() == g_0_connecting.algebraic_degree()
&& f_a_connecting.absolute_walsh_hadamard_spectrum() == g_0_connecting.absolute_walsh_hadamard_spectrum()
&& f_a_connecting.absolute_autocorrelation() == g_0_connecting.absolute_autocorrelation()
});
let possible_d_columns = (0..f.variables_count())
.into_iter()
.map(|column| {
let g_j_connecting = g.get_1_local_neighbor(1 << column);
(0u32..=f.get_max_input_value())
.into_iter()
.filter(|i| {
let f_i_connecting = f.get_1_local_neighbor(*i);
f_i_connecting.algebraic_degree() == g_j_connecting.algebraic_degree()
&& f_i_connecting.absolute_walsh_hadamard_spectrum() == g_j_connecting.absolute_walsh_hadamard_spectrum()
&& f_i_connecting.absolute_autocorrelation() == g_j_connecting.absolute_autocorrelation()
})
.collect::<Vec<_>>()
});
let all_matrix_candidates_iter = possible_d_columns.multi_cartesian_product();
for a_candidate in possible_a {
for d_a_matrix_candidate in all_matrix_candidates_iter.clone() {
for b_candidate in 0..=f.get_max_input_value() {
for c_candidate in [true, false] {
let mut test_tt_small = 0u64;
let mut test_tt_big = BigUint::zero();
for x in 0..=f.get_max_input_value() {
let mut new_x = 0u32;
for (index, d_a_col_candidate) in d_a_matrix_candidate.iter().enumerate() {
let d_col = d_a_col_candidate ^ a_candidate;
let x_bit = utils::fast_binary_dot_product(d_col, x) & 1;
new_x |= x_bit << index;
}
let new_x_a = new_x ^ a_candidate;
let f_eval_new_x_a = f.compute_cellular_automata_rule(new_x_a);
let computed_bit_value = f_eval_new_x_a ^ ((utils::fast_binary_dot_product(x, b_candidate) & 1) != 0) ^ c_candidate;
match f.get_boolean_function_type() {
BooleanFunctionType::Small => {
test_tt_small |= (computed_bit_value as u64) << x;
}
BooleanFunctionType::Big => {
test_tt_big.set_bit(x as u64, computed_bit_value);
}
}
}
let same_tt = match f.get_boolean_function_type() {
BooleanFunctionType::Small => test_tt_small == g.try_u64_truth_table().unwrap(),
BooleanFunctionType::Big => test_tt_big == g.biguint_truth_table()
};
if same_tt {
return Some(AffineEquivalenceFactors {
d: d_a_matrix_candidate.iter()
.map(|column| column ^ a_candidate)
.collect(),
a: a_candidate,
b: b_candidate,
c: c_candidate,
});
}
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use crate::affine_equivalence_classes::compute_affine_equivalence;
use crate::{BooleanFunction, SmallBooleanFunction};
#[test]
fn test_compute_affine_equivalence_not_same_var_count() {
let f = BooleanFunction::from_hex_string_truth_table("aa").unwrap();
let g = BooleanFunction::from_hex_string_truth_table("aaaa").unwrap();
assert_eq!(compute_affine_equivalence(&f, &g), None);
}
#[test]
fn test_compute_affine_equivalence() {
let f = BooleanFunction::from_hex_string_truth_table("1234").unwrap();
let g = BooleanFunction::from_hex_string_truth_table("1234").unwrap();
let factors = compute_affine_equivalence(&f, &g);
assert!(factors.is_some());
let factors = factors.unwrap();
assert_eq!(factors.d, [1, 2, 4, 8]); assert_eq!(factors.a, 0);
assert_eq!(factors.b, 0);
assert_eq!(factors.c, false);
let f = BooleanFunction::from_hex_string_truth_table("1234").unwrap();
let g = BooleanFunction::from_hex_string_truth_table("edcb").unwrap(); let factors = compute_affine_equivalence(&f, &g);
assert!(factors.is_some());
let factors = factors.unwrap();
assert_eq!(factors.d, [1, 2, 4, 8]); assert_eq!(factors.a, 0);
assert_eq!(factors.b, 0);
assert_eq!(factors.c, true);
let f = BooleanFunction::from_hex_string_truth_table("aaaa").unwrap();
let g = BooleanFunction::from_hex_string_truth_table("5555").unwrap();
let factors = compute_affine_equivalence(&f, &g);
assert!(factors.is_some());
let factors = factors.unwrap();
assert_eq!(factors.d, [0, 0, 0, 0]); assert_eq!(factors.a, 0);
assert_eq!(factors.b, 1);
assert_eq!(factors.c, true);
let f = BooleanFunction::from_hex_string_truth_table("aa55").unwrap();
let g = BooleanFunction::from_hex_string_truth_table("ab55").unwrap();
let factors = compute_affine_equivalence(&f, &g);
assert!(factors.is_none());
let f = BooleanFunction::from_hex_string_truth_table("ac90").unwrap();
let g = BooleanFunction::from_hex_string_truth_table("dbd4").unwrap();
let factors = compute_affine_equivalence(&f, &g);
assert!(factors.is_some());
let factors = factors.unwrap();
assert_eq!(factors.d, [1, 2, 6, 8]);
assert_eq!(factors.a, 0);
assert_eq!(factors.b, 10);
assert_eq!(factors.c, false);
}
#[test]
fn test_compute_affine_equivalence_small() {
let f = SmallBooleanFunction::from_truth_table(0x1234, 4).unwrap();
let g = BooleanFunction::from_hex_string_truth_table("1234").unwrap();
let factors = compute_affine_equivalence(&f, &g);
assert!(factors.is_some());
let factors = factors.unwrap();
assert_eq!(factors.d, [1, 2, 4, 8]); assert_eq!(factors.a, 0);
assert_eq!(factors.b, 0);
assert_eq!(factors.c, false);
}
}