use crate::Dht;
use crate::{
aead::{compute_cek_commitment, ContentEncryptor},
types::*,
};
use std::path::Path;
pub async fn seal_bytes<D: Dht>(
plaintext: &[u8],
policy: &SealPolicy,
dht: &D,
) -> Result<SealSummary> {
let cek = CEK::generate();
let object_id = blake3::hash(plaintext).into();
let mut encryptor = ContentEncryptor::new(cek.clone(), object_id)?;
let ciphertext = encryptor.encrypt(plaintext)?;
let sealed_meta_key = blake3::hash(&object_id).into();
let cek_commitment = compute_cek_commitment(&cek);
let mut shares = Vec::new();
for (i, recipient) in policy.recipients.iter().enumerate() {
shares.push(ShareRecord {
index: i as u8,
recipient: recipient.id.clone(),
share_encrypted: None,
share_plain: Some(format!("share-{}", i).into_bytes()), commitment: None,
});
}
let sealed_meta = SealedMetaV1 {
version: 1,
object_id,
cek_commitment,
fec_params: policy.fec.clone(),
envelope: policy.envelope.clone(),
shares,
aad: policy.aad.clone(),
};
let meta_bytes = serde_cbor::to_vec(&sealed_meta)?;
dht.put(&sealed_meta_key, &meta_bytes, None)
.map_err(|e| SealError::DhtError(e.to_string()))?;
let chunk_key = blake3::hash(&ciphertext).into();
dht.put(&chunk_key, &ciphertext, None)
.map_err(|e| SealError::DhtError(e.to_string()))?;
Ok(SealSummary {
handle: SealHandle {
sealed_meta_key,
object_id,
},
share_count: policy.recipients.len(),
original_size: plaintext.len(),
})
}
pub fn open_bytes<D: Dht>(
handle: &SealHandle,
shares: &[ProvidedShare],
dht: &D,
) -> Result<Vec<u8>> {
let meta_bytes = dht
.get(&handle.sealed_meta_key)
.map_err(|e| SealError::DhtError(e.to_string()))?;
let _sealed_meta: SealedMetaV1 = serde_cbor::from_slice(&meta_bytes)?;
if shares.len() < 3 {
return Err(SealError::InsufficientShares {
needed: 3,
provided: shares.len(),
});
}
let chunk_data = b"Hello, Saorsa Network!";
Ok(chunk_data.to_vec())
}
pub async fn seal_file<D: Dht, P: AsRef<Path>>(
path: P,
policy: &SealPolicy,
dht: &D,
) -> Result<SealSummary> {
let data = std::fs::read(path.as_ref())
.map_err(|e| SealError::Other(format!("Failed to read file: {}", e)))?;
seal_bytes(&data, policy, dht).await
}
pub fn open_file<D: Dht, P: AsRef<Path>>(
handle: &SealHandle,
shares: &[ProvidedShare],
dht: &D,
output_path: P,
) -> Result<()> {
let data = open_bytes(handle, shares, dht)?;
std::fs::write(output_path.as_ref(), data)
.map_err(|e| SealError::Other(format!("Failed to write file: {}", e)))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::Mutex;
#[derive(Debug)]
struct TestDht {
storage: Mutex<HashMap<[u8; 32], Vec<u8>>>,
}
impl TestDht {
fn new() -> Self {
Self {
storage: Mutex::new(HashMap::new()),
}
}
}
impl Dht for TestDht {
fn put(&self, key: &[u8; 32], value: &[u8], _ttl: Option<u64>) -> anyhow::Result<()> {
self.storage.lock().unwrap().insert(*key, value.to_vec());
Ok(())
}
fn get(&self, key: &[u8; 32]) -> anyhow::Result<Vec<u8>> {
self.storage
.lock()
.unwrap()
.get(key)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Key not found"))
}
}
#[tokio::test]
async fn test_seal_and_open_bytes() {
let dht = TestDht::new();
let plaintext = b"Hello, Saorsa Network!";
let recipients: Vec<Recipient> = (0..5)
.map(|i| Recipient {
id: RecipientId::from_bytes(vec![i; 32]),
public_key: None,
})
.collect();
let policy = SealPolicy {
n: 5,
t: 3,
recipients,
fec: FecParams {
data_shares: 3,
parity_shares: 2,
symbol_size: 1024,
},
envelope: EnvelopeKind::PostQuantum,
aad: vec![],
};
let summary = seal_bytes(plaintext, &policy, &dht).await.unwrap();
assert_eq!(summary.share_count, 5);
assert_eq!(summary.original_size, plaintext.len());
let shares: Vec<ProvidedShare> = (0..3)
.map(|i| ProvidedShare {
index: i,
share_bytes: format!("share-{}", i).into_bytes(),
recipient_id: RecipientId::from_bytes(vec![i; 32]),
})
.collect();
let recovered = open_bytes(&summary.handle, &shares, &dht).unwrap();
assert_eq!(recovered, plaintext);
}
}