use crate::encryption::{EncryptionNonce, decrypt as aead_decrypt, encrypt as aead_encrypt};
use crate::hash::hash;
use blake3::Hasher;
use rand::Rng as _;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use zeroize::Zeroize;
#[derive(Error, Debug)]
pub enum CertifiedDeletionError {
#[error("Decryption failed: data already deleted or invalid")]
DecryptionFailed,
#[error("Invalid deletion certificate")]
InvalidCertificate,
#[error("Witness not found for ciphertext")]
WitnessNotFound,
#[error("Commitment mismatch")]
CommitmentMismatch,
#[error("Serialization error: {0}")]
Serialization(String),
}
pub type CertifiedDeletionResult<T> = Result<T, CertifiedDeletionError>;
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
struct Witness {
value: [u8; 32],
}
impl Witness {
fn new() -> Self {
use rand::Rng as _;
let mut value = [0u8; 32];
rand::rng().fill_bytes(&mut value);
Self { value }
}
fn commitment(&self) -> Vec<u8> {
hash(&self.value).to_vec()
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct EncryptedWithWitness {
ciphertext: Vec<u8>,
nonce: EncryptionNonce,
witness_commitment: Vec<u8>,
id: Vec<u8>,
}
impl EncryptedWithWitness {
pub fn commitment(&self) -> &[u8] {
&self.witness_commitment
}
pub fn id(&self) -> &[u8] {
&self.id
}
pub fn to_bytes(&self) -> CertifiedDeletionResult<Vec<u8>> {
crate::codec::encode(self).map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
}
pub fn from_bytes(bytes: &[u8]) -> CertifiedDeletionResult<Self> {
crate::codec::decode(bytes)
.map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct DeletionCertificate {
witness_commitment: Vec<u8>,
timestamp: u64,
proof: Vec<u8>,
}
impl DeletionCertificate {
pub fn verify(&self, expected_commitment: &[u8]) -> CertifiedDeletionResult<()> {
if self.witness_commitment != expected_commitment {
return Err(CertifiedDeletionError::CommitmentMismatch);
}
Ok(())
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn to_bytes(&self) -> CertifiedDeletionResult<Vec<u8>> {
crate::codec::encode(self).map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
}
pub fn from_bytes(bytes: &[u8]) -> CertifiedDeletionResult<Self> {
crate::codec::decode(bytes)
.map_err(|e| CertifiedDeletionError::Serialization(e.to_string()))
}
}
pub struct CertifiedDeletion {
witnesses: HashMap<Vec<u8>, Witness>,
}
impl CertifiedDeletion {
pub fn new() -> Self {
Self {
witnesses: HashMap::new(),
}
}
pub fn encrypt(&mut self, plaintext: &[u8]) -> EncryptedWithWitness {
let witness = Witness::new();
let witness_commitment = witness.commitment();
let key = self.derive_key_from_witness(&witness);
let mut nonce = [0u8; 12];
rand::rng().fill_bytes(&mut nonce);
let ciphertext = aead_encrypt(plaintext, &key, &nonce)
.expect("Encryption should not fail with valid inputs");
let id = hash(&ciphertext).to_vec();
self.witnesses.insert(id.clone(), witness);
EncryptedWithWitness {
ciphertext,
nonce,
witness_commitment,
id,
}
}
pub fn decrypt(&self, encrypted: &EncryptedWithWitness) -> CertifiedDeletionResult<Vec<u8>> {
let witness = self
.witnesses
.get(&encrypted.id)
.ok_or(CertifiedDeletionError::WitnessNotFound)?;
if witness.commitment() != encrypted.witness_commitment {
return Err(CertifiedDeletionError::CommitmentMismatch);
}
let key = self.derive_key_from_witness(witness);
aead_decrypt(&encrypted.ciphertext, &key, &encrypted.nonce)
.map_err(|_| CertifiedDeletionError::DecryptionFailed)
}
pub fn certify_deletion(
&mut self,
encrypted: &EncryptedWithWitness,
) -> CertifiedDeletionResult<DeletionCertificate> {
let witness = self
.witnesses
.remove(&encrypted.id)
.ok_or(CertifiedDeletionError::WitnessNotFound)?;
let witness_commitment = witness.commitment();
let timestamp = current_timestamp();
let proof = self.generate_deletion_proof(&witness, timestamp);
drop(witness);
Ok(DeletionCertificate {
witness_commitment,
timestamp,
proof,
})
}
pub fn can_decrypt(&self, encrypted: &EncryptedWithWitness) -> bool {
self.witnesses.contains_key(&encrypted.id)
}
fn derive_key_from_witness(&self, witness: &Witness) -> [u8; 32] {
let mut hasher = Hasher::new();
hasher.update(b"certified-deletion-key");
hasher.update(&witness.value);
let hash = hasher.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(hash.as_bytes());
key
}
fn generate_deletion_proof(&self, witness: &Witness, timestamp: u64) -> Vec<u8> {
let mut hasher = Hasher::new();
hasher.update(b"deletion-proof");
hasher.update(&witness.value);
hasher.update(×tamp.to_le_bytes());
hasher.finalize().as_bytes().to_vec()
}
}
impl Default for CertifiedDeletion {
fn default() -> Self {
Self::new()
}
}
fn current_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
}
pub struct BatchDeletion {
cd: CertifiedDeletion,
}
impl BatchDeletion {
pub fn new() -> Self {
Self {
cd: CertifiedDeletion::new(),
}
}
pub fn encrypt_batch(&mut self, items: &[Vec<u8>]) -> Vec<EncryptedWithWitness> {
items.iter().map(|item| self.cd.encrypt(item)).collect()
}
pub fn certify_batch_deletion(
&mut self,
encrypted: &[EncryptedWithWitness],
) -> CertifiedDeletionResult<Vec<DeletionCertificate>> {
encrypted
.iter()
.map(|enc| self.cd.certify_deletion(enc))
.collect()
}
pub fn inner(&mut self) -> &mut CertifiedDeletion {
&mut self.cd
}
}
impl Default for BatchDeletion {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_certified_deletion_basic() {
let mut cd = CertifiedDeletion::new();
let data = b"sensitive data";
let encrypted = cd.encrypt(data);
let decrypted = cd.decrypt(&encrypted).unwrap();
assert_eq!(decrypted, data);
let cert = cd.certify_deletion(&encrypted).unwrap();
assert!(cert.verify(encrypted.commitment()).is_ok());
assert!(cd.decrypt(&encrypted).is_err());
}
#[test]
fn test_cannot_decrypt_after_deletion() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"secret");
cd.certify_deletion(&encrypted).unwrap();
let result = cd.decrypt(&encrypted);
assert!(result.is_err());
}
#[test]
fn test_multiple_encryptions() {
let mut cd = CertifiedDeletion::new();
let enc1 = cd.encrypt(b"data1");
let enc2 = cd.encrypt(b"data2");
assert_eq!(cd.decrypt(&enc1).unwrap(), b"data1");
assert_eq!(cd.decrypt(&enc2).unwrap(), b"data2");
cd.certify_deletion(&enc1).unwrap();
assert!(cd.decrypt(&enc1).is_err());
assert_eq!(cd.decrypt(&enc2).unwrap(), b"data2");
}
#[test]
fn test_can_decrypt_check() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"data");
assert!(cd.can_decrypt(&encrypted));
cd.certify_deletion(&encrypted).unwrap();
assert!(!cd.can_decrypt(&encrypted));
}
#[test]
fn test_deletion_certificate_timestamp() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"data");
let before = current_timestamp();
let cert = cd.certify_deletion(&encrypted).unwrap();
let after = current_timestamp();
assert!(cert.timestamp() >= before);
assert!(cert.timestamp() <= after);
}
#[test]
fn test_encrypted_serialization() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"data");
let bytes = encrypted.to_bytes().unwrap();
let deserialized = EncryptedWithWitness::from_bytes(&bytes).unwrap();
assert_eq!(encrypted.id(), deserialized.id());
assert_eq!(encrypted.commitment(), deserialized.commitment());
}
#[test]
fn test_certificate_serialization() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"data");
let cert = cd.certify_deletion(&encrypted).unwrap();
let bytes = cert.to_bytes().unwrap();
let deserialized = DeletionCertificate::from_bytes(&bytes).unwrap();
assert_eq!(cert.timestamp(), deserialized.timestamp());
assert!(deserialized.verify(encrypted.commitment()).is_ok());
}
#[test]
fn test_invalid_commitment_verification() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"data");
let cert = cd.certify_deletion(&encrypted).unwrap();
let wrong_commitment = b"wrong_commitment";
assert!(cert.verify(wrong_commitment).is_err());
}
#[test]
fn test_batch_deletion() {
let mut batch = BatchDeletion::new();
let items = vec![b"item1".to_vec(), b"item2".to_vec(), b"item3".to_vec()];
let encrypted = batch.encrypt_batch(&items);
assert_eq!(encrypted.len(), 3);
let certs = batch.certify_batch_deletion(&encrypted).unwrap();
assert_eq!(certs.len(), 3);
for (enc, cert) in encrypted.iter().zip(certs.iter()) {
assert!(!batch.inner().can_decrypt(enc));
assert!(cert.verify(enc.commitment()).is_ok());
}
}
#[test]
fn test_cd_default() {
let mut cd = CertifiedDeletion::default();
let encrypted = cd.encrypt(b"test");
assert!(cd.can_decrypt(&encrypted));
}
#[test]
fn test_batch_default() {
let mut batch = BatchDeletion::default();
let encrypted = batch.encrypt_batch(&[b"test".to_vec()]);
assert_eq!(encrypted.len(), 1);
}
#[test]
fn test_double_deletion_fails() {
let mut cd = CertifiedDeletion::new();
let encrypted = cd.encrypt(b"data");
cd.certify_deletion(&encrypted).unwrap();
assert!(cd.certify_deletion(&encrypted).is_err());
}
#[test]
fn test_deletion_makes_decryption_impossible() {
let mut cd = CertifiedDeletion::new();
let data = b"sensitive information";
let encrypted = cd.encrypt(data);
assert_eq!(cd.decrypt(&encrypted).unwrap(), data);
cd.certify_deletion(&encrypted).unwrap();
match cd.decrypt(&encrypted) {
Err(CertifiedDeletionError::WitnessNotFound) => {
}
_ => panic!("Expected WitnessNotFound error"),
}
}
}