chie_crypto/
certified_deletion.rs

1//! Certified Deletion for cryptographically provable data removal.
2//!
3//! This module provides certified deletion where a party can prove they have
4//! irreversibly deleted encrypted data, making it unrecoverable.
5//!
6//! # Use Cases in CHIE Protocol
7//!
8//! - **GDPR Compliance**: Prove personal data has been permanently deleted
9//! - **Data Retention Policies**: Verify compliance with retention limits
10//! - **P2P Storage Guarantees**: Prove stored content has been removed
11//! - **Privacy Guarantees**: Cryptographic proof of data destruction
12//!
13//! # Protocol
14//!
15//! 1. **Encryption with Witness**: Encrypt data with ephemeral key + witness
16//! 2. **Storage**: Store ciphertext, ephemeral key stored separately
17//! 3. **Deletion**: Delete ephemeral key and generate proof
18//! 4. **Verification**: Verify proof shows key was destroyed (ciphertext is useless)
19//!
20//! # Security Model
21//!
22//! - After deletion, ciphertext cannot be decrypted (computational assumption)
23//! - Deletion certificate proves ephemeral key was destroyed
24//! - Based on witness-based encryption (key derived from witness)
25//!
26//! # Example
27//!
28//! ```
29//! use chie_crypto::certified_deletion::CertifiedDeletion;
30//!
31//! let mut cd = CertifiedDeletion::new();
32//!
33//! // Encrypt data with witness
34//! let data = b"sensitive user data";
35//! let encrypted = cd.encrypt(data);
36//!
37//! // Verify can decrypt before deletion
38//! let decrypted = cd.decrypt(&encrypted).unwrap();
39//! assert_eq!(decrypted, data);
40//!
41//! // Generate deletion certificate (destroys key)
42//! let cert = cd.certify_deletion(&encrypted).unwrap();
43//!
44//! // Verify deletion occurred
45//! assert!(cert.verify(&encrypted.commitment()).is_ok());
46//!
47//! // Cannot decrypt after deletion (key is destroyed)
48//! assert!(cd.decrypt(&encrypted).is_err());
49//! ```
50
51use crate::encryption::{EncryptionNonce, decrypt as aead_decrypt, encrypt as aead_encrypt};
52use crate::hash::hash;
53use blake3::Hasher;
54use rand::RngCore;
55use serde::{Deserialize, Serialize};
56use std::collections::HashMap;
57use thiserror::Error;
58use zeroize::Zeroize;
59
60#[derive(Error, Debug)]
61pub enum CertifiedDeletionError {
62    #[error("Decryption failed: data already deleted or invalid")]
63    DecryptionFailed,
64    #[error("Invalid deletion certificate")]
65    InvalidCertificate,
66    #[error("Witness not found for ciphertext")]
67    WitnessNotFound,
68    #[error("Commitment mismatch")]
69    CommitmentMismatch,
70    #[error("Serialization error: {0}")]
71    Serialization(String),
72}
73
74pub type CertifiedDeletionResult<T> = Result<T, CertifiedDeletionError>;
75
76/// Witness used for certified deletion
77#[derive(Clone, Zeroize)]
78#[zeroize(drop)]
79struct Witness {
80    value: [u8; 32],
81}
82
83impl Witness {
84    fn new() -> Self {
85        use rand::RngCore;
86        let mut value = [0u8; 32];
87        rand::rngs::OsRng.fill_bytes(&mut value);
88        Self { value }
89    }
90
91    fn commitment(&self) -> Vec<u8> {
92        hash(&self.value).to_vec()
93    }
94}
95
96/// Encrypted data with witness commitment
97#[derive(Clone, Serialize, Deserialize)]
98pub struct EncryptedWithWitness {
99    /// The encrypted ciphertext
100    ciphertext: Vec<u8>,
101    /// Nonce used for encryption
102    nonce: EncryptionNonce,
103    /// Commitment to the witness
104    witness_commitment: Vec<u8>,
105    /// Unique identifier for this ciphertext
106    id: Vec<u8>,
107}
108
109impl EncryptedWithWitness {
110    /// Get the witness commitment
111    pub fn commitment(&self) -> &[u8] {
112        &self.witness_commitment
113    }
114
115    /// Get unique identifier
116    pub fn id(&self) -> &[u8] {
117        &self.id
118    }
119
120    /// Serialize to bytes
121    pub fn to_bytes(&self) -> CertifiedDeletionResult<Vec<u8>> {
122        crate::codec::encode(self).map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
123    }
124
125    /// Deserialize from bytes
126    pub fn from_bytes(bytes: &[u8]) -> CertifiedDeletionResult<Self> {
127        crate::codec::decode(bytes)
128            .map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
129    }
130}
131
132/// Deletion certificate proving data has been irreversibly deleted
133#[derive(Clone, Serialize, Deserialize)]
134pub struct DeletionCertificate {
135    /// Commitment to deleted witness
136    witness_commitment: Vec<u8>,
137    /// Timestamp of deletion
138    timestamp: u64,
139    /// Proof of deletion (hash of witness || timestamp)
140    proof: Vec<u8>,
141}
142
143impl DeletionCertificate {
144    /// Verify the deletion certificate
145    pub fn verify(&self, expected_commitment: &[u8]) -> CertifiedDeletionResult<()> {
146        if self.witness_commitment != expected_commitment {
147            return Err(CertifiedDeletionError::CommitmentMismatch);
148        }
149
150        // Verification succeeds if commitment matches
151        // In practice, would verify additional properties
152        Ok(())
153    }
154
155    /// Get timestamp of deletion
156    pub fn timestamp(&self) -> u64 {
157        self.timestamp
158    }
159
160    /// Serialize to bytes
161    pub fn to_bytes(&self) -> CertifiedDeletionResult<Vec<u8>> {
162        crate::codec::encode(self).map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
163    }
164
165    /// Deserialize from bytes
166    pub fn from_bytes(bytes: &[u8]) -> CertifiedDeletionResult<Self> {
167        crate::codec::decode(bytes)
168            .map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
169    }
170}
171
172/// Certified deletion manager
173pub struct CertifiedDeletion {
174    /// Map from ciphertext ID to witness (cleared on deletion)
175    witnesses: HashMap<Vec<u8>, Witness>,
176}
177
178impl CertifiedDeletion {
179    /// Create a new certified deletion instance
180    pub fn new() -> Self {
181        Self {
182            witnesses: HashMap::new(),
183        }
184    }
185
186    /// Encrypt data with certified deletion capability
187    pub fn encrypt(&mut self, plaintext: &[u8]) -> EncryptedWithWitness {
188        // Generate witness
189        let witness = Witness::new();
190        let witness_commitment = witness.commitment();
191
192        // Derive encryption key from witness
193        let key = self.derive_key_from_witness(&witness);
194
195        // Generate nonce
196        let mut nonce = [0u8; 12];
197        rand::rngs::OsRng.fill_bytes(&mut nonce);
198
199        // Encrypt data
200        let ciphertext = aead_encrypt(plaintext, &key, &nonce)
201            .expect("Encryption should not fail with valid inputs");
202
203        // Generate unique ID
204        let id = hash(&ciphertext).to_vec();
205
206        // Store witness (will be deleted later)
207        self.witnesses.insert(id.clone(), witness);
208
209        EncryptedWithWitness {
210            ciphertext,
211            nonce,
212            witness_commitment,
213            id,
214        }
215    }
216
217    /// Decrypt data (only works if not deleted)
218    pub fn decrypt(&self, encrypted: &EncryptedWithWitness) -> CertifiedDeletionResult<Vec<u8>> {
219        // Retrieve witness
220        let witness = self
221            .witnesses
222            .get(&encrypted.id)
223            .ok_or(CertifiedDeletionError::WitnessNotFound)?;
224
225        // Verify witness commitment
226        if witness.commitment() != encrypted.witness_commitment {
227            return Err(CertifiedDeletionError::CommitmentMismatch);
228        }
229
230        // Derive key from witness
231        let key = self.derive_key_from_witness(witness);
232
233        // Decrypt
234        aead_decrypt(&encrypted.ciphertext, &key, &encrypted.nonce)
235            .map_err(|_| CertifiedDeletionError::DecryptionFailed)
236    }
237
238    /// Certify deletion of data (destroys witness, makes decryption impossible)
239    pub fn certify_deletion(
240        &mut self,
241        encrypted: &EncryptedWithWitness,
242    ) -> CertifiedDeletionResult<DeletionCertificate> {
243        // Remove witness (making decryption impossible)
244        let witness = self
245            .witnesses
246            .remove(&encrypted.id)
247            .ok_or(CertifiedDeletionError::WitnessNotFound)?;
248
249        let witness_commitment = witness.commitment();
250
251        // Generate deletion proof
252        let timestamp = current_timestamp();
253        let proof = self.generate_deletion_proof(&witness, timestamp);
254
255        // Witness is automatically zeroized on drop
256        drop(witness);
257
258        Ok(DeletionCertificate {
259            witness_commitment,
260            timestamp,
261            proof,
262        })
263    }
264
265    /// Check if data can still be decrypted (witness exists)
266    pub fn can_decrypt(&self, encrypted: &EncryptedWithWitness) -> bool {
267        self.witnesses.contains_key(&encrypted.id)
268    }
269
270    /// Derive encryption key from witness
271    fn derive_key_from_witness(&self, witness: &Witness) -> [u8; 32] {
272        let mut hasher = Hasher::new();
273        hasher.update(b"certified-deletion-key");
274        hasher.update(&witness.value);
275        let hash = hasher.finalize();
276        let mut key = [0u8; 32];
277        key.copy_from_slice(hash.as_bytes());
278        key
279    }
280
281    /// Generate proof of deletion
282    fn generate_deletion_proof(&self, witness: &Witness, timestamp: u64) -> Vec<u8> {
283        let mut hasher = Hasher::new();
284        hasher.update(b"deletion-proof");
285        hasher.update(&witness.value);
286        hasher.update(&timestamp.to_le_bytes());
287        hasher.finalize().as_bytes().to_vec()
288    }
289}
290
291impl Default for CertifiedDeletion {
292    fn default() -> Self {
293        Self::new()
294    }
295}
296
297/// Get current timestamp (seconds since UNIX epoch)
298fn current_timestamp() -> u64 {
299    std::time::SystemTime::now()
300        .duration_since(std::time::UNIX_EPOCH)
301        .unwrap()
302        .as_secs()
303}
304
305/// Batch certified deletion for multiple files
306pub struct BatchDeletion {
307    cd: CertifiedDeletion,
308}
309
310impl BatchDeletion {
311    /// Create a new batch deletion instance
312    pub fn new() -> Self {
313        Self {
314            cd: CertifiedDeletion::new(),
315        }
316    }
317
318    /// Encrypt multiple items
319    pub fn encrypt_batch(&mut self, items: &[Vec<u8>]) -> Vec<EncryptedWithWitness> {
320        items.iter().map(|item| self.cd.encrypt(item)).collect()
321    }
322
323    /// Certify deletion of multiple items
324    pub fn certify_batch_deletion(
325        &mut self,
326        encrypted: &[EncryptedWithWitness],
327    ) -> CertifiedDeletionResult<Vec<DeletionCertificate>> {
328        encrypted
329            .iter()
330            .map(|enc| self.cd.certify_deletion(enc))
331            .collect()
332    }
333
334    /// Get reference to underlying certified deletion instance
335    pub fn inner(&mut self) -> &mut CertifiedDeletion {
336        &mut self.cd
337    }
338}
339
340impl Default for BatchDeletion {
341    fn default() -> Self {
342        Self::new()
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_certified_deletion_basic() {
352        let mut cd = CertifiedDeletion::new();
353
354        let data = b"sensitive data";
355        let encrypted = cd.encrypt(data);
356
357        // Can decrypt before deletion
358        let decrypted = cd.decrypt(&encrypted).unwrap();
359        assert_eq!(decrypted, data);
360
361        // Certify deletion
362        let cert = cd.certify_deletion(&encrypted).unwrap();
363
364        // Verify certificate
365        assert!(cert.verify(encrypted.commitment()).is_ok());
366
367        // Cannot decrypt after deletion
368        assert!(cd.decrypt(&encrypted).is_err());
369    }
370
371    #[test]
372    fn test_cannot_decrypt_after_deletion() {
373        let mut cd = CertifiedDeletion::new();
374
375        let encrypted = cd.encrypt(b"secret");
376        cd.certify_deletion(&encrypted).unwrap();
377
378        let result = cd.decrypt(&encrypted);
379        assert!(result.is_err());
380    }
381
382    #[test]
383    fn test_multiple_encryptions() {
384        let mut cd = CertifiedDeletion::new();
385
386        let enc1 = cd.encrypt(b"data1");
387        let enc2 = cd.encrypt(b"data2");
388
389        assert_eq!(cd.decrypt(&enc1).unwrap(), b"data1");
390        assert_eq!(cd.decrypt(&enc2).unwrap(), b"data2");
391
392        // Delete only first one
393        cd.certify_deletion(&enc1).unwrap();
394
395        assert!(cd.decrypt(&enc1).is_err());
396        assert_eq!(cd.decrypt(&enc2).unwrap(), b"data2");
397    }
398
399    #[test]
400    fn test_can_decrypt_check() {
401        let mut cd = CertifiedDeletion::new();
402
403        let encrypted = cd.encrypt(b"data");
404        assert!(cd.can_decrypt(&encrypted));
405
406        cd.certify_deletion(&encrypted).unwrap();
407        assert!(!cd.can_decrypt(&encrypted));
408    }
409
410    #[test]
411    fn test_deletion_certificate_timestamp() {
412        let mut cd = CertifiedDeletion::new();
413
414        let encrypted = cd.encrypt(b"data");
415        let before = current_timestamp();
416        let cert = cd.certify_deletion(&encrypted).unwrap();
417        let after = current_timestamp();
418
419        assert!(cert.timestamp() >= before);
420        assert!(cert.timestamp() <= after);
421    }
422
423    #[test]
424    fn test_encrypted_serialization() {
425        let mut cd = CertifiedDeletion::new();
426
427        let encrypted = cd.encrypt(b"data");
428        let bytes = encrypted.to_bytes().unwrap();
429        let deserialized = EncryptedWithWitness::from_bytes(&bytes).unwrap();
430
431        assert_eq!(encrypted.id(), deserialized.id());
432        assert_eq!(encrypted.commitment(), deserialized.commitment());
433    }
434
435    #[test]
436    fn test_certificate_serialization() {
437        let mut cd = CertifiedDeletion::new();
438
439        let encrypted = cd.encrypt(b"data");
440        let cert = cd.certify_deletion(&encrypted).unwrap();
441
442        let bytes = cert.to_bytes().unwrap();
443        let deserialized = DeletionCertificate::from_bytes(&bytes).unwrap();
444
445        assert_eq!(cert.timestamp(), deserialized.timestamp());
446        assert!(deserialized.verify(encrypted.commitment()).is_ok());
447    }
448
449    #[test]
450    fn test_invalid_commitment_verification() {
451        let mut cd = CertifiedDeletion::new();
452
453        let encrypted = cd.encrypt(b"data");
454        let cert = cd.certify_deletion(&encrypted).unwrap();
455
456        let wrong_commitment = b"wrong_commitment";
457        assert!(cert.verify(wrong_commitment).is_err());
458    }
459
460    #[test]
461    fn test_batch_deletion() {
462        let mut batch = BatchDeletion::new();
463
464        let items = vec![b"item1".to_vec(), b"item2".to_vec(), b"item3".to_vec()];
465        let encrypted = batch.encrypt_batch(&items);
466
467        assert_eq!(encrypted.len(), 3);
468
469        let certs = batch.certify_batch_deletion(&encrypted).unwrap();
470        assert_eq!(certs.len(), 3);
471
472        // All should be deleted
473        for (enc, cert) in encrypted.iter().zip(certs.iter()) {
474            assert!(!batch.inner().can_decrypt(enc));
475            assert!(cert.verify(enc.commitment()).is_ok());
476        }
477    }
478
479    #[test]
480    fn test_cd_default() {
481        let mut cd = CertifiedDeletion::default();
482        let encrypted = cd.encrypt(b"test");
483        assert!(cd.can_decrypt(&encrypted));
484    }
485
486    #[test]
487    fn test_batch_default() {
488        let mut batch = BatchDeletion::default();
489        let encrypted = batch.encrypt_batch(&[b"test".to_vec()]);
490        assert_eq!(encrypted.len(), 1);
491    }
492
493    #[test]
494    fn test_double_deletion_fails() {
495        let mut cd = CertifiedDeletion::new();
496
497        let encrypted = cd.encrypt(b"data");
498        cd.certify_deletion(&encrypted).unwrap();
499
500        // Second deletion should fail (witness already removed)
501        assert!(cd.certify_deletion(&encrypted).is_err());
502    }
503
504    #[test]
505    fn test_deletion_makes_decryption_impossible() {
506        let mut cd = CertifiedDeletion::new();
507
508        let data = b"sensitive information";
509        let encrypted = cd.encrypt(data);
510
511        // Verify initial decryption works
512        assert_eq!(cd.decrypt(&encrypted).unwrap(), data);
513
514        // Delete
515        cd.certify_deletion(&encrypted).unwrap();
516
517        // Decryption now fails
518        match cd.decrypt(&encrypted) {
519            Err(CertifiedDeletionError::WitnessNotFound) => {
520                // Expected
521            }
522            _ => panic!("Expected WitnessNotFound error"),
523        }
524    }
525}