use sha2::{Sha256, Digest};
pub const MAX_SCRAMBLE_SIZE: usize = 65536;
pub struct CiphertextScrambler {
forward_map: Vec<u16>,
reverse_map: Vec<u16>,
size: usize,
}
impl CiphertextScrambler {
pub fn from_entropy(entropy: &[u8; 32], size: usize) -> Self {
assert!(size <= MAX_SCRAMBLE_SIZE, "Size exceeds maximum scramble size");
let forward_map = Self::generate_permutation(entropy, size);
let mut reverse_map = vec![0u16; size];
for (original_pos, &scrambled_pos) in forward_map.iter().enumerate() {
reverse_map[scrambled_pos as usize] = original_pos as u16;
}
Self {
forward_map,
reverse_map,
size,
}
}
fn generate_permutation(entropy: &[u8; 32], size: usize) -> Vec<u16> {
let mut perm: Vec<u16> = (0..size as u16).collect();
if size <= 1 {
return perm;
}
let mut state = *entropy;
let mut random_idx = 0usize;
let mut random_bytes = [0u8; 32];
for i in (1..size).rev() {
let j = if i <= 255 {
if random_idx >= 32 {
let mut hasher = Sha256::new();
hasher.update(&state);
hasher.update(&(i as u64).to_le_bytes());
let result = hasher.finalize();
random_bytes.copy_from_slice(&result);
state = random_bytes;
random_idx = 0;
}
let r = random_bytes[random_idx];
random_idx += 1;
(r as usize) % (i + 1)
} else {
if random_idx >= 28 { let mut hasher = Sha256::new();
hasher.update(&state);
hasher.update(&(i as u64).to_le_bytes());
let result = hasher.finalize();
random_bytes.copy_from_slice(&result);
random_idx = 0;
}
let r = ((random_bytes[random_idx] as u32) << 24)
| ((random_bytes[random_idx + 1] as u32) << 16)
| ((random_bytes[random_idx + 2] as u32) << 8)
| (random_bytes[random_idx + 3] as u32);
random_idx += 4;
(r as usize) % (i + 1)
};
perm.swap(i, j);
}
perm
}
pub fn scramble(&self, data: &mut [u8]) {
assert_eq!(data.len(), self.size, "Data length must match scrambler size");
let mut visited = vec![false; self.size];
for i in 0..self.size {
if visited[i] {
continue;
}
let mut current = i;
let temp = data[i];
loop {
let next = self.forward_map[current] as usize;
if next == i {
data[current] = temp;
visited[current] = true;
break;
}
data[current] = data[next];
visited[current] = true;
current = next;
}
}
}
pub fn unscramble(&self, data: &mut [u8]) {
assert_eq!(data.len(), self.size, "Data length must match scrambler size");
let mut visited = vec![false; self.size];
for i in 0..self.size {
if visited[i] {
continue;
}
let mut current = i;
let temp = data[i];
loop {
let prev = self.reverse_map[current] as usize;
if prev == i {
data[current] = temp;
visited[current] = true;
break;
}
data[current] = data[prev];
visited[current] = true;
current = prev;
}
}
}
pub fn scramble_copy(&self, data: &[u8]) -> Vec<u8> {
assert_eq!(data.len(), self.size, "Data length must match scrambler size");
let mut result = vec![0u8; self.size];
for (original_pos, &scrambled_pos) in self.forward_map.iter().enumerate() {
result[scrambled_pos as usize] = data[original_pos];
}
result
}
pub fn unscramble_copy(&self, data: &[u8]) -> Vec<u8> {
assert_eq!(data.len(), self.size, "Data length must match scrambler size");
let mut result = vec![0u8; self.size];
for (scrambled_pos, &original_pos) in self.reverse_map.iter().enumerate() {
result[original_pos as usize] = data[scrambled_pos];
}
result
}
pub fn size(&self) -> usize {
self.size
}
}
pub fn scramble_with_entropy(data: &mut [u8], entropy: &[u8; 32]) {
if data.is_empty() {
return;
}
let scrambler = CiphertextScrambler::from_entropy(entropy, data.len());
scrambler.scramble(data);
}
pub fn unscramble_with_entropy(data: &mut [u8], entropy: &[u8; 32]) {
if data.is_empty() {
return;
}
let scrambler = CiphertextScrambler::from_entropy(entropy, data.len());
scrambler.unscramble(data);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scramble_unscramble_identity() {
let entropy = [0x42u8; 32];
let original = b"Hello, World! This is a test message for scrambling.";
let scrambler = CiphertextScrambler::from_entropy(&entropy, original.len());
let mut data = original.to_vec();
scrambler.scramble(&mut data);
assert_ne!(&data[..], &original[..]);
scrambler.unscramble(&mut data);
assert_eq!(&data[..], &original[..]);
}
#[test]
fn test_deterministic_scrambling() {
let entropy = [0xABu8; 32];
let data = b"Test data for deterministic scrambling";
let scrambler1 = CiphertextScrambler::from_entropy(&entropy, data.len());
let scrambler2 = CiphertextScrambler::from_entropy(&entropy, data.len());
let result1 = scrambler1.scramble_copy(data);
let result2 = scrambler2.scramble_copy(data);
assert_eq!(result1, result2);
}
#[test]
fn test_different_entropy_different_result() {
let entropy1 = [0x11u8; 32];
let entropy2 = [0x22u8; 32];
let data = b"Test data for different entropy scrambling test";
let scrambler1 = CiphertextScrambler::from_entropy(&entropy1, data.len());
let scrambler2 = CiphertextScrambler::from_entropy(&entropy2, data.len());
let result1 = scrambler1.scramble_copy(data);
let result2 = scrambler2.scramble_copy(data);
assert_ne!(result1, result2);
}
#[test]
fn test_convenience_functions() {
let entropy = [0x99u8; 32];
let original = b"Convenience test";
let mut data = original.to_vec();
scramble_with_entropy(&mut data, &entropy);
assert_ne!(&data[..], &original[..]);
unscramble_with_entropy(&mut data, &entropy);
assert_eq!(&data[..], &original[..]);
}
#[test]
fn test_single_byte() {
let entropy = [0xFFu8; 32];
let mut data = vec![0x42u8];
scramble_with_entropy(&mut data, &entropy);
assert_eq!(data, vec![0x42u8]); }
#[test]
fn test_empty_data() {
let entropy = [0x00u8; 32];
let mut data: Vec<u8> = vec![];
scramble_with_entropy(&mut data, &entropy);
assert!(data.is_empty());
}
}