Skip to main content

ipfrs_storage/
storage_encryption_layer.rs

1//! Storage Encryption Layer (`storage_encryption_layer`)
2//!
3//! Production-quality encryption layer for block storage using pure-Rust
4//! stream cipher implementations (ChaCha20, XSalsa20, Xor256).
5//!
6//! Features:
7//! - Inline ChaCha20 (20-round, quarter-round based)
8//! - Inline XSalsa20 (HSalsa20 key derivation + Salsa20)
9//! - Xor256 (256-byte repeating XOR for testing)
10//! - Key store with rotation support
11//! - Encrypted block index for decrypt-by-CID lookup
12//! - Bounded audit log (1000 entries)
13//! - FNV-1a MAC verification
14//! - Batch encrypt / re-encrypt operations
15
16use std::collections::{HashMap, VecDeque};
17
18// ── Type aliases ────────────────────────────────────────────────────────────
19
20/// 16-byte key identifier.
21pub type KeyId = [u8; 16];
22/// 32-byte block content identifier.
23pub type BlockCid = [u8; 32];
24
25// ── Error type ───────────────────────────────────────────────────────────────
26
27/// Errors produced by [`StorageEncryptionLayer`].
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum SelError {
30    /// No active key has been set.
31    NoActiveKey,
32    /// The requested key identifier was not found.
33    KeyNotFound(KeyId),
34    /// The CID was not found in the encrypted block index.
35    BlockNotFound(BlockCid),
36    /// Cipher internal error (e.g. input too large).
37    CipherError(String),
38    /// MAC verification failed.
39    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// ── Cipher selection ─────────────────────────────────────────────────────────
57
58/// Stream cipher variant to use for block encryption.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum SelCipher {
61    /// ChaCha20 (20 rounds, RFC 8439 layout).
62    #[default]
63    ChaCha20,
64    /// XSalsa20 (extended 192-bit nonce).
65    XSalsa20,
66    /// Repeating 256-byte XOR (for unit testing only).
67    Xor256,
68}
69
70// ── Configuration ────────────────────────────────────────────────────────────
71
72/// Configuration for [`StorageEncryptionLayer`].
73#[derive(Debug, Clone)]
74pub struct SelEncryptionConfig {
75    /// Which cipher to use for new encryptions.
76    pub cipher: SelCipher,
77    /// Seconds between automatic key rotations (0 = disabled).
78    pub key_rotation_interval_secs: u64,
79    /// Whether to record operations in the audit log.
80    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// ── Key material ─────────────────────────────────────────────────────────────
94
95/// A symmetric encryption key with metadata.
96#[derive(Debug, Clone)]
97pub struct SelEncryptionKey {
98    /// Key identifier (16 bytes).
99    pub id: KeyId,
100    /// Raw key bytes (32 bytes).
101    pub key_bytes: Vec<u8>,
102    /// UNIX timestamp (seconds) when this key was created.
103    pub created_at: u64,
104    /// Key identifier this was rotated from, if any.
105    pub rotated_from: Option<KeyId>,
106}
107
108/// Public type alias for [`SelEncryptionKey`].
109pub type EncryptionKey = SelEncryptionKey;
110
111// ── Encrypted block record ────────────────────────────────────────────────────
112
113/// Index record for an encrypted block.
114#[derive(Debug, Clone)]
115pub struct SelEncryptedBlockRecord {
116    /// Original plaintext CID.
117    pub cid: BlockCid,
118    /// CID of the encrypted form (FNV-1a derived from ciphertext).
119    pub encrypted_cid: [u8; 32],
120    /// Key identifier used during encryption.
121    pub key_id: KeyId,
122    /// 24-byte nonce used during encryption.
123    pub nonce: [u8; 24],
124    /// Size of the encrypted payload (bytes).
125    pub size_enc: usize,
126    /// UNIX timestamp (seconds) when this record was created.
127    pub created_at: u64,
128}
129
130/// Public type alias for [`SelEncryptedBlockRecord`].
131pub type EncryptedBlockRecord = SelEncryptedBlockRecord;
132
133// ── Audit entry ───────────────────────────────────────────────────────────────
134
135/// A single entry in the bounded audit log.
136#[derive(Debug, Clone)]
137pub struct EncAuditEntry {
138    /// UNIX timestamp (seconds).
139    pub ts: u64,
140    /// Human-readable operation name.
141    pub op: String,
142    /// Key involved, if applicable.
143    pub key_id: Option<KeyId>,
144    /// Block CID involved, if applicable.
145    pub block_cid: Option<BlockCid>,
146}
147
148// ── Statistics ────────────────────────────────────────────────────────────────
149
150/// Aggregate statistics for [`StorageEncryptionLayer`].
151#[derive(Debug, Clone, Default)]
152pub struct SelEncryptionStats {
153    /// Total blocks encrypted since creation.
154    pub blocks_encrypted: u64,
155    /// Total blocks decrypted since creation.
156    pub blocks_decrypted: u64,
157    /// Total key-rotation operations performed.
158    pub key_rotations: u64,
159    /// Total re-encryption operations performed.
160    pub re_encryptions: u64,
161    /// Number of MAC verifications that passed.
162    pub mac_ok: u64,
163    /// Number of MAC verifications that failed.
164    pub mac_fail: u64,
165    /// Number of keys currently in the key store.
166    pub key_count: usize,
167    /// Number of blocks currently in the encrypted block index.
168    pub index_size: usize,
169    /// Number of entries in the audit log.
170    pub audit_log_len: usize,
171}
172
173// ── Inline PRNG ───────────────────────────────────────────────────────────────
174
175#[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 FNV-1a ─────────────────────────────────────────────────────────────
186
187#[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
197// ── Inline timestamp helper ───────────────────────────────────────────────────
198
199fn 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// ── ChaCha20 (20 rounds, pure Rust) ──────────────────────────────────────────
208//
209// RFC 8439 §2.1 quarter-round, §2.3 block function.
210
211#[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
230/// Produce one 64-byte ChaCha20 keystream block.
231///
232/// * `key`   – 32-byte key
233/// * `nonce` – 12-byte nonce (bytes 0..12 of the 24-byte nonce field)
234/// * `counter` – 32-bit block counter
235fn chacha20_block(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u8; 64] {
236    // Constants: "expand 32-byte k"
237    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        // Column rounds
261        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        // Diagonal rounds
266        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
284/// ChaCha20 keystream XOR.
285///
286/// `nonce` – 12 bytes (first 12 of the 24-byte nonce stored in the record).
287fn 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// ── XSalsa20 (pure Rust) ─────────────────────────────────────────────────────
306//
307// XSalsa20 = HSalsa20(key, nonce[0..16]) → subkey, then Salsa20(subkey, nonce[16..24]).
308
309#[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
317/// HSalsa20: derive a 32-byte subkey from `key` and `nonce[0..16]`.
318fn 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        // Column rounds
340        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        // Diagonal rounds
345        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
359/// Produce one 64-byte Salsa20 keystream block.
360fn 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
408/// XSalsa20 keystream XOR.
409///
410/// `nonce` – 24 bytes as stored in [`SelEncryptedBlockRecord`].
411fn 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
436// ── Xor256 ───────────────────────────────────────────────────────────────────
437
438/// Repeating 256-byte key XOR (for testing).
439fn xor256_xor(key: &[u8; 32], input: &[u8]) -> Vec<u8> {
440    // Expand the 32-byte key to 256 bytes using xorshift64.
441    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
461// ── Nonce derivation from seed ────────────────────────────────────────────────
462
463/// Derive a 24-byte nonce from a 64-bit seed using xorshift64.
464fn 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
477/// Derive an encrypted CID (32 bytes) from ciphertext using FNV-1a.
478fn 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    // Fill 32 bytes by repeating the 8-byte hash 4 times.
483    for i in 0..4 {
484        cid[i * 8..(i + 1) * 8].copy_from_slice(&bytes);
485    }
486    // Mix each quarter with a different constant so they aren't identical.
487    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
498// ── Core struct ───────────────────────────────────────────────────────────────
499
500/// A pure-Rust encryption layer for block storage.
501///
502/// Manages a key store, an encrypted-block index, a bounded audit log, and
503/// exposes encrypt/decrypt/rotate/re-encrypt operations.
504pub struct StorageEncryptionLayer {
505    /// Map from KeyId → EncryptionKey.
506    key_store: HashMap<KeyId, SelEncryptionKey>,
507    /// Currently active key identifier.
508    active_key: Option<KeyId>,
509    /// Map from plaintext BlockCid → EncryptedBlockRecord.
510    block_index: HashMap<BlockCid, SelEncryptedBlockRecord>,
511    /// Bounded audit log (max 1000 entries).
512    audit_log: VecDeque<EncAuditEntry>,
513    /// Configuration.
514    config: SelEncryptionConfig,
515    /// Internal PRNG state for nonce generation.
516    prng_state: u64,
517    /// Aggregate counters.
518    stats: SelEncryptionStats,
519}
520
521/// Public type alias for [`StorageEncryptionLayer`].
522pub type SelStorageEncryptionLayer = StorageEncryptionLayer;
523
524impl StorageEncryptionLayer {
525    // ── Constructors ─────────────────────────────────────────────────────────
526
527    /// Create a new layer with default configuration and a randomly seeded PRNG.
528    pub fn new() -> Self {
529        Self::with_config(SelEncryptionConfig::default())
530    }
531
532    /// Create a new layer with the given configuration.
533    pub fn with_config(config: SelEncryptionConfig) -> Self {
534        // Seed PRNG from a combination of a fixed constant XORed with the
535        // current timestamp so different instances get different nonces.
536        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    // ── Audit helpers ─────────────────────────────────────────────────────────
549
550    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    // ── PRNG / nonce ──────────────────────────────────────────────────────────
566
567    fn next_nonce(&mut self) -> [u8; 24] {
568        nonce_from_seed(xorshift64(&mut self.prng_state))
569    }
570
571    // ── Key management ────────────────────────────────────────────────────────
572
573    /// Generate a new key derived from `seed` via xorshift64 and add it to
574    /// the key store.  Returns the new [`KeyId`].
575    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        // Derive a 16-byte key ID from the key bytes using FNV-1a.
589        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    /// Set the active key.  Returns an error if `key_id` is not in the store.
608    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    /// Generate a new key (from `seed`) and make it active.  The old key
618    /// remains in the store so existing encrypted blocks can still be
619    /// decrypted.  Returns the new [`KeyId`].
620    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        // Tag the new key as rotated from the old one.
625        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    /// Return the active [`KeyId`], or an error if none is set.
638    pub fn active_key_id(&self) -> Result<KeyId, SelError> {
639        self.active_key.ok_or(SelError::NoActiveKey)
640    }
641
642    /// Look up a key by ID.
643    pub fn get_key(&self, key_id: &KeyId) -> Option<&SelEncryptionKey> {
644        self.key_store.get(key_id)
645    }
646
647    // ── Low-level cipher dispatch ─────────────────────────────────────────────
648
649    /// Apply the configured cipher (encrypt or decrypt — stream ciphers are
650    /// symmetric: applying twice recovers the plaintext).
651    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    // ── Block encrypt / decrypt ───────────────────────────────────────────────
675
676    /// Encrypt a block identified by `cid`.
677    ///
678    /// Stores a record in the encrypted block index so that `decrypt_block`
679    /// can look up the key and nonce by CID.
680    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    /// Decrypt a block identified by `cid`.
710    ///
711    /// Looks up the key and nonce from the block index.
712    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    // ── Batch encrypt ─────────────────────────────────────────────────────────
734
735    /// Encrypt multiple blocks in one call.  Each entry returns independently;
736    /// a failure on one block does not abort the rest.
737    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    // ── Re-encrypt ────────────────────────────────────────────────────────────
783
784    /// Re-encrypt a block with a different key.
785    ///
786    /// Decrypts with the key stored in the index, then re-encrypts with
787    /// `new_key_id` and updates the index entry.
788    pub fn re_encrypt(
789        &mut self,
790        cid: BlockCid,
791        ciphertext: &[u8],
792        new_key_id: KeyId,
793    ) -> Result<Vec<u8>, SelError> {
794        // Validate the new key exists before touching state.
795        if !self.key_store.contains_key(&new_key_id) {
796            return Err(SelError::KeyNotFound(new_key_id));
797        }
798
799        // Decrypt with existing key.
800        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        // Encrypt with new key.
816        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        // Update the index.
828        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    // ── MAC verification ──────────────────────────────────────────────────────
845
846    /// Verify a simple FNV-1a-based MAC for a block.
847    ///
848    /// The expected MAC is computed as `fnv1a_64(cid || data)` and compared
849    /// against the FNV-1a hash of the block index entry's encrypted CID.
850    /// Returns `true` if and only if the MACs match.
851    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        // Compute candidate MAC: FNV-1a over concatenation of CID and data.
861        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        // Expected: FNV-1a of the stored encrypted_cid.
867        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    // ── Statistics / introspection ────────────────────────────────────────────
879
880    /// Return current aggregate statistics (snapshot).
881    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    /// Return a reference to the full audit log.
891    pub fn audit_log(&self) -> &VecDeque<EncAuditEntry> {
892        &self.audit_log
893    }
894
895    /// Return a reference to the block index.
896    pub fn block_index(&self) -> &HashMap<BlockCid, SelEncryptedBlockRecord> {
897        &self.block_index
898    }
899
900    /// Return the number of keys in the key store.
901    pub fn key_count(&self) -> usize {
902        self.key_store.len()
903    }
904
905    /// Return the cipher configured for this layer.
906    pub fn cipher(&self) -> SelCipher {
907        self.config.cipher
908    }
909
910    /// Remove the block index entry for `cid`, if present.
911    /// Returns `true` if an entry was removed.
912    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    /// Delete a key from the store.  Active key is cleared if it matches.
922    /// Returns an error if the key is not found.
923    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    /// List all key IDs in the store.
936    pub fn list_key_ids(&self) -> Vec<KeyId> {
937        self.key_store.keys().copied().collect()
938    }
939
940    /// Clear the audit log.
941    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// ── Tests ─────────────────────────────────────────────────────────────────────
953
954#[cfg(test)]
955mod tests {
956    use super::*;
957
958    // ── Helpers ───────────────────────────────────────────────────────────────
959
960    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    // ── xorshift64 ────────────────────────────────────────────────────────────
999
1000    #[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    // ── fnv1a_64 ──────────────────────────────────────────────────────────────
1022
1023    #[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    // ── ChaCha20 unit tests ───────────────────────────────────────────────────
1041
1042    #[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    // ── XSalsa20 unit tests ───────────────────────────────────────────────────
1096
1097    #[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    // ── Xor256 unit tests ─────────────────────────────────────────────────────
1142
1143    #[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    // ── Key management ────────────────────────────────────────────────────────
1160
1161    #[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        // Old key must still be accessible.
1210        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    // ── Encrypt / decrypt (ChaCha20) ──────────────────────────────────────────
1260
1261    #[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    // ── Encrypt / decrypt (XSalsa20) ─────────────────────────────────────────
1315
1316    #[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    // ── Encrypt / decrypt (Xor256) ────────────────────────────────────────────
1327
1328    #[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    // ── Batch encrypt ─────────────────────────────────────────────────────────
1339
1340    #[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    // ── Re-encrypt ────────────────────────────────────────────────────────────
1385
1386    #[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        // c2 should decrypt to the same plaintext.
1397        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    // ── MAC verification ──────────────────────────────────────────────────────
1434
1435    #[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        // The index contains the record; whether it passes is deterministic.
1456        // Both outcomes just need to update the correct counter.
1457        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    // ── Statistics ────────────────────────────────────────────────────────────
1467
1468    #[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    // ── Audit log ─────────────────────────────────────────────────────────────
1495
1496    #[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        // Flood the audit log far beyond 1000 entries.
1509        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        // Perform a few more operations to trigger trimming.
1522        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    // ── remove_block ──────────────────────────────────────────────────────────
1552
1553    #[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    // ── Default / new ─────────────────────────────────────────────────────────
1570
1571    #[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    // ── Error display ─────────────────────────────────────────────────────────
1593
1594    #[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    // ── nonce_from_seed ───────────────────────────────────────────────────────
1619
1620    #[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    // ── derive_encrypted_cid ──────────────────────────────────────────────────
1644
1645    #[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    // ── Encrypt large block ───────────────────────────────────────────────────
1662
1663    #[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    // ── Multi-key scenario ────────────────────────────────────────────────────
1684
1685    #[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        // Rotate key — old key stays in store.
1693        l.rotate_key(555);
1694
1695        // We should still decrypt the old block (index holds the old key_id).
1696        let recover = l.decrypt_block(cid, &cipher).unwrap();
1697        assert_eq!(recover, plain);
1698    }
1699}