/ core / src / crypto / aead.rs
aead.rs
  1  //! Authenticated Encryption with Associated Data (AEAD).
  2  //!
  3  //! This module provides ChaCha20-Poly1305 encryption and decryption, the same
  4  //! algorithm used by TLS 1.3, WireGuard, and many modern protocols.
  5  //!
  6  //! # What is AEAD?
  7  //!
  8  //! AEAD ciphers provide two guarantees in a single operation:
  9  //!
 10  //! - **Confidentiality**: Encrypted data cannot be read without the key
 11  //! - **Authenticity**: Any tampering with ciphertext or AAD is detected
 12  //!
 13  //! This is strictly better than encrypting and MACing separately, as AEAD
 14  //! avoids subtle composition errors that have broken many protocols.
 15  //!
 16  //! # Why ChaCha20-Poly1305?
 17  //!
 18  //! - **No timing side-channels**: Pure arithmetic operations, no table lookups
 19  //! - **Software-friendly**: Fast on platforms without AES hardware (mobile, etc.)
 20  //! - **Widely audited**: Used by TLS 1.3, WireGuard, Signal, etc.
 21  //! - **Simple API**: Hard to misuse compared to block cipher modes
 22  //!
 23  //! # Security Properties
 24  //!
 25  //! | Property | Provided? | Notes |
 26  //! |----------|-----------|-------|
 27  //! | Confidentiality | ✓ | Data is encrypted with 256-bit key |
 28  //! | Authenticity | ✓ | 128-bit Poly1305 tag detects tampering |
 29  //! | Integrity of AAD | ✓ | Associated data is authenticated |
 30  //! | Nonce misuse resistance | ✗ | **Never reuse a nonce with the same key** |
 31  //!
 32  //! # Nonce Safety
 33  //!
 34  //! **CRITICAL**: Reusing a nonce with the same key completely breaks security.
 35  //! This module provides two safe patterns:
 36  //!
 37  //! 1. **Random nonces**: Use [`generate_nonce()`] or [`seal()`] which generate
 38  //!    random 96-bit nonces. Safe for up to 2^32 messages per key.
 39  //!
 40  //! 2. **Counter nonces**: If you need more messages, use a counter stored
 41  //!    persistently. Must never be reused, even after crash/restart.
 42  //!
 43  //! # Usage
 44  //!
 45  //! ```
 46  //! use dead_drop_core::crypto::aead::{encrypt, decrypt, generate_nonce};
 47  //!
 48  //! let key = [0x42u8; 32];
 49  //! let nonce = generate_nonce();
 50  //! let plaintext = b"secret message";
 51  //! let aad = b"message-id-123";  // Authenticated but not encrypted
 52  //!
 53  //! let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
 54  //! let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
 55  //!
 56  //! assert_eq!(plaintext.as_slice(), decrypted.as_slice());
 57  //! ```
 58  //!
 59  //! # High-Level vs Low-Level API
 60  //!
 61  //! | Function | Nonce Handling | Best For |
 62  //! |----------|----------------|----------|
 63  //! | [`seal()`] / [`open()`] | Automatic (random, prepended) | Most use cases |
 64  //! | [`encrypt()`] / [`decrypt()`] | Manual (caller provides) | Custom protocols |
 65  
 66  use chacha20poly1305::{
 67      aead::{Aead, KeyInit, Payload},
 68      ChaCha20Poly1305, Key, Nonce,
 69  };
 70  use rand::rngs::OsRng;
 71  use rand::RngCore;
 72  use zeroize::Zeroize;
 73  
 74  use crate::error::{DeadDropError, Result};
 75  
 76  // =============================================================================
 77  // CONSTANTS
 78  // =============================================================================
 79  
 80  /// Size of ChaCha20-Poly1305 key in bytes (256 bits).
 81  ///
 82  /// This provides 256-bit security against brute-force attacks.
 83  pub const KEY_SIZE: usize = 32;
 84  
 85  /// Size of ChaCha20-Poly1305 nonce in bytes (96 bits).
 86  ///
 87  /// With random nonces, you can safely encrypt up to 2^32 messages
 88  /// before the birthday bound becomes concerning.
 89  pub const NONCE_SIZE: usize = 12;
 90  
 91  /// Size of Poly1305 authentication tag in bytes (128 bits).
 92  ///
 93  /// The tag is appended to the ciphertext and verified during decryption.
 94  /// Any modification to the ciphertext or AAD will cause verification to fail.
 95  pub const TAG_SIZE: usize = 16;
 96  
 97  /// Maximum plaintext size (to prevent memory exhaustion).
 98  ///
 99  /// Set to 64 MB. Adjust based on your application's requirements.
100  /// Messages larger than this will return `MessageTooLarge` error.
101  pub const MAX_PLAINTEXT_SIZE: usize = 64 * 1024 * 1024; // 64 MB
102  
103  /// Encrypt plaintext using ChaCha20-Poly1305.
104  ///
105  /// # Arguments
106  ///
107  /// * `key` - 32-byte encryption key
108  /// * `nonce` - 12-byte nonce (must be unique for each encryption with the same key)
109  /// * `plaintext` - Data to encrypt
110  /// * `associated_data` - Additional data to authenticate but not encrypt
111  ///
112  /// # Returns
113  ///
114  /// Ciphertext with appended authentication tag (plaintext.len() + 16 bytes).
115  ///
116  /// # Errors
117  ///
118  /// - `Encryption` if the plaintext is too large
119  /// - `Encryption` if the encryption operation fails (should not happen with valid inputs)
120  ///
121  /// # Security
122  ///
123  /// **CRITICAL**: Never reuse a nonce with the same key. Use `generate_nonce()`
124  /// to create random nonces, or use a counter-based approach if you need
125  /// deterministic nonces.
126  pub fn encrypt(
127      key: &[u8; KEY_SIZE],
128      nonce: &[u8; NONCE_SIZE],
129      plaintext: &[u8],
130      associated_data: &[u8],
131  ) -> Result<Vec<u8>> {
132      // Check plaintext size
133      if plaintext.len() > MAX_PLAINTEXT_SIZE {
134          return Err(DeadDropError::MessageTooLarge {
135              size: plaintext.len(),
136              max: MAX_PLAINTEXT_SIZE,
137          });
138      }
139  
140      let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
141      let nonce = Nonce::from_slice(nonce);
142  
143      let payload = Payload {
144          msg: plaintext,
145          aad: associated_data,
146      };
147  
148      cipher
149          .encrypt(nonce, payload)
150          .map_err(|_| DeadDropError::Encryption("ChaCha20-Poly1305 encryption failed".to_string()))
151  }
152  
153  /// Decrypt ciphertext using ChaCha20-Poly1305.
154  ///
155  /// # Arguments
156  ///
157  /// * `key` - 32-byte decryption key
158  /// * `nonce` - 12-byte nonce (same nonce used for encryption)
159  /// * `ciphertext` - Data to decrypt (includes authentication tag)
160  /// * `associated_data` - Additional authenticated data (must match encryption)
161  ///
162  /// # Returns
163  ///
164  /// Decrypted plaintext.
165  ///
166  /// # Errors
167  ///
168  /// - `Decryption` if authentication fails (ciphertext or AAD was tampered with)
169  /// - `Decryption` if the ciphertext is too short (< 16 bytes for tag)
170  pub fn decrypt(
171      key: &[u8; KEY_SIZE],
172      nonce: &[u8; NONCE_SIZE],
173      ciphertext: &[u8],
174      associated_data: &[u8],
175  ) -> Result<Vec<u8>> {
176      // Ciphertext must be at least TAG_SIZE bytes (for the auth tag)
177      if ciphertext.len() < TAG_SIZE {
178          return Err(DeadDropError::Decryption(
179              "Ciphertext too short".to_string(),
180          ));
181      }
182  
183      let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
184      let nonce = Nonce::from_slice(nonce);
185  
186      let payload = Payload {
187          msg: ciphertext,
188          aad: associated_data,
189      };
190  
191      cipher
192          .decrypt(nonce, payload)
193          .map_err(|_| DeadDropError::Decryption("Authentication failed".to_string()))
194  }
195  
196  /// Generate a random 12-byte nonce.
197  ///
198  /// Uses the operating system's secure random number generator.
199  ///
200  /// # Security
201  ///
202  /// Random nonces are safe for up to 2^32 messages with the same key
203  /// before collision probability becomes concerning. For higher message
204  /// volumes, consider using a counter-based nonce scheme.
205  pub fn generate_nonce() -> [u8; NONCE_SIZE] {
206      let mut nonce = [0u8; NONCE_SIZE];
207      OsRng.fill_bytes(&mut nonce);
208      nonce
209  }
210  
211  /// Encrypt and return both nonce and ciphertext.
212  ///
213  /// This is a convenience function that generates a random nonce
214  /// and returns it along with the ciphertext.
215  ///
216  /// # Arguments
217  ///
218  /// * `key` - 32-byte encryption key
219  /// * `plaintext` - Data to encrypt
220  /// * `associated_data` - Additional data to authenticate but not encrypt
221  ///
222  /// # Returns
223  ///
224  /// A tuple of (nonce, ciphertext).
225  pub fn encrypt_with_random_nonce(
226      key: &[u8; KEY_SIZE],
227      plaintext: &[u8],
228      associated_data: &[u8],
229  ) -> Result<([u8; NONCE_SIZE], Vec<u8>)> {
230      let nonce = generate_nonce();
231      let ciphertext = encrypt(key, &nonce, plaintext, associated_data)?;
232      Ok((nonce, ciphertext))
233  }
234  
235  /// Sealed box: nonce prepended to ciphertext.
236  ///
237  /// This format is convenient for storage and transmission as it
238  /// keeps the nonce with the ciphertext.
239  ///
240  /// Format: nonce (12 bytes) || ciphertext (N + 16 bytes)
241  ///
242  /// # Arguments
243  ///
244  /// * `key` - 32-byte encryption key
245  /// * `plaintext` - Data to encrypt
246  /// * `associated_data` - Additional data to authenticate but not encrypt
247  ///
248  /// # Returns
249  ///
250  /// Combined nonce and ciphertext (12 + plaintext.len() + 16 bytes).
251  pub fn seal(key: &[u8; KEY_SIZE], plaintext: &[u8], associated_data: &[u8]) -> Result<Vec<u8>> {
252      let nonce = generate_nonce();
253      let ciphertext = encrypt(key, &nonce, plaintext, associated_data)?;
254  
255      let mut sealed = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
256      sealed.extend_from_slice(&nonce);
257      sealed.extend(ciphertext);
258  
259      Ok(sealed)
260  }
261  
262  /// Open a sealed box.
263  ///
264  /// Extracts the nonce from the beginning of the sealed data and decrypts.
265  ///
266  /// # Arguments
267  ///
268  /// * `key` - 32-byte decryption key
269  /// * `sealed` - Combined nonce and ciphertext
270  /// * `associated_data` - Additional authenticated data
271  ///
272  /// # Returns
273  ///
274  /// Decrypted plaintext.
275  ///
276  /// # Errors
277  ///
278  /// - `Decryption` if the sealed data is too short
279  /// - `Decryption` if authentication fails
280  pub fn open(key: &[u8; KEY_SIZE], sealed: &[u8], associated_data: &[u8]) -> Result<Vec<u8>> {
281      // Need at least nonce + tag
282      if sealed.len() < NONCE_SIZE + TAG_SIZE {
283          return Err(DeadDropError::Decryption("Sealed data too short".to_string()));
284      }
285  
286      let nonce: [u8; NONCE_SIZE] = sealed[..NONCE_SIZE]
287          .try_into()
288          .map_err(|_| DeadDropError::Decryption("Invalid nonce".to_string()))?;
289  
290      let ciphertext = &sealed[NONCE_SIZE..];
291  
292      decrypt(key, &nonce, ciphertext, associated_data)
293  }
294  
295  /// Securely encrypt sensitive data with automatic key zeroization.
296  ///
297  /// This function takes ownership of the key and zeroizes it after use.
298  ///
299  /// # Arguments
300  ///
301  /// * `key` - 32-byte encryption key (will be zeroized)
302  /// * `plaintext` - Data to encrypt
303  /// * `associated_data` - Additional data to authenticate
304  ///
305  /// # Returns
306  ///
307  /// Sealed ciphertext (nonce prepended).
308  pub fn seal_and_zeroize_key(
309      mut key: [u8; KEY_SIZE],
310      plaintext: &[u8],
311      associated_data: &[u8],
312  ) -> Result<Vec<u8>> {
313      let result = seal(&key, plaintext, associated_data);
314      key.zeroize();
315      result
316  }
317  
318  /// Calculate the ciphertext length for a given plaintext length.
319  ///
320  /// Useful for pre-allocating buffers.
321  pub const fn ciphertext_len(plaintext_len: usize) -> usize {
322      plaintext_len + TAG_SIZE
323  }
324  
325  /// Calculate the sealed length for a given plaintext length.
326  ///
327  /// Includes nonce and authentication tag.
328  pub const fn sealed_len(plaintext_len: usize) -> usize {
329      NONCE_SIZE + plaintext_len + TAG_SIZE
330  }
331  
332  #[cfg(test)]
333  mod tests {
334      use super::*;
335  
336      // ==================== Basic Encryption/Decryption Tests ====================
337  
338      #[test]
339      fn test_encrypt_decrypt_basic() {
340          let key = [0x42u8; KEY_SIZE];
341          let nonce = generate_nonce();
342          let plaintext = b"Hello, World!";
343          let aad = b"";
344  
345          let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
346          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
347  
348          assert_eq!(plaintext.as_slice(), decrypted.as_slice());
349      }
350  
351      #[test]
352      fn test_encrypt_decrypt_with_aad() {
353          let key = [0x42u8; KEY_SIZE];
354          let nonce = generate_nonce();
355          let plaintext = b"Secret message";
356          let aad = b"message-id-12345";
357  
358          let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
359          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
360  
361          assert_eq!(plaintext.as_slice(), decrypted.as_slice());
362      }
363  
364      #[test]
365      fn test_encrypt_decrypt_empty_plaintext() {
366          let key = [0x42u8; KEY_SIZE];
367          let nonce = generate_nonce();
368          let plaintext = b"";
369          let aad = b"some-aad";
370  
371          let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
372          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
373  
374          assert_eq!(plaintext.as_slice(), decrypted.as_slice());
375          // Ciphertext should just be the tag
376          assert_eq!(ciphertext.len(), TAG_SIZE);
377      }
378  
379      #[test]
380      fn test_encrypt_decrypt_large_plaintext() {
381          let key = [0x42u8; KEY_SIZE];
382          let nonce = generate_nonce();
383          let plaintext = vec![0xABu8; 1024 * 1024]; // 1 MB
384          let aad = b"";
385  
386          let ciphertext = encrypt(&key, &nonce, &plaintext, aad).unwrap();
387          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
388  
389          assert_eq!(plaintext, decrypted);
390      }
391  
392      #[test]
393      fn test_ciphertext_length() {
394          let key = [0x42u8; KEY_SIZE];
395          let nonce = generate_nonce();
396          let plaintext = b"test message";
397          let aad = b"";
398  
399          let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
400  
401          // Ciphertext should be plaintext + tag
402          assert_eq!(ciphertext.len(), plaintext.len() + TAG_SIZE);
403          assert_eq!(ciphertext.len(), ciphertext_len(plaintext.len()));
404      }
405  
406      // ==================== Authentication Tests ====================
407  
408      #[test]
409      fn test_decrypt_wrong_key_fails() {
410          let key1 = [0x42u8; KEY_SIZE];
411          let key2 = [0x43u8; KEY_SIZE];
412          let nonce = generate_nonce();
413          let plaintext = b"Secret";
414          let aad = b"";
415  
416          let ciphertext = encrypt(&key1, &nonce, plaintext, aad).unwrap();
417          let result = decrypt(&key2, &nonce, &ciphertext, aad);
418  
419          assert!(result.is_err());
420          assert!(matches!(result.unwrap_err(), DeadDropError::Decryption(_)));
421      }
422  
423      #[test]
424      fn test_decrypt_wrong_nonce_fails() {
425          let key = [0x42u8; KEY_SIZE];
426          let nonce1 = [0x01u8; NONCE_SIZE];
427          let nonce2 = [0x02u8; NONCE_SIZE];
428          let plaintext = b"Secret";
429          let aad = b"";
430  
431          let ciphertext = encrypt(&key, &nonce1, plaintext, aad).unwrap();
432          let result = decrypt(&key, &nonce2, &ciphertext, aad);
433  
434          assert!(result.is_err());
435      }
436  
437      #[test]
438      fn test_decrypt_wrong_aad_fails() {
439          let key = [0x42u8; KEY_SIZE];
440          let nonce = generate_nonce();
441          let plaintext = b"Secret";
442          let aad1 = b"correct-aad";
443          let aad2 = b"wrong-aad";
444  
445          let ciphertext = encrypt(&key, &nonce, plaintext, aad1).unwrap();
446          let result = decrypt(&key, &nonce, &ciphertext, aad2);
447  
448          assert!(result.is_err());
449      }
450  
451      #[test]
452      fn test_decrypt_tampered_ciphertext_fails() {
453          let key = [0x42u8; KEY_SIZE];
454          let nonce = generate_nonce();
455          let plaintext = b"Secret message";
456          let aad = b"";
457  
458          let mut ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
459  
460          // Tamper with the ciphertext
461          if !ciphertext.is_empty() {
462              ciphertext[0] ^= 0xFF;
463          }
464  
465          let result = decrypt(&key, &nonce, &ciphertext, aad);
466          assert!(result.is_err());
467      }
468  
469      #[test]
470      fn test_decrypt_tampered_tag_fails() {
471          let key = [0x42u8; KEY_SIZE];
472          let nonce = generate_nonce();
473          let plaintext = b"Secret message";
474          let aad = b"";
475  
476          let mut ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
477  
478          // Tamper with the authentication tag (last 16 bytes)
479          let len = ciphertext.len();
480          ciphertext[len - 1] ^= 0xFF;
481  
482          let result = decrypt(&key, &nonce, &ciphertext, aad);
483          assert!(result.is_err());
484      }
485  
486      #[test]
487      fn test_decrypt_truncated_ciphertext_fails() {
488          let key = [0x42u8; KEY_SIZE];
489          let nonce = generate_nonce();
490          let plaintext = b"Secret message";
491          let aad = b"";
492  
493          let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
494  
495          // Truncate to less than tag size
496          let truncated = &ciphertext[..TAG_SIZE - 1];
497          let result = decrypt(&key, &nonce, truncated, aad);
498  
499          assert!(result.is_err());
500      }
501  
502      // ==================== Nonce Generation Tests ====================
503  
504      #[test]
505      fn test_generate_nonce_correct_size() {
506          let nonce = generate_nonce();
507          assert_eq!(nonce.len(), NONCE_SIZE);
508      }
509  
510      #[test]
511      fn test_generate_nonce_unique() {
512          let nonce1 = generate_nonce();
513          let nonce2 = generate_nonce();
514  
515          // Random nonces should be different
516          assert_ne!(nonce1, nonce2);
517      }
518  
519      #[test]
520      fn test_generate_nonce_not_zero() {
521          // Generate multiple nonces and ensure they're not all zeros
522          for _ in 0..10 {
523              let nonce = generate_nonce();
524              // Very unlikely to be all zeros
525              assert_ne!(nonce, [0u8; NONCE_SIZE]);
526          }
527      }
528  
529      // ==================== Convenience Function Tests ====================
530  
531      #[test]
532      fn test_encrypt_with_random_nonce() {
533          let key = [0x42u8; KEY_SIZE];
534          let plaintext = b"Hello";
535          let aad = b"test";
536  
537          let (nonce, ciphertext) = encrypt_with_random_nonce(&key, plaintext, aad).unwrap();
538  
539          // Should be able to decrypt with returned nonce
540          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
541          assert_eq!(plaintext.as_slice(), decrypted.as_slice());
542      }
543  
544      #[test]
545      fn test_encrypt_with_random_nonce_unique_nonces() {
546          let key = [0x42u8; KEY_SIZE];
547          let plaintext = b"Hello";
548          let aad = b"test";
549  
550          let (nonce1, _) = encrypt_with_random_nonce(&key, plaintext, aad).unwrap();
551          let (nonce2, _) = encrypt_with_random_nonce(&key, plaintext, aad).unwrap();
552  
553          // Each call should generate a different nonce
554          assert_ne!(nonce1, nonce2);
555      }
556  
557      // ==================== Seal/Open Tests ====================
558  
559      #[test]
560      fn test_seal_open_basic() {
561          let key = [0x42u8; KEY_SIZE];
562          let plaintext = b"Secret message";
563          let aad = b"metadata";
564  
565          let sealed = seal(&key, plaintext, aad).unwrap();
566          let opened = open(&key, &sealed, aad).unwrap();
567  
568          assert_eq!(plaintext.as_slice(), opened.as_slice());
569      }
570  
571      #[test]
572      fn test_seal_length() {
573          let key = [0x42u8; KEY_SIZE];
574          let plaintext = b"Hello, World!";
575          let aad = b"";
576  
577          let sealed = seal(&key, plaintext, aad).unwrap();
578  
579          assert_eq!(sealed.len(), sealed_len(plaintext.len()));
580          assert_eq!(sealed.len(), NONCE_SIZE + plaintext.len() + TAG_SIZE);
581      }
582  
583      #[test]
584      fn test_seal_open_empty_plaintext() {
585          let key = [0x42u8; KEY_SIZE];
586          let plaintext = b"";
587          let aad = b"some-aad";
588  
589          let sealed = seal(&key, plaintext, aad).unwrap();
590          let opened = open(&key, &sealed, aad).unwrap();
591  
592          assert_eq!(plaintext.as_slice(), opened.as_slice());
593      }
594  
595      #[test]
596      fn test_open_wrong_key_fails() {
597          let key1 = [0x42u8; KEY_SIZE];
598          let key2 = [0x43u8; KEY_SIZE];
599          let plaintext = b"Secret";
600          let aad = b"";
601  
602          let sealed = seal(&key1, plaintext, aad).unwrap();
603          let result = open(&key2, &sealed, aad);
604  
605          assert!(result.is_err());
606      }
607  
608      #[test]
609      fn test_open_wrong_aad_fails() {
610          let key = [0x42u8; KEY_SIZE];
611          let plaintext = b"Secret";
612          let aad1 = b"correct";
613          let aad2 = b"wrong";
614  
615          let sealed = seal(&key, plaintext, aad1).unwrap();
616          let result = open(&key, &sealed, aad2);
617  
618          assert!(result.is_err());
619      }
620  
621      #[test]
622      fn test_open_too_short_fails() {
623          let key = [0x42u8; KEY_SIZE];
624          let short_data = [0u8; NONCE_SIZE + TAG_SIZE - 1]; // Just under minimum
625  
626          let result = open(&key, &short_data, b"");
627          assert!(result.is_err());
628      }
629  
630      #[test]
631      fn test_open_tampered_nonce_fails() {
632          let key = [0x42u8; KEY_SIZE];
633          let plaintext = b"Secret";
634          let aad = b"";
635  
636          let mut sealed = seal(&key, plaintext, aad).unwrap();
637  
638          // Tamper with the nonce (first 12 bytes)
639          sealed[0] ^= 0xFF;
640  
641          let result = open(&key, &sealed, aad);
642          assert!(result.is_err());
643      }
644  
645      #[test]
646      fn test_seal_unique_each_time() {
647          let key = [0x42u8; KEY_SIZE];
648          let plaintext = b"Same message";
649          let aad = b"";
650  
651          let sealed1 = seal(&key, plaintext, aad).unwrap();
652          let sealed2 = seal(&key, plaintext, aad).unwrap();
653  
654          // Different random nonces should produce different sealed data
655          assert_ne!(sealed1, sealed2);
656  
657          // But both should decrypt to the same plaintext
658          let opened1 = open(&key, &sealed1, aad).unwrap();
659          let opened2 = open(&key, &sealed2, aad).unwrap();
660  
661          assert_eq!(opened1, opened2);
662      }
663  
664      // ==================== seal_and_zeroize_key Tests ====================
665  
666      #[test]
667      fn test_seal_and_zeroize_key() {
668          let key = [0x42u8; KEY_SIZE];
669          let plaintext = b"Secret";
670          let aad = b"";
671  
672          let sealed = seal_and_zeroize_key(key, plaintext, aad).unwrap();
673          let opened = open(&[0x42u8; KEY_SIZE], &sealed, aad).unwrap();
674  
675          assert_eq!(plaintext.as_slice(), opened.as_slice());
676      }
677  
678      // ==================== Length Calculation Tests ====================
679  
680      #[test]
681      fn test_ciphertext_len_calculation() {
682          assert_eq!(ciphertext_len(0), TAG_SIZE);
683          assert_eq!(ciphertext_len(10), 10 + TAG_SIZE);
684          assert_eq!(ciphertext_len(1000), 1000 + TAG_SIZE);
685      }
686  
687      #[test]
688      fn test_sealed_len_calculation() {
689          assert_eq!(sealed_len(0), NONCE_SIZE + TAG_SIZE);
690          assert_eq!(sealed_len(10), NONCE_SIZE + 10 + TAG_SIZE);
691          assert_eq!(sealed_len(1000), NONCE_SIZE + 1000 + TAG_SIZE);
692      }
693  
694      // ==================== Error Case Tests ====================
695  
696      #[test]
697      fn test_encrypt_too_large_plaintext() {
698          let key = [0x42u8; KEY_SIZE];
699          let nonce = generate_nonce();
700          let plaintext = vec![0u8; MAX_PLAINTEXT_SIZE + 1];
701          let aad = b"";
702  
703          let result = encrypt(&key, &nonce, &plaintext, aad);
704  
705          assert!(result.is_err());
706          match result.unwrap_err() {
707              DeadDropError::MessageTooLarge { size, max } => {
708                  assert_eq!(size, MAX_PLAINTEXT_SIZE + 1);
709                  assert_eq!(max, MAX_PLAINTEXT_SIZE);
710              }
711              _ => panic!("Expected MessageTooLarge error"),
712          }
713      }
714  
715      #[test]
716      fn test_decrypt_empty_ciphertext() {
717          let key = [0x42u8; KEY_SIZE];
718          let nonce = generate_nonce();
719          let ciphertext = b"";
720          let aad = b"";
721  
722          let result = decrypt(&key, &nonce, ciphertext, aad);
723          assert!(result.is_err());
724      }
725  
726      // ==================== Determinism Tests ====================
727  
728      #[test]
729      fn test_encrypt_deterministic_with_same_nonce() {
730          let key = [0x42u8; KEY_SIZE];
731          let nonce = [0x01u8; NONCE_SIZE];
732          let plaintext = b"Hello";
733          let aad = b"test";
734  
735          let ciphertext1 = encrypt(&key, &nonce, plaintext, aad).unwrap();
736          let ciphertext2 = encrypt(&key, &nonce, plaintext, aad).unwrap();
737  
738          // Same key, nonce, plaintext, AAD should produce same ciphertext
739          assert_eq!(ciphertext1, ciphertext2);
740      }
741  
742      // ==================== Binary Data Tests ====================
743  
744      #[test]
745      fn test_encrypt_decrypt_binary_data() {
746          let key = [0x42u8; KEY_SIZE];
747          let nonce = generate_nonce();
748  
749          // Binary data with all byte values
750          let plaintext: Vec<u8> = (0..=255).collect();
751          let aad = b"binary-test";
752  
753          let ciphertext = encrypt(&key, &nonce, &plaintext, aad).unwrap();
754          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
755  
756          assert_eq!(plaintext, decrypted);
757      }
758  
759      #[test]
760      fn test_encrypt_decrypt_with_null_bytes() {
761          let key = [0x42u8; KEY_SIZE];
762          let nonce = generate_nonce();
763          let plaintext = b"hello\x00world\x00\x00";
764          let aad = b"\x00\x00\x00";
765  
766          let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
767          let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
768  
769          assert_eq!(plaintext.as_slice(), decrypted.as_slice());
770      }
771  }