use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater};
use vitaminc_random::{RandomError, Rng, SafeRand};
use zeroize::Zeroizing;
use crate::private::IsPermutable;
const fn batcher_fill(n: usize, out: &mut [(u8, u8)]) -> usize {
assert!(n.is_power_of_two(), "network size must be a power of two");
let mut gates = 0;
let mut p = 1;
while p < n {
let mut k = p;
while k != 0 {
let mut j = k % p;
while j + k < n {
let mut i = 0;
while i < k {
assert!(i + j + k < n, "gate index out of bounds");
if (i + j) / (2 * p) == (i + j + k) / (2 * p) {
if gates < out.len() {
out[gates] = ((i + j) as u8, (i + j + k) as u8);
}
gates += 1;
}
i += 1;
}
j += 2 * k;
}
k /= 2;
}
p *= 2;
}
gates
}
pub(crate) const fn batcher_gate_count(n: usize) -> usize {
batcher_fill(n, &mut [])
}
pub(crate) const fn batcher_schedule<const G: usize>(n: usize) -> [(u8, u8); G] {
assert!(n <= 256, "gate indices must fit in u8");
let mut out = [(0u8, 0u8); G];
let gates = batcher_fill(n, &mut out);
assert!(gates == G, "schedule length must match the gate count");
out
}
#[inline(always)]
fn compare_exchange(w: &mut [u64], a: usize, b: usize) {
let swap = w[a].ct_gt(&w[b]);
let (lo, hi) = w.split_at_mut(b);
u64::conditional_swap(&mut lo[a], &mut hi[0], swap);
}
pub(crate) fn sort<const N: usize>(w: &mut [u64; N])
where
[u8; N]: IsPermutable,
{
for &(a, b) in <[u8; N] as IsPermutable>::SCHEDULE {
compare_exchange(w, a as usize, b as usize);
}
}
fn permutation_from_words<const N: usize>(w: &mut [u64; N], out: &mut [u8; N]) -> bool
where
[u8; N]: IsPermutable,
{
sort(w);
let mut collision = Choice::from(0u8);
for pair in w.windows(2) {
collision |= (pair[0] >> 8).ct_eq(&(pair[1] >> 8));
}
if bool::from(collision) {
return false;
}
for (o, x) in out.iter_mut().zip(w.iter()) {
*o = (x & 0xFF) as u8;
}
true
}
pub(crate) fn random_permutation<const N: usize>(
rng: &mut SafeRand,
out: &mut [u8; N],
) -> Result<(), RandomError>
where
[u8; N]: IsPermutable,
{
let mut w: Zeroizing<[u64; N]> = Zeroizing::new([0; N]);
for (i, slot) in w.iter_mut().enumerate() {
*slot = (rng.next_u64() << 8) + i as u64;
}
if permutation_from_words(&mut w, out) {
Ok(())
} else {
Err(RandomError::SeedRejected)
}
}
#[cfg(test)]
mod tests {
use super::*;
use vitaminc_random::SeedableRng;
fn assert_is_permutation<const N: usize>(p: &[u8; N]) {
let mut seen = [false; N];
for &v in p {
assert!((v as usize) < N, "value {v} out of range");
assert!(!seen[v as usize], "value {v} repeated");
seen[v as usize] = true;
}
}
#[test]
fn gate_counts_match_closed_form() {
for (n, expected) in [
(2, 1),
(4, 5),
(8, 19),
(16, 63),
(32, 191),
(64, 543),
(128, 1471),
] {
assert_eq!(batcher_gate_count(n), expected, "gate count for n = {n}");
}
}
#[test]
#[should_panic(expected = "network size must be a power of two")]
fn network_rejects_a_size_that_is_not_a_power_of_two() {
batcher_gate_count(6);
}
#[test]
fn schedule_gates_are_in_bounds_and_ordered() {
fn check<const N: usize>()
where
[u8; N]: IsPermutable,
{
let gates = <[u8; N] as IsPermutable>::SCHEDULE;
assert_eq!(gates.len(), batcher_gate_count(N));
for &(a, b) in gates {
assert!(a < b, "gate ({a}, {b}) not ordered");
assert!((b as usize) < N, "gate ({a}, {b}) out of bounds for {N}");
}
}
check::<8>();
check::<16>();
check::<32>();
check::<64>();
check::<128>();
}
#[test]
fn network_sorts_all_binary_inputs_for_every_length_to_16() {
for n in [2usize, 4, 8, 16] {
let count = batcher_gate_count(n);
let mut gates = vec![(0u8, 0u8); count];
assert_eq!(batcher_fill(n, &mut gates), count);
for &(a, b) in &gates {
assert!(a < b, "n = {n}: gate ({a}, {b}) not ordered");
assert!((b as usize) < n, "n = {n}: gate ({a}, {b}) out of bounds");
}
match n {
8 => assert_eq!(<[u8; 8] as IsPermutable>::SCHEDULE, &gates[..]),
16 => assert_eq!(<[u8; 16] as IsPermutable>::SCHEDULE, &gates[..]),
_ => {}
}
for bits in 0u32..(1 << n) {
let mut w: Vec<u64> = (0..n).map(|i| u64::from(bits >> i) & 1).collect();
for &(a, b) in &gates {
compare_exchange(&mut w, a as usize, b as usize);
}
assert!(w.is_sorted(), "n = {n}: failed on binary input {bits:#b}");
}
}
}
#[test]
fn network_sorts_random_words() {
fn check<const N: usize>(rng: &mut SafeRand)
where
[u8; N]: IsPermutable,
{
for _ in 0..100 {
let mut w: [u64; N] = core::array::from_fn(|_| rng.next_u64());
let mut expected = w;
expected.sort_unstable();
sort(&mut w);
assert_eq!(w, expected);
}
}
let mut rng = SafeRand::from_seed([42u8; 32]);
check::<8>(&mut rng);
check::<16>(&mut rng);
check::<32>(&mut rng);
check::<64>(&mut rng);
check::<128>(&mut rng);
}
#[test]
fn output_is_a_valid_permutation() {
fn check<const N: usize>(rng: &mut SafeRand)
where
[u8; N]: IsPermutable,
{
for _ in 0..50 {
let mut out = [0u8; N];
random_permutation::<N>(rng, &mut out).unwrap();
assert_is_permutation(&out);
}
}
let mut rng = SafeRand::from_seed([9u8; 32]);
check::<8>(&mut rng);
check::<16>(&mut rng);
check::<32>(&mut rng);
check::<64>(&mut rng);
check::<128>(&mut rng);
}
#[test]
fn colliding_keys_reject_the_batch() {
let mut w: [u64; 8] = core::array::from_fn(|i| ((i as u64) << 8) | i as u64);
w[3] = (7 << 8) | 3; let mut out = [0xAAu8; 8];
assert!(!permutation_from_words(&mut w, &mut out));
assert_eq!(out, [0xAA; 8]);
let mut w: [u64; 8] = core::array::from_fn(|i| ((7 - i as u64) << 8) | i as u64);
assert!(permutation_from_words(&mut w, &mut out));
assert_eq!(out, [7, 6, 5, 4, 3, 2, 1, 0]);
}
#[test]
fn output_is_deterministic_for_a_seed() {
let mut a = [0u8; 64];
let mut b = [0u8; 64];
random_permutation(&mut SafeRand::from_seed([1u8; 32]), &mut a).unwrap();
random_permutation(&mut SafeRand::from_seed([1u8; 32]), &mut b).unwrap();
assert_eq!(a, b);
}
}