toolkit_zero/serialization/
veil.rs1use bincode::{
9 config::standard,
10 encode_to_vec, decode_from_slice,
11 Encode, Decode,
12 error::{EncodeError, DecodeError},
13};use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
14#[derive(Debug)]
18pub enum SerializationError {
19 Encode(EncodeError),
21 Decode(DecodeError),
23}
24
25impl std::fmt::Display for SerializationError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 Self::Encode(e) => write!(f, "seal encode error: {e}"),
29 Self::Decode(e) => write!(f, "open decode error: {e}"),
30 }
31 }
32}
33
34impl std::error::Error for SerializationError {}
35
36impl From<EncodeError> for SerializationError {
37 fn from(e: EncodeError) -> Self { Self::Encode(e) }
38}
39
40impl From<DecodeError> for SerializationError {
41 fn from(e: DecodeError) -> Self { Self::Decode(e) }
42}
43
44const DEFAULT_KEY: &str = "serialization/deserialization";
47const BLOCK: usize = 16;
48
49pub fn seal<T: Encode>(value: &T, key: Option<String>) -> Result<Vec<u8>, SerializationError> {
62 let key: Zeroizing<String> = Zeroizing::new(key.unwrap_or_else(|| DEFAULT_KEY.to_string()));
63
64 let plain: Zeroizing<Vec<u8>> = Zeroizing::new(encode_to_vec(value, standard())?);
66
67 let cipher = veil_encrypt(&*plain, key.as_str());
69
70 let blob = encode_to_vec(&cipher, standard())?;
72 Ok(blob)
73}
74
75pub fn open<T: Decode<()>>(blob: &[u8], key: Option<String>) -> Result<T, SerializationError> {
84 let key: Zeroizing<String> = Zeroizing::new(key.unwrap_or_else(|| DEFAULT_KEY.to_string()));
85
86 let (cipher, _): (Vec<u8>, _) = decode_from_slice(blob, standard())?;
88
89 let plain = veil_decrypt(&cipher, key.as_str())?;
91
92 let (value, _): (T, _) = decode_from_slice(&plain[..], standard())?;
94 Ok(value)
95}
96
97fn veil_encrypt(plain: &[u8], key: &str) -> Vec<u8> {
100 let ks = KeySchedule::new(key);
101
102 let mut buf: Vec<u8> = plain.iter().map(|&b| ks.sbox[b as usize]).collect();
104
105 for (i, b) in buf.iter_mut().enumerate() {
107 *b ^= ks.stream_byte(i);
108 }
109
110 position_mix_forward(&mut buf);
112
113 block_diffuse_forward(&mut buf);
115
116 block_shuffle_forward(&mut buf, &ks);
118
119 buf
120}
121
122fn veil_decrypt(cipher: &[u8], key: &str) -> Result<Zeroizing<Vec<u8>>, SerializationError> {
125 let ks = KeySchedule::new(key);
126
127 let mut buf: Zeroizing<Vec<u8>> = Zeroizing::new(cipher.to_vec());
129
130 block_shuffle_reverse(&mut *buf, &ks);
132
133 block_diffuse_reverse(&mut *buf);
135
136 position_mix_reverse(&mut *buf);
138
139 for (i, b) in buf.iter_mut().enumerate() {
141 *b ^= ks.stream_byte(i);
142 }
143
144 for b in buf.iter_mut() {
146 *b = ks.sbox_inv[*b as usize];
147 }
148
149 Ok(buf)
150}
151
152#[derive(Zeroize, ZeroizeOnDrop)]
158struct KeySchedule {
159 sbox: [u8; 256],
161 sbox_inv: [u8; 256],
163 stream_seed: u64,
165 shuffle_seed: u64,
167}
168
169impl KeySchedule {
170 fn new(key: &str) -> Self {
171 let h0 = fnv1a_64(key.as_bytes());
173 let h1 = splitmix64(h0 ^ 0xdeadbeef_cafebabe);
174
175 let mut sbox: [u8; 256] = std::array::from_fn(|i| i as u8);
177 let mut rng = Rng::new(h0);
178 for i in (1..256usize).rev() {
179 let j = (rng.next() as usize) % (i + 1);
180 sbox.swap(i, j);
181 }
182
183 let mut sbox_inv = [0u8; 256];
185 for (i, &v) in sbox.iter().enumerate() {
186 sbox_inv[v as usize] = i as u8;
187 }
188
189 Self {
190 sbox,
191 sbox_inv,
192 stream_seed: h1,
193 shuffle_seed: splitmix64(h1 ^ 0x1234567890abcdef),
194 }
195 }
196
197 #[inline]
202 fn stream_byte(&self, pos: usize) -> u8 {
203 let salted = self.stream_seed
204 .wrapping_add(pos as u64)
205 .wrapping_mul(0x9e3779b97f4a7c15);
206 let v = splitmix64(salted);
207 ((v >> 32) ^ (v & 0xffff_ffff)) as u8
209 }
210
211 fn block_perm_len(&self, block_index: usize, len: usize) -> Vec<usize> {
217 let seed = self.shuffle_seed
218 .wrapping_add(block_index as u64)
219 .wrapping_mul(0x6c62272e07bb0142);
220 let mut perm: Vec<usize> = (0..len).collect();
221 let mut rng = Rng::new(splitmix64(seed));
222 for i in (1..len).rev() {
223 let j = (rng.next() as usize) % (i + 1);
224 perm.swap(i, j);
225 }
226 perm
227 }
228
229 fn block_perm(&self, block_index: usize) -> [usize; BLOCK] {
231 let v = self.block_perm_len(block_index, BLOCK);
232 std::array::from_fn(|i| v[i])
233 }
234}
235
236struct Rng(u64);
239
240impl Rng {
241 fn new(seed: u64) -> Self { Self(seed) }
242 #[inline]
243 fn next(&mut self) -> u64 {
244 self.0 = splitmix64(self.0);
245 self.0
246 }
247}
248
249#[inline]
251fn splitmix64(mut x: u64) -> u64 {
252 x = x.wrapping_add(0x9e3779b97f4a7c15);
253 x = (x ^ (x >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
254 x = (x ^ (x >> 27)).wrapping_mul(0x94d049bb133111eb);
255 x ^ (x >> 31)
256}
257
258#[inline]
260fn fnv1a_64(data: &[u8]) -> u64 {
261 const OFFSET: u64 = 0xcbf29ce484222325;
262 const PRIME: u64 = 0x00000100000001b3;
263 let mut h = OFFSET;
264 for &b in data {
265 h ^= b as u64;
266 h = h.wrapping_mul(PRIME);
267 }
268 h
269}
270
271fn position_mix_forward(buf: &mut [u8]) {
278 let mut prev_orig: u8 = 0xA7;
282 for (i, b) in buf.iter_mut().enumerate() {
283 let original = *b;
284 let mix = (i as u8).wrapping_add(prev_orig).wrapping_mul(0x6b);
285 *b ^= mix;
286 prev_orig = original;
287 }
288}
289
290fn position_mix_reverse(buf: &mut [u8]) {
291 let mut prev_orig: u8 = 0xA7;
294 for (i, b) in buf.iter_mut().enumerate() {
295 let mix = (i as u8).wrapping_add(prev_orig).wrapping_mul(0x6b);
296 let original = *b ^ mix;
297 *b = original;
298 prev_orig = original;
299 }
300}
301
302fn block_diffuse_forward(buf: &mut [u8]) {
309 let mut acc: u8 = 0xB3;
310 for b in buf.iter_mut() {
311 let out = *b ^ acc;
312 acc = acc.rotate_left(3) ^ out;
313 *b = out;
314 }
315}
316
317fn block_diffuse_reverse(buf: &mut [u8]) {
318 let mut acc: u8 = 0xB3;
322 for b in buf.iter_mut() {
323 let out = *b;
324 let original = out ^ acc;
325 acc = acc.rotate_left(3) ^ out;
326 *b = original;
327 }
328}
329
330fn block_shuffle_forward(buf: &mut [u8], ks: &KeySchedule) {
337 let n = buf.len();
338 let full_blocks = n / BLOCK;
339 for bi in 0..full_blocks {
340 let perm = ks.block_perm(bi);
341 let base = bi * BLOCK;
342 let block: [u8; BLOCK] = std::array::from_fn(|i| buf[base + i]);
343 for i in 0..BLOCK {
344 buf[base + i] = block[perm[i]];
345 }
346 }
347 let tail_start = full_blocks * BLOCK;
349 let tail_len = n - tail_start;
350 if tail_len > 1 {
351 let perm = ks.block_perm_len(full_blocks, tail_len);
352 let tail: Vec<u8> = buf[tail_start..].to_vec();
353 for i in 0..tail_len {
354 buf[tail_start + i] = tail[perm[i]];
355 }
356 }
357}
358
359fn block_shuffle_reverse(buf: &mut [u8], ks: &KeySchedule) {
360 let n = buf.len();
361 let full_blocks = n / BLOCK;
362 for bi in 0..full_blocks {
363 let perm = ks.block_perm(bi);
364 let base = bi * BLOCK;
365 let block: [u8; BLOCK] = std::array::from_fn(|i| buf[base + i]);
366 let mut orig = [0u8; BLOCK];
367 for i in 0..BLOCK {
368 orig[perm[i]] = block[i];
369 }
370 buf[base..base + BLOCK].copy_from_slice(&orig);
371 }
372 let tail_start = full_blocks * BLOCK;
374 let tail_len = n - tail_start;
375 if tail_len > 1 {
376 let perm = ks.block_perm_len(full_blocks, tail_len);
377 let tail: Vec<u8> = buf[tail_start..].to_vec();
378 let mut orig = vec![0u8; tail_len];
379 for i in 0..tail_len {
380 orig[perm[i]] = tail[i];
381 }
382 buf[tail_start..].copy_from_slice(&orig);
383 }
384}
385
386#[cfg(test)]
389mod tests {
390 use super::*;
391 use bincode::{Encode, Decode};
392
393 #[derive(Encode, Decode, Debug, PartialEq)]
394 struct Point { x: f64, y: f64, label: String }
395
396 #[derive(Encode, Decode, Debug, PartialEq)]
397 struct Nested { id: u64, inner: Point, tags: Vec<String> }
398
399 #[test]
400 fn round_trip_default_key() {
401 let p = Point { x: 1.5, y: -3.0, label: "origin".into() };
402 let blob = seal(&p, None).unwrap();
403 let back: Point = open(&blob, None).unwrap();
404 assert_eq!(p, back);
405 }
406
407
408 #[test]
409 fn round_trip_custom_key() {
410 let p = Point { x: 42.0, y: 0.001, label: "custom".into() };
411 let blob = seal(&p, Some("hunter2".to_string())).unwrap();
412 let back: Point = open(&blob, Some("hunter2".to_string())).unwrap();
413 assert_eq!(p, back);
414 }
415
416 #[test]
417 fn round_trip_nested() {
418 let n = Nested {
419 id: 9999,
420 inner: Point { x: -1.0, y: 2.5, label: "nested".into() },
421 tags: vec!["a".into(), "bb".into(), "ccc".into()],
422 };
423 let blob = seal(&n, Some("nested-key".to_string())).unwrap();
424 let back: Nested = open(&blob, Some("nested-key".to_string())).unwrap();
425 assert_eq!(n, back);
426 }
427
428 #[test]
429 fn wrong_key_fails() {
430 let p = Point { x: 1.0, y: 2.0, label: "x".into() };
431 let blob = seal(&p, Some("correct".to_string())).unwrap();
432 let result: Result<Point, _> = open(&blob, Some("wrong".to_string()));
435 assert!(result.is_err());
436 }
437
438 #[test]
439 fn ciphertext_differs_from_plaintext() {
440 let p = Point { x: 0.0, y: 0.0, label: "zero".into() };
441 let plain = bincode::encode_to_vec(&p, bincode::config::standard()).unwrap();
442 let blob = seal(&p, None).unwrap();
443 assert_ne!(blob, plain);
445 }
446
447 #[test]
448 fn same_plaintext_same_key_produces_same_ciphertext() {
449 let p = Point { x: 1.0, y: 2.0, label: "det".into() };
450 let b1 = seal(&p, Some("k".to_string())).unwrap();
451 let b2 = seal(&p, Some("k".to_string())).unwrap();
452 assert_eq!(b1, b2); }
454}