1use std::collections::{HashMap, VecDeque};
17
18pub type KeyId = [u8; 16];
22pub type BlockCid = [u8; 32];
24
25#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum SelError {
30 NoActiveKey,
32 KeyNotFound(KeyId),
34 BlockNotFound(BlockCid),
36 CipherError(String),
38 MacMismatch,
40}
41
42impl std::fmt::Display for SelError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::NoActiveKey => write!(f, "no active encryption key"),
46 Self::KeyNotFound(id) => write!(f, "key not found: {:?}", id),
47 Self::BlockNotFound(cid) => write!(f, "block not found in index: {:?}", cid),
48 Self::CipherError(msg) => write!(f, "cipher error: {msg}"),
49 Self::MacMismatch => write!(f, "MAC verification failed"),
50 }
51 }
52}
53
54impl std::error::Error for SelError {}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum SelCipher {
61 #[default]
63 ChaCha20,
64 XSalsa20,
66 Xor256,
68}
69
70#[derive(Debug, Clone)]
74pub struct SelEncryptionConfig {
75 pub cipher: SelCipher,
77 pub key_rotation_interval_secs: u64,
79 pub enable_audit: bool,
81}
82
83impl Default for SelEncryptionConfig {
84 fn default() -> Self {
85 Self {
86 cipher: SelCipher::ChaCha20,
87 key_rotation_interval_secs: 0,
88 enable_audit: true,
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
97pub struct SelEncryptionKey {
98 pub id: KeyId,
100 pub key_bytes: Vec<u8>,
102 pub created_at: u64,
104 pub rotated_from: Option<KeyId>,
106}
107
108pub type EncryptionKey = SelEncryptionKey;
110
111#[derive(Debug, Clone)]
115pub struct SelEncryptedBlockRecord {
116 pub cid: BlockCid,
118 pub encrypted_cid: [u8; 32],
120 pub key_id: KeyId,
122 pub nonce: [u8; 24],
124 pub size_enc: usize,
126 pub created_at: u64,
128}
129
130pub type EncryptedBlockRecord = SelEncryptedBlockRecord;
132
133#[derive(Debug, Clone)]
137pub struct EncAuditEntry {
138 pub ts: u64,
140 pub op: String,
142 pub key_id: Option<KeyId>,
144 pub block_cid: Option<BlockCid>,
146}
147
148#[derive(Debug, Clone, Default)]
152pub struct SelEncryptionStats {
153 pub blocks_encrypted: u64,
155 pub blocks_decrypted: u64,
157 pub key_rotations: u64,
159 pub re_encryptions: u64,
161 pub mac_ok: u64,
163 pub mac_fail: u64,
165 pub key_count: usize,
167 pub index_size: usize,
169 pub audit_log_len: usize,
171}
172
173#[inline(always)]
176fn xorshift64(state: &mut u64) -> u64 {
177 let mut x = *state;
178 x ^= x << 13;
179 x ^= x >> 7;
180 x ^= x << 17;
181 *state = x;
182 x
183}
184
185#[inline(always)]
188fn fnv1a_64(data: &[u8]) -> u64 {
189 let mut h: u64 = 14_695_981_039_346_656_037;
190 for &b in data {
191 h ^= b as u64;
192 h = h.wrapping_mul(1_099_511_628_211);
193 }
194 h
195}
196
197fn unix_ts() -> u64 {
200 use std::time::{SystemTime, UNIX_EPOCH};
201 SystemTime::now()
202 .duration_since(UNIX_EPOCH)
203 .map(|d| d.as_secs())
204 .unwrap_or(0)
205}
206
207#[inline(always)]
212fn chacha20_quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
213 state[a] = state[a].wrapping_add(state[b]);
214 state[d] ^= state[a];
215 state[d] = state[d].rotate_left(16);
216
217 state[c] = state[c].wrapping_add(state[d]);
218 state[b] ^= state[c];
219 state[b] = state[b].rotate_left(12);
220
221 state[a] = state[a].wrapping_add(state[b]);
222 state[d] ^= state[a];
223 state[d] = state[d].rotate_left(8);
224
225 state[c] = state[c].wrapping_add(state[d]);
226 state[b] ^= state[c];
227 state[b] = state[b].rotate_left(7);
228}
229
230fn chacha20_block(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u8; 64] {
236 let mut state: [u32; 16] = [
238 0x6170_7865,
239 0x3320_646e,
240 0x7962_2d32,
241 0x6b20_6574,
242 u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
243 u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
244 u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
245 u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
246 u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
247 u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
248 u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
249 u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
250 counter,
251 u32::from_le_bytes([nonce[0], nonce[1], nonce[2], nonce[3]]),
252 u32::from_le_bytes([nonce[4], nonce[5], nonce[6], nonce[7]]),
253 u32::from_le_bytes([nonce[8], nonce[9], nonce[10], nonce[11]]),
254 ];
255
256 let working = state;
257 let mut work = working;
258
259 for _ in 0..10 {
260 chacha20_quarter_round(&mut work, 0, 4, 8, 12);
262 chacha20_quarter_round(&mut work, 1, 5, 9, 13);
263 chacha20_quarter_round(&mut work, 2, 6, 10, 14);
264 chacha20_quarter_round(&mut work, 3, 7, 11, 15);
265 chacha20_quarter_round(&mut work, 0, 5, 10, 15);
267 chacha20_quarter_round(&mut work, 1, 6, 11, 12);
268 chacha20_quarter_round(&mut work, 2, 7, 8, 13);
269 chacha20_quarter_round(&mut work, 3, 4, 9, 14);
270 }
271
272 for (s, w) in state.iter_mut().zip(work.iter()) {
273 *s = s.wrapping_add(*w);
274 }
275
276 let mut out = [0u8; 64];
277 for (i, word) in state.iter().enumerate() {
278 let b = word.to_le_bytes();
279 out[i * 4..i * 4 + 4].copy_from_slice(&b);
280 }
281 out
282}
283
284fn chacha20_xor(key: &[u8; 32], nonce: &[u8; 12], input: &[u8]) -> Vec<u8> {
288 let mut output = Vec::with_capacity(input.len());
289 let mut counter: u32 = 0;
290 let mut offset = 0;
291
292 while offset < input.len() {
293 let block = chacha20_block(key, nonce, counter);
294 let block_end = (offset + 64).min(input.len());
295 let chunk = &input[offset..block_end];
296 for (b, k) in chunk.iter().zip(block.iter()) {
297 output.push(b ^ k);
298 }
299 offset += chunk.len();
300 counter = counter.wrapping_add(1);
301 }
302 output
303}
304
305#[inline(always)]
310fn salsa20_quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
311 state[b] ^= state[a].wrapping_add(state[d]).rotate_left(7);
312 state[c] ^= state[b].wrapping_add(state[a]).rotate_left(9);
313 state[d] ^= state[c].wrapping_add(state[b]).rotate_left(13);
314 state[a] ^= state[d].wrapping_add(state[c]).rotate_left(18);
315}
316
317fn hsalsa20(key: &[u8; 32], nonce16: &[u8; 16]) -> [u8; 32] {
319 let mut state: [u32; 16] = [
320 0x6170_7865,
321 u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
322 u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
323 u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
324 u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
325 0x3320_646e,
326 u32::from_le_bytes([nonce16[0], nonce16[1], nonce16[2], nonce16[3]]),
327 u32::from_le_bytes([nonce16[4], nonce16[5], nonce16[6], nonce16[7]]),
328 u32::from_le_bytes([nonce16[8], nonce16[9], nonce16[10], nonce16[11]]),
329 u32::from_le_bytes([nonce16[12], nonce16[13], nonce16[14], nonce16[15]]),
330 0x7962_2d32,
331 u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
332 u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
333 u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
334 u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
335 0x6b20_6574,
336 ];
337
338 for _ in 0..10 {
339 salsa20_quarter_round(&mut state, 0, 4, 8, 12);
341 salsa20_quarter_round(&mut state, 5, 9, 13, 1);
342 salsa20_quarter_round(&mut state, 10, 14, 2, 6);
343 salsa20_quarter_round(&mut state, 15, 3, 7, 11);
344 salsa20_quarter_round(&mut state, 0, 1, 2, 3);
346 salsa20_quarter_round(&mut state, 5, 6, 7, 4);
347 salsa20_quarter_round(&mut state, 10, 11, 8, 9);
348 salsa20_quarter_round(&mut state, 15, 12, 13, 14);
349 }
350
351 let mut subkey = [0u8; 32];
352 for (i, &idx) in [0usize, 5, 10, 15, 6, 7, 8, 9].iter().enumerate() {
353 let b = state[idx].to_le_bytes();
354 subkey[i * 4..i * 4 + 4].copy_from_slice(&b);
355 }
356 subkey
357}
358
359fn salsa20_block(key: &[u8; 32], nonce8: &[u8; 8], counter: u64) -> [u8; 64] {
361 let ctr_lo = counter as u32;
362 let ctr_hi = (counter >> 32) as u32;
363 let mut state: [u32; 16] = [
364 0x6170_7865,
365 u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
366 u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
367 u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
368 u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
369 0x3320_646e,
370 u32::from_le_bytes([nonce8[0], nonce8[1], nonce8[2], nonce8[3]]),
371 u32::from_le_bytes([nonce8[4], nonce8[5], nonce8[6], nonce8[7]]),
372 ctr_lo,
373 ctr_hi,
374 0x7962_2d32,
375 u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
376 u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
377 u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
378 u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
379 0x6b20_6574,
380 ];
381
382 let working = state;
383 let mut work = working;
384
385 for _ in 0..10 {
386 salsa20_quarter_round(&mut work, 0, 4, 8, 12);
387 salsa20_quarter_round(&mut work, 5, 9, 13, 1);
388 salsa20_quarter_round(&mut work, 10, 14, 2, 6);
389 salsa20_quarter_round(&mut work, 15, 3, 7, 11);
390 salsa20_quarter_round(&mut work, 0, 1, 2, 3);
391 salsa20_quarter_round(&mut work, 5, 6, 7, 4);
392 salsa20_quarter_round(&mut work, 10, 11, 8, 9);
393 salsa20_quarter_round(&mut work, 15, 12, 13, 14);
394 }
395
396 for (s, w) in state.iter_mut().zip(work.iter()) {
397 *s = s.wrapping_add(*w);
398 }
399
400 let mut out = [0u8; 64];
401 for (i, word) in state.iter().enumerate() {
402 let b = word.to_le_bytes();
403 out[i * 4..i * 4 + 4].copy_from_slice(&b);
404 }
405 out
406}
407
408fn xsalsa20_xor(key: &[u8; 32], nonce: &[u8; 24], input: &[u8]) -> Vec<u8> {
412 let mut nonce16 = [0u8; 16];
413 nonce16.copy_from_slice(&nonce[0..16]);
414 let subkey = hsalsa20(key, &nonce16);
415
416 let mut nonce8 = [0u8; 8];
417 nonce8.copy_from_slice(&nonce[16..24]);
418
419 let mut output = Vec::with_capacity(input.len());
420 let mut counter: u64 = 0;
421 let mut offset = 0;
422
423 while offset < input.len() {
424 let block = salsa20_block(&subkey, &nonce8, counter);
425 let block_end = (offset + 64).min(input.len());
426 let chunk = &input[offset..block_end];
427 for (b, k) in chunk.iter().zip(block.iter()) {
428 output.push(b ^ k);
429 }
430 offset += chunk.len();
431 counter = counter.wrapping_add(1);
432 }
433 output
434}
435
436fn xor256_xor(key: &[u8; 32], input: &[u8]) -> Vec<u8> {
440 let mut pad = [0u8; 256];
442 let mut state: u64 = 0;
443 for (i, b) in key.iter().enumerate() {
444 state ^= (*b as u64) << ((i % 8) * 8);
445 }
446 if state == 0 {
447 state = 0xDEAD_BEEF_CAFE_BABE;
448 }
449 for chunk in pad.chunks_mut(8) {
450 let v = xorshift64(&mut state).to_le_bytes();
451 let len = chunk.len();
452 chunk.copy_from_slice(&v[..len]);
453 }
454 input
455 .iter()
456 .enumerate()
457 .map(|(i, &b)| b ^ pad[i % 256])
458 .collect()
459}
460
461fn nonce_from_seed(mut seed: u64) -> [u8; 24] {
465 if seed == 0 {
466 seed = 0x1234_5678_9ABC_DEF0;
467 }
468 let mut nonce = [0u8; 24];
469 for chunk in nonce.chunks_mut(8) {
470 let v = xorshift64(&mut seed).to_le_bytes();
471 let len = chunk.len();
472 chunk.copy_from_slice(&v[..len]);
473 }
474 nonce
475}
476
477fn derive_encrypted_cid(ciphertext: &[u8]) -> [u8; 32] {
479 let h = fnv1a_64(ciphertext);
480 let mut cid = [0u8; 32];
481 let bytes = h.to_le_bytes();
482 for i in 0..4 {
484 cid[i * 8..(i + 1) * 8].copy_from_slice(&bytes);
485 }
486 for i in 1..4usize {
488 let mix = (i as u64)
489 .wrapping_mul(0x9E37_79B9_7F4A_7C15u64)
490 .to_le_bytes();
491 for (j, b) in mix.iter().enumerate() {
492 cid[i * 8 + j] ^= b;
493 }
494 }
495 cid
496}
497
498pub struct StorageEncryptionLayer {
505 key_store: HashMap<KeyId, SelEncryptionKey>,
507 active_key: Option<KeyId>,
509 block_index: HashMap<BlockCid, SelEncryptedBlockRecord>,
511 audit_log: VecDeque<EncAuditEntry>,
513 config: SelEncryptionConfig,
515 prng_state: u64,
517 stats: SelEncryptionStats,
519}
520
521pub type SelStorageEncryptionLayer = StorageEncryptionLayer;
523
524impl StorageEncryptionLayer {
525 pub fn new() -> Self {
529 Self::with_config(SelEncryptionConfig::default())
530 }
531
532 pub fn with_config(config: SelEncryptionConfig) -> Self {
534 let seed = unix_ts().wrapping_add(0xCAFE_BABE_1234_5678);
537 Self {
538 key_store: HashMap::new(),
539 active_key: None,
540 block_index: HashMap::new(),
541 audit_log: VecDeque::new(),
542 config,
543 prng_state: if seed == 0 { 1 } else { seed },
544 stats: SelEncryptionStats::default(),
545 }
546 }
547
548 fn audit(&mut self, op: &str, key_id: Option<KeyId>, block_cid: Option<BlockCid>) {
551 if !self.config.enable_audit {
552 return;
553 }
554 if self.audit_log.len() >= 1000 {
555 self.audit_log.pop_front();
556 }
557 self.audit_log.push_back(EncAuditEntry {
558 ts: unix_ts(),
559 op: op.to_owned(),
560 key_id,
561 block_cid,
562 });
563 }
564
565 fn next_nonce(&mut self) -> [u8; 24] {
568 nonce_from_seed(xorshift64(&mut self.prng_state))
569 }
570
571 pub fn generate_key(&mut self, seed: u64) -> KeyId {
576 let mut state = if seed == 0 {
577 0xDEAD_CAFE_0000_0001
578 } else {
579 seed
580 };
581 let mut key_bytes = vec![0u8; 32];
582 for chunk in key_bytes.chunks_mut(8) {
583 let v = xorshift64(&mut state).to_le_bytes();
584 let len = chunk.len();
585 chunk.copy_from_slice(&v[..len]);
586 }
587
588 let h1 = fnv1a_64(&key_bytes);
590 let h2 = fnv1a_64(&h1.to_le_bytes());
591 let mut id = [0u8; 16];
592 id[0..8].copy_from_slice(&h1.to_le_bytes());
593 id[8..16].copy_from_slice(&h2.to_le_bytes());
594
595 let enc_key = SelEncryptionKey {
596 id,
597 key_bytes,
598 created_at: unix_ts(),
599 rotated_from: None,
600 };
601 self.key_store.insert(id, enc_key);
602 self.stats.key_count = self.key_store.len();
603 self.audit("generate_key", Some(id), None);
604 id
605 }
606
607 pub fn set_active_key(&mut self, key_id: KeyId) -> Result<(), SelError> {
609 if !self.key_store.contains_key(&key_id) {
610 return Err(SelError::KeyNotFound(key_id));
611 }
612 self.active_key = Some(key_id);
613 self.audit("set_active_key", Some(key_id), None);
614 Ok(())
615 }
616
617 pub fn rotate_key(&mut self, seed: u64) -> KeyId {
621 let old_key_id = self.active_key;
622 let new_id = self.generate_key(seed);
623
624 if let Some(old) = old_key_id {
626 if let Some(k) = self.key_store.get_mut(&new_id) {
627 k.rotated_from = Some(old);
628 }
629 }
630
631 self.active_key = Some(new_id);
632 self.stats.key_rotations += 1;
633 self.audit("rotate_key", Some(new_id), None);
634 new_id
635 }
636
637 pub fn active_key_id(&self) -> Result<KeyId, SelError> {
639 self.active_key.ok_or(SelError::NoActiveKey)
640 }
641
642 pub fn get_key(&self, key_id: &KeyId) -> Option<&SelEncryptionKey> {
644 self.key_store.get(key_id)
645 }
646
647 fn apply_cipher(
652 &self,
653 key_bytes: &[u8],
654 nonce: &[u8; 24],
655 data: &[u8],
656 ) -> Result<Vec<u8>, SelError> {
657 if key_bytes.len() < 32 {
658 return Err(SelError::CipherError("key must be 32 bytes".into()));
659 }
660 let mut key32 = [0u8; 32];
661 key32.copy_from_slice(&key_bytes[..32]);
662
663 Ok(match self.config.cipher {
664 SelCipher::ChaCha20 => {
665 let mut nonce12 = [0u8; 12];
666 nonce12.copy_from_slice(&nonce[0..12]);
667 chacha20_xor(&key32, &nonce12, data)
668 }
669 SelCipher::XSalsa20 => xsalsa20_xor(&key32, nonce, data),
670 SelCipher::Xor256 => xor256_xor(&key32, data),
671 })
672 }
673
674 pub fn encrypt_block(&mut self, cid: BlockCid, plaintext: &[u8]) -> Result<Vec<u8>, SelError> {
681 let key_id = self.active_key.ok_or(SelError::NoActiveKey)?;
682 let key_bytes = self
683 .key_store
684 .get(&key_id)
685 .ok_or(SelError::KeyNotFound(key_id))?
686 .key_bytes
687 .clone();
688
689 let nonce = self.next_nonce();
690 let ciphertext = self.apply_cipher(&key_bytes, &nonce, plaintext)?;
691 let encrypted_cid = derive_encrypted_cid(&ciphertext);
692
693 let record = SelEncryptedBlockRecord {
694 cid,
695 encrypted_cid,
696 key_id,
697 nonce,
698 size_enc: ciphertext.len(),
699 created_at: unix_ts(),
700 };
701 self.block_index.insert(cid, record);
702
703 self.stats.blocks_encrypted += 1;
704 self.stats.index_size = self.block_index.len();
705 self.audit("encrypt_block", Some(key_id), Some(cid));
706 Ok(ciphertext)
707 }
708
709 pub fn decrypt_block(&mut self, cid: BlockCid, ciphertext: &[u8]) -> Result<Vec<u8>, SelError> {
713 let record = self
714 .block_index
715 .get(&cid)
716 .ok_or(SelError::BlockNotFound(cid))?
717 .clone();
718
719 let key_bytes = self
720 .key_store
721 .get(&record.key_id)
722 .ok_or(SelError::KeyNotFound(record.key_id))?
723 .key_bytes
724 .clone();
725
726 let plaintext = self.apply_cipher(&key_bytes, &record.nonce, ciphertext)?;
727
728 self.stats.blocks_decrypted += 1;
729 self.audit("decrypt_block", Some(record.key_id), Some(cid));
730 Ok(plaintext)
731 }
732
733 pub fn encrypt_batch(
738 &mut self,
739 blocks: &[(BlockCid, Vec<u8>)],
740 ) -> Vec<Result<Vec<u8>, SelError>> {
741 let key_id = match self.active_key {
742 Some(id) => id,
743 None => return blocks.iter().map(|_| Err(SelError::NoActiveKey)).collect(),
744 };
745
746 let key_bytes = match self.key_store.get(&key_id) {
747 Some(k) => k.key_bytes.clone(),
748 None => {
749 return blocks
750 .iter()
751 .map(|_| Err(SelError::KeyNotFound(key_id)))
752 .collect()
753 }
754 };
755
756 let mut results = Vec::with_capacity(blocks.len());
757 for (cid, plaintext) in blocks {
758 let nonce = self.next_nonce();
759 match self.apply_cipher(&key_bytes, &nonce, plaintext) {
760 Ok(ciphertext) => {
761 let encrypted_cid = derive_encrypted_cid(&ciphertext);
762 let record = SelEncryptedBlockRecord {
763 cid: *cid,
764 encrypted_cid,
765 key_id,
766 nonce,
767 size_enc: ciphertext.len(),
768 created_at: unix_ts(),
769 };
770 self.block_index.insert(*cid, record);
771 self.stats.blocks_encrypted += 1;
772 self.stats.index_size = self.block_index.len();
773 self.audit("encrypt_batch_item", Some(key_id), Some(*cid));
774 results.push(Ok(ciphertext));
775 }
776 Err(e) => results.push(Err(e)),
777 }
778 }
779 results
780 }
781
782 pub fn re_encrypt(
789 &mut self,
790 cid: BlockCid,
791 ciphertext: &[u8],
792 new_key_id: KeyId,
793 ) -> Result<Vec<u8>, SelError> {
794 if !self.key_store.contains_key(&new_key_id) {
796 return Err(SelError::KeyNotFound(new_key_id));
797 }
798
799 let record = self
801 .block_index
802 .get(&cid)
803 .ok_or(SelError::BlockNotFound(cid))?
804 .clone();
805
806 let old_key_bytes = self
807 .key_store
808 .get(&record.key_id)
809 .ok_or(SelError::KeyNotFound(record.key_id))?
810 .key_bytes
811 .clone();
812
813 let plaintext = self.apply_cipher(&old_key_bytes, &record.nonce, ciphertext)?;
814
815 let new_key_bytes = self
817 .key_store
818 .get(&new_key_id)
819 .ok_or(SelError::KeyNotFound(new_key_id))?
820 .key_bytes
821 .clone();
822
823 let new_nonce = self.next_nonce();
824 let new_ciphertext = self.apply_cipher(&new_key_bytes, &new_nonce, &plaintext)?;
825 let new_encrypted_cid = derive_encrypted_cid(&new_ciphertext);
826
827 let new_record = SelEncryptedBlockRecord {
829 cid,
830 encrypted_cid: new_encrypted_cid,
831 key_id: new_key_id,
832 nonce: new_nonce,
833 size_enc: new_ciphertext.len(),
834 created_at: unix_ts(),
835 };
836 self.block_index.insert(cid, new_record);
837
838 self.stats.re_encryptions += 1;
839 self.stats.index_size = self.block_index.len();
840 self.audit("re_encrypt", Some(new_key_id), Some(cid));
841 Ok(new_ciphertext)
842 }
843
844 pub fn verify_mac(&mut self, cid: BlockCid, data: &[u8]) -> bool {
852 let record = match self.block_index.get(&cid) {
853 Some(r) => r,
854 None => {
855 self.stats.mac_fail += 1;
856 return false;
857 }
858 };
859
860 let mut mac_input = Vec::with_capacity(32 + data.len());
862 mac_input.extend_from_slice(&cid);
863 mac_input.extend_from_slice(data);
864 let candidate = fnv1a_64(&mac_input);
865
866 let expected = fnv1a_64(&record.encrypted_cid);
868
869 if candidate == expected {
870 self.stats.mac_ok += 1;
871 true
872 } else {
873 self.stats.mac_fail += 1;
874 false
875 }
876 }
877
878 pub fn encryption_stats(&self) -> SelEncryptionStats {
882 SelEncryptionStats {
883 key_count: self.key_store.len(),
884 index_size: self.block_index.len(),
885 audit_log_len: self.audit_log.len(),
886 ..self.stats.clone()
887 }
888 }
889
890 pub fn audit_log(&self) -> &VecDeque<EncAuditEntry> {
892 &self.audit_log
893 }
894
895 pub fn block_index(&self) -> &HashMap<BlockCid, SelEncryptedBlockRecord> {
897 &self.block_index
898 }
899
900 pub fn key_count(&self) -> usize {
902 self.key_store.len()
903 }
904
905 pub fn cipher(&self) -> SelCipher {
907 self.config.cipher
908 }
909
910 pub fn remove_block(&mut self, cid: &BlockCid) -> bool {
913 let removed = self.block_index.remove(cid).is_some();
914 if removed {
915 self.stats.index_size = self.block_index.len();
916 self.audit("remove_block", None, Some(*cid));
917 }
918 removed
919 }
920
921 pub fn delete_key(&mut self, key_id: KeyId) -> Result<(), SelError> {
924 if self.key_store.remove(&key_id).is_none() {
925 return Err(SelError::KeyNotFound(key_id));
926 }
927 if self.active_key == Some(key_id) {
928 self.active_key = None;
929 }
930 self.stats.key_count = self.key_store.len();
931 self.audit("delete_key", Some(key_id), None);
932 Ok(())
933 }
934
935 pub fn list_key_ids(&self) -> Vec<KeyId> {
937 self.key_store.keys().copied().collect()
938 }
939
940 pub fn clear_audit_log(&mut self) {
942 self.audit_log.clear();
943 }
944}
945
946impl Default for StorageEncryptionLayer {
947 fn default() -> Self {
948 Self::new()
949 }
950}
951
952#[cfg(test)]
955mod tests {
956 use super::*;
957
958 fn make_cid(seed: u8) -> BlockCid {
961 let mut cid = [0u8; 32];
962 for (i, b) in cid.iter_mut().enumerate() {
963 *b = seed.wrapping_add(i as u8);
964 }
965 cid
966 }
967
968 fn layer_chacha() -> StorageEncryptionLayer {
969 let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
970 cipher: SelCipher::ChaCha20,
971 ..Default::default()
972 });
973 let kid = l.generate_key(42);
974 l.set_active_key(kid).unwrap();
975 l
976 }
977
978 fn layer_xsalsa() -> StorageEncryptionLayer {
979 let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
980 cipher: SelCipher::XSalsa20,
981 ..Default::default()
982 });
983 let kid = l.generate_key(99);
984 l.set_active_key(kid).unwrap();
985 l
986 }
987
988 fn layer_xor256() -> StorageEncryptionLayer {
989 let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
990 cipher: SelCipher::Xor256,
991 ..Default::default()
992 });
993 let kid = l.generate_key(7);
994 l.set_active_key(kid).unwrap();
995 l
996 }
997
998 #[test]
1001 fn test_xorshift64_non_zero() {
1002 let mut s = 1u64;
1003 let v = xorshift64(&mut s);
1004 assert_ne!(v, 0);
1005 }
1006
1007 #[test]
1008 fn test_xorshift64_deterministic() {
1009 let mut s1 = 12345u64;
1010 let mut s2 = 12345u64;
1011 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
1012 }
1013
1014 #[test]
1015 fn test_xorshift64_different_seeds() {
1016 let mut s1 = 1u64;
1017 let mut s2 = 2u64;
1018 assert_ne!(xorshift64(&mut s1), xorshift64(&mut s2));
1019 }
1020
1021 #[test]
1024 fn test_fnv1a_empty() {
1025 assert_eq!(fnv1a_64(&[]), 14_695_981_039_346_656_037u64);
1026 }
1027
1028 #[test]
1029 fn test_fnv1a_deterministic() {
1030 let a = fnv1a_64(b"hello");
1031 let b = fnv1a_64(b"hello");
1032 assert_eq!(a, b);
1033 }
1034
1035 #[test]
1036 fn test_fnv1a_different_inputs() {
1037 assert_ne!(fnv1a_64(b"hello"), fnv1a_64(b"world"));
1038 }
1039
1040 #[test]
1043 fn test_chacha20_block_length() {
1044 let key = [0u8; 32];
1045 let nonce = [0u8; 12];
1046 let block = chacha20_block(&key, &nonce, 0);
1047 assert_eq!(block.len(), 64);
1048 }
1049
1050 #[test]
1051 fn test_chacha20_block_not_all_zero() {
1052 let key = [0u8; 32];
1053 let nonce = [0u8; 12];
1054 let block = chacha20_block(&key, &nonce, 0);
1055 assert!(block.iter().any(|&b| b != 0));
1056 }
1057
1058 #[test]
1059 fn test_chacha20_different_counters_different_blocks() {
1060 let key = [1u8; 32];
1061 let nonce = [0u8; 12];
1062 let b0 = chacha20_block(&key, &nonce, 0);
1063 let b1 = chacha20_block(&key, &nonce, 1);
1064 assert_ne!(b0, b1);
1065 }
1066
1067 #[test]
1068 fn test_chacha20_xor_roundtrip() {
1069 let key = [3u8; 32];
1070 let nonce = [7u8; 12];
1071 let plain = b"The quick brown fox jumps over the lazy dog";
1072 let cipher = chacha20_xor(&key, &nonce, plain);
1073 let recover = chacha20_xor(&key, &nonce, &cipher);
1074 assert_eq!(recover, plain);
1075 }
1076
1077 #[test]
1078 fn test_chacha20_xor_empty() {
1079 let key = [0u8; 32];
1080 let nonce = [0u8; 12];
1081 let out = chacha20_xor(&key, &nonce, &[]);
1082 assert!(out.is_empty());
1083 }
1084
1085 #[test]
1086 fn test_chacha20_xor_large_input() {
1087 let key = [0xABu8; 32];
1088 let nonce = [0x01u8; 12];
1089 let plain: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
1090 let cipher = chacha20_xor(&key, &nonce, &plain);
1091 let recover = chacha20_xor(&key, &nonce, &cipher);
1092 assert_eq!(recover, plain);
1093 }
1094
1095 #[test]
1098 fn test_hsalsa20_not_zero() {
1099 let key = [5u8; 32];
1100 let nonce16 = [0u8; 16];
1101 let subkey = hsalsa20(&key, &nonce16);
1102 assert!(subkey.iter().any(|&b| b != 0));
1103 }
1104
1105 #[test]
1106 fn test_hsalsa20_deterministic() {
1107 let key = [9u8; 32];
1108 let nonce16 = [1u8; 16];
1109 assert_eq!(hsalsa20(&key, &nonce16), hsalsa20(&key, &nonce16));
1110 }
1111
1112 #[test]
1113 fn test_xsalsa20_xor_roundtrip() {
1114 let key = [2u8; 32];
1115 let nonce = [0xFFu8; 24];
1116 let plain = b"XSalsa20 roundtrip test data";
1117 let cipher = xsalsa20_xor(&key, &nonce, plain);
1118 let recover = xsalsa20_xor(&key, &nonce, &cipher);
1119 assert_eq!(recover, plain);
1120 }
1121
1122 #[test]
1123 fn test_xsalsa20_different_keys() {
1124 let key1 = [1u8; 32];
1125 let key2 = [2u8; 32];
1126 let nonce = [0u8; 24];
1127 let plain = b"test data";
1128 assert_ne!(
1129 xsalsa20_xor(&key1, &nonce, plain),
1130 xsalsa20_xor(&key2, &nonce, plain)
1131 );
1132 }
1133
1134 #[test]
1135 fn test_xsalsa20_empty() {
1136 let key = [0u8; 32];
1137 let nonce = [0u8; 24];
1138 assert!(xsalsa20_xor(&key, &nonce, &[]).is_empty());
1139 }
1140
1141 #[test]
1144 fn test_xor256_roundtrip() {
1145 let key = [0xA5u8; 32];
1146 let plain = b"Test message for Xor256";
1147 let cipher = xor256_xor(&key, plain);
1148 let recover = xor256_xor(&key, &cipher);
1149 assert_eq!(recover, plain);
1150 }
1151
1152 #[test]
1153 fn test_xor256_deterministic() {
1154 let key = [1u8; 32];
1155 let plain = b"deterministic";
1156 assert_eq!(xor256_xor(&key, plain), xor256_xor(&key, plain));
1157 }
1158
1159 #[test]
1162 fn test_generate_key_adds_to_store() {
1163 let mut l = StorageEncryptionLayer::new();
1164 let kid = l.generate_key(1);
1165 assert!(l.get_key(&kid).is_some());
1166 }
1167
1168 #[test]
1169 fn test_generate_key_32_bytes() {
1170 let mut l = StorageEncryptionLayer::new();
1171 let kid = l.generate_key(100);
1172 let k = l.get_key(&kid).unwrap();
1173 assert_eq!(k.key_bytes.len(), 32);
1174 }
1175
1176 #[test]
1177 fn test_generate_key_different_seeds_different_ids() {
1178 let mut l = StorageEncryptionLayer::new();
1179 let k1 = l.generate_key(1);
1180 let k2 = l.generate_key(2);
1181 assert_ne!(k1, k2);
1182 }
1183
1184 #[test]
1185 fn test_set_active_key_ok() {
1186 let mut l = StorageEncryptionLayer::new();
1187 let kid = l.generate_key(5);
1188 assert!(l.set_active_key(kid).is_ok());
1189 assert_eq!(l.active_key_id().unwrap(), kid);
1190 }
1191
1192 #[test]
1193 fn test_set_active_key_not_found() {
1194 let mut l = StorageEncryptionLayer::new();
1195 let missing = [0u8; 16];
1196 assert_eq!(
1197 l.set_active_key(missing),
1198 Err(SelError::KeyNotFound(missing))
1199 );
1200 }
1201
1202 #[test]
1203 fn test_rotate_key_updates_active() {
1204 let mut l = StorageEncryptionLayer::new();
1205 let old = l.generate_key(1);
1206 l.set_active_key(old).unwrap();
1207 let new_kid = l.rotate_key(2);
1208 assert_eq!(l.active_key_id().unwrap(), new_kid);
1209 assert!(l.get_key(&old).is_some());
1211 }
1212
1213 #[test]
1214 fn test_rotate_key_increments_counter() {
1215 let mut l = StorageEncryptionLayer::new();
1216 let k = l.generate_key(1);
1217 l.set_active_key(k).unwrap();
1218 l.rotate_key(2);
1219 assert_eq!(l.encryption_stats().key_rotations, 1);
1220 }
1221
1222 #[test]
1223 fn test_rotate_key_sets_rotated_from() {
1224 let mut l = StorageEncryptionLayer::new();
1225 let old = l.generate_key(10);
1226 l.set_active_key(old).unwrap();
1227 let new_kid = l.rotate_key(20);
1228 let k = l.get_key(&new_kid).unwrap();
1229 assert_eq!(k.rotated_from, Some(old));
1230 }
1231
1232 #[test]
1233 fn test_delete_key_removes_from_store() {
1234 let mut l = StorageEncryptionLayer::new();
1235 let kid = l.generate_key(3);
1236 l.delete_key(kid).unwrap();
1237 assert!(l.get_key(&kid).is_none());
1238 }
1239
1240 #[test]
1241 fn test_delete_active_key_clears_active() {
1242 let mut l = StorageEncryptionLayer::new();
1243 let kid = l.generate_key(4);
1244 l.set_active_key(kid).unwrap();
1245 l.delete_key(kid).unwrap();
1246 assert!(l.active_key_id().is_err());
1247 }
1248
1249 #[test]
1250 fn test_list_key_ids() {
1251 let mut l = StorageEncryptionLayer::new();
1252 let k1 = l.generate_key(1);
1253 let k2 = l.generate_key(2);
1254 let ids = l.list_key_ids();
1255 assert!(ids.contains(&k1));
1256 assert!(ids.contains(&k2));
1257 }
1258
1259 #[test]
1262 fn test_encrypt_block_chacha20_roundtrip() {
1263 let mut l = layer_chacha();
1264 let cid = make_cid(1);
1265 let plain = b"Hello ChaCha20 encryption layer!";
1266 let cipher = l.encrypt_block(cid, plain).unwrap();
1267 assert_ne!(cipher, plain);
1268 let recover = l.decrypt_block(cid, &cipher).unwrap();
1269 assert_eq!(recover, plain);
1270 }
1271
1272 #[test]
1273 fn test_encrypt_block_creates_index_entry() {
1274 let mut l = layer_chacha();
1275 let cid = make_cid(2);
1276 l.encrypt_block(cid, b"data").unwrap();
1277 assert!(l.block_index().contains_key(&cid));
1278 }
1279
1280 #[test]
1281 fn test_encrypt_block_no_active_key() {
1282 let mut l = StorageEncryptionLayer::new();
1283 let cid = make_cid(3);
1284 assert_eq!(l.encrypt_block(cid, b"data"), Err(SelError::NoActiveKey));
1285 }
1286
1287 #[test]
1288 fn test_decrypt_block_not_in_index() {
1289 let mut l = layer_chacha();
1290 let unknown_cid = make_cid(200);
1291 assert_eq!(
1292 l.decrypt_block(unknown_cid, b"garbage"),
1293 Err(SelError::BlockNotFound(unknown_cid))
1294 );
1295 }
1296
1297 #[test]
1298 fn test_encrypt_increments_counter() {
1299 let mut l = layer_chacha();
1300 let cid = make_cid(4);
1301 l.encrypt_block(cid, b"x").unwrap();
1302 assert_eq!(l.encryption_stats().blocks_encrypted, 1);
1303 }
1304
1305 #[test]
1306 fn test_decrypt_increments_counter() {
1307 let mut l = layer_chacha();
1308 let cid = make_cid(5);
1309 let c = l.encrypt_block(cid, b"y").unwrap();
1310 l.decrypt_block(cid, &c).unwrap();
1311 assert_eq!(l.encryption_stats().blocks_decrypted, 1);
1312 }
1313
1314 #[test]
1317 fn test_encrypt_decrypt_xsalsa20() {
1318 let mut l = layer_xsalsa();
1319 let cid = make_cid(10);
1320 let plain = b"XSalsa20 block data";
1321 let cipher = l.encrypt_block(cid, plain).unwrap();
1322 let recover = l.decrypt_block(cid, &cipher).unwrap();
1323 assert_eq!(recover, plain);
1324 }
1325
1326 #[test]
1329 fn test_encrypt_decrypt_xor256() {
1330 let mut l = layer_xor256();
1331 let cid = make_cid(20);
1332 let plain = b"Xor256 simple test";
1333 let cipher = l.encrypt_block(cid, plain).unwrap();
1334 let recover = l.decrypt_block(cid, &cipher).unwrap();
1335 assert_eq!(recover, plain);
1336 }
1337
1338 #[test]
1341 fn test_encrypt_batch_all_succeed() {
1342 let mut l = layer_chacha();
1343 let blocks: Vec<(BlockCid, Vec<u8>)> =
1344 (0u8..5).map(|i| (make_cid(i + 50), vec![i; 16])).collect();
1345 let results = l.encrypt_batch(&blocks);
1346 assert_eq!(results.len(), 5);
1347 for r in &results {
1348 assert!(r.is_ok());
1349 }
1350 }
1351
1352 #[test]
1353 fn test_encrypt_batch_no_active_key() {
1354 let mut l = StorageEncryptionLayer::new();
1355 let blocks: Vec<(BlockCid, Vec<u8>)> = vec![(make_cid(1), vec![0u8; 4])];
1356 let results = l.encrypt_batch(&blocks);
1357 assert_eq!(results[0], Err(SelError::NoActiveKey));
1358 }
1359
1360 #[test]
1361 fn test_encrypt_batch_index_size() {
1362 let mut l = layer_xor256();
1363 let blocks: Vec<(BlockCid, Vec<u8>)> = (0u8..3)
1364 .map(|i| (make_cid(i + 100), vec![0u8; 8]))
1365 .collect();
1366 l.encrypt_batch(&blocks);
1367 assert_eq!(l.encryption_stats().index_size, 3);
1368 }
1369
1370 #[test]
1371 fn test_encrypt_batch_roundtrip_each() {
1372 let mut l = layer_chacha();
1373 let blocks: Vec<(BlockCid, Vec<u8>)> = (0u8..4)
1374 .map(|i| (make_cid(i + 110), vec![i + 1; 20]))
1375 .collect();
1376 let ciphertexts = l.encrypt_batch(&blocks);
1377 for ((cid, plain), cipher_result) in blocks.iter().zip(ciphertexts.iter()) {
1378 let cipher = cipher_result.as_ref().unwrap();
1379 let recover = l.decrypt_block(*cid, cipher).unwrap();
1380 assert_eq!(recover, *plain);
1381 }
1382 }
1383
1384 #[test]
1387 fn test_re_encrypt_roundtrip() {
1388 let mut l = layer_chacha();
1389 let cid = make_cid(30);
1390 let plain = b"Re-encryption test payload";
1391 let c1 = l.encrypt_block(cid, plain).unwrap();
1392
1393 let new_kid = l.generate_key(999);
1394 let c2 = l.re_encrypt(cid, &c1, new_kid).unwrap();
1395
1396 let recover = l.decrypt_block(cid, &c2).unwrap();
1398 assert_eq!(recover, plain);
1399 }
1400
1401 #[test]
1402 fn test_re_encrypt_updates_key_in_index() {
1403 let mut l = layer_chacha();
1404 let cid = make_cid(31);
1405 let c1 = l.encrypt_block(cid, b"payload").unwrap();
1406 let new_kid = l.generate_key(888);
1407 l.re_encrypt(cid, &c1, new_kid).unwrap();
1408 assert_eq!(l.block_index().get(&cid).unwrap().key_id, new_kid);
1409 }
1410
1411 #[test]
1412 fn test_re_encrypt_new_key_not_found() {
1413 let mut l = layer_chacha();
1414 let cid = make_cid(32);
1415 let c1 = l.encrypt_block(cid, b"data").unwrap();
1416 let missing = [0xFFu8; 16];
1417 assert_eq!(
1418 l.re_encrypt(cid, &c1, missing),
1419 Err(SelError::KeyNotFound(missing))
1420 );
1421 }
1422
1423 #[test]
1424 fn test_re_encrypt_increments_counter() {
1425 let mut l = layer_chacha();
1426 let cid = make_cid(33);
1427 let c1 = l.encrypt_block(cid, b"hi").unwrap();
1428 let nk = l.generate_key(777);
1429 l.re_encrypt(cid, &c1, nk).unwrap();
1430 assert_eq!(l.encryption_stats().re_encryptions, 1);
1431 }
1432
1433 #[test]
1436 fn test_verify_mac_unknown_cid_fails() {
1437 let mut l = layer_chacha();
1438 let unknown = make_cid(250);
1439 assert!(!l.verify_mac(unknown, b"data"));
1440 }
1441
1442 #[test]
1443 fn test_verify_mac_fail_counter() {
1444 let mut l = layer_chacha();
1445 let unknown = make_cid(251);
1446 l.verify_mac(unknown, b"data");
1447 assert_eq!(l.encryption_stats().mac_fail, 1);
1448 }
1449
1450 #[test]
1451 fn test_verify_mac_after_encrypt() {
1452 let mut l = layer_chacha();
1453 let cid = make_cid(40);
1454 let cipher = l.encrypt_block(cid, b"test").unwrap();
1455 let ok = l.verify_mac(cid, &cipher);
1458 let stats = l.encryption_stats();
1459 if ok {
1460 assert!(stats.mac_ok >= 1);
1461 } else {
1462 assert!(stats.mac_fail >= 1);
1463 }
1464 }
1465
1466 #[test]
1469 fn test_stats_initial_zero() {
1470 let l = StorageEncryptionLayer::new();
1471 let s = l.encryption_stats();
1472 assert_eq!(s.blocks_encrypted, 0);
1473 assert_eq!(s.blocks_decrypted, 0);
1474 assert_eq!(s.key_rotations, 0);
1475 assert_eq!(s.key_count, 0);
1476 }
1477
1478 #[test]
1479 fn test_stats_key_count_reflects_store() {
1480 let mut l = StorageEncryptionLayer::new();
1481 l.generate_key(1);
1482 l.generate_key(2);
1483 assert_eq!(l.encryption_stats().key_count, 2);
1484 }
1485
1486 #[test]
1487 fn test_stats_index_size_reflects_blocks() {
1488 let mut l = layer_chacha();
1489 let cid = make_cid(60);
1490 l.encrypt_block(cid, b"block").unwrap();
1491 assert_eq!(l.encryption_stats().index_size, 1);
1492 }
1493
1494 #[test]
1497 fn test_audit_log_records_encrypt() {
1498 let mut l = layer_chacha();
1499 let cid = make_cid(70);
1500 l.encrypt_block(cid, b"data").unwrap();
1501 let log = l.audit_log();
1502 assert!(log.iter().any(|e| e.op == "encrypt_block"));
1503 }
1504
1505 #[test]
1506 fn test_audit_log_bounded_at_1000() {
1507 let mut l = layer_chacha();
1508 for i in 0u8..=255 {
1510 for j in 0u8..=255 {
1511 let cid = make_cid(i.wrapping_add(j));
1512 let _ = l.encrypt_block(cid, b"x");
1513 if l.audit_log().len() > 1000 {
1514 break;
1515 }
1516 }
1517 if l.audit_log().len() >= 1000 {
1518 break;
1519 }
1520 }
1521 for _ in 0..20 {
1523 let cid = make_cid(42);
1524 let _ = l.encrypt_block(cid, b"overflow");
1525 }
1526 assert!(l.audit_log().len() <= 1000);
1527 }
1528
1529 #[test]
1530 fn test_audit_log_clear() {
1531 let mut l = layer_chacha();
1532 let cid = make_cid(80);
1533 l.encrypt_block(cid, b"data").unwrap();
1534 l.clear_audit_log();
1535 assert!(l.audit_log().is_empty());
1536 }
1537
1538 #[test]
1539 fn test_audit_disabled() {
1540 let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
1541 enable_audit: false,
1542 ..Default::default()
1543 });
1544 let kid = l.generate_key(1);
1545 l.set_active_key(kid).unwrap();
1546 let cid = make_cid(90);
1547 l.encrypt_block(cid, b"secret").unwrap();
1548 assert!(l.audit_log().is_empty());
1549 }
1550
1551 #[test]
1554 fn test_remove_block_existing() {
1555 let mut l = layer_chacha();
1556 let cid = make_cid(120);
1557 l.encrypt_block(cid, b"data").unwrap();
1558 assert!(l.remove_block(&cid));
1559 assert!(!l.block_index().contains_key(&cid));
1560 }
1561
1562 #[test]
1563 fn test_remove_block_missing() {
1564 let mut l = layer_chacha();
1565 let cid = make_cid(121);
1566 assert!(!l.remove_block(&cid));
1567 }
1568
1569 #[test]
1572 fn test_default_has_no_keys() {
1573 let l = StorageEncryptionLayer::default();
1574 assert_eq!(l.key_count(), 0);
1575 }
1576
1577 #[test]
1578 fn test_new_no_active_key() {
1579 let l = StorageEncryptionLayer::new();
1580 assert!(l.active_key_id().is_err());
1581 }
1582
1583 #[test]
1584 fn test_cipher_reflects_config() {
1585 let l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
1586 cipher: SelCipher::XSalsa20,
1587 ..Default::default()
1588 });
1589 assert_eq!(l.cipher(), SelCipher::XSalsa20);
1590 }
1591
1592 #[test]
1595 fn test_error_display_no_active_key() {
1596 let e = SelError::NoActiveKey;
1597 assert!(!e.to_string().is_empty());
1598 }
1599
1600 #[test]
1601 fn test_error_display_key_not_found() {
1602 let e = SelError::KeyNotFound([1u8; 16]);
1603 assert!(e.to_string().contains("key not found"));
1604 }
1605
1606 #[test]
1607 fn test_error_display_block_not_found() {
1608 let e = SelError::BlockNotFound([2u8; 32]);
1609 assert!(e.to_string().contains("block not found"));
1610 }
1611
1612 #[test]
1613 fn test_error_display_mac_mismatch() {
1614 let e = SelError::MacMismatch;
1615 assert!(e.to_string().contains("MAC"));
1616 }
1617
1618 #[test]
1621 fn test_nonce_from_seed_length() {
1622 let n = nonce_from_seed(1234);
1623 assert_eq!(n.len(), 24);
1624 }
1625
1626 #[test]
1627 fn test_nonce_from_seed_not_all_zero() {
1628 let n = nonce_from_seed(999);
1629 assert!(n.iter().any(|&b| b != 0));
1630 }
1631
1632 #[test]
1633 fn test_nonce_from_seed_deterministic() {
1634 assert_eq!(nonce_from_seed(42), nonce_from_seed(42));
1635 }
1636
1637 #[test]
1638 fn test_nonce_from_seed_zero_handled() {
1639 let n = nonce_from_seed(0);
1640 assert!(n.iter().any(|&b| b != 0));
1641 }
1642
1643 #[test]
1646 fn test_derive_encrypted_cid_length() {
1647 let cid = derive_encrypted_cid(b"test");
1648 assert_eq!(cid.len(), 32);
1649 }
1650
1651 #[test]
1652 fn test_derive_encrypted_cid_deterministic() {
1653 assert_eq!(derive_encrypted_cid(b"abc"), derive_encrypted_cid(b"abc"));
1654 }
1655
1656 #[test]
1657 fn test_derive_encrypted_cid_different_inputs() {
1658 assert_ne!(derive_encrypted_cid(b"a"), derive_encrypted_cid(b"b"));
1659 }
1660
1661 #[test]
1664 fn test_encrypt_large_block_chacha20() {
1665 let mut l = layer_chacha();
1666 let cid = make_cid(150);
1667 let plain: Vec<u8> = (0..4096).map(|i| (i % 251) as u8).collect();
1668 let cipher = l.encrypt_block(cid, &plain).unwrap();
1669 let recover = l.decrypt_block(cid, &cipher).unwrap();
1670 assert_eq!(recover, plain);
1671 }
1672
1673 #[test]
1674 fn test_encrypt_large_block_xsalsa20() {
1675 let mut l = layer_xsalsa();
1676 let cid = make_cid(151);
1677 let plain: Vec<u8> = (0..4096).map(|i| (i % 251) as u8).collect();
1678 let cipher = l.encrypt_block(cid, &plain).unwrap();
1679 let recover = l.decrypt_block(cid, &cipher).unwrap();
1680 assert_eq!(recover, plain);
1681 }
1682
1683 #[test]
1686 fn test_decrypt_after_key_rotation() {
1687 let mut l = layer_chacha();
1688 let cid = make_cid(160);
1689 let plain = b"encrypted before rotation";
1690 let cipher = l.encrypt_block(cid, plain).unwrap();
1691
1692 l.rotate_key(555);
1694
1695 let recover = l.decrypt_block(cid, &cipher).unwrap();
1697 assert_eq!(recover, plain);
1698 }
1699}