saorsa-seal 0.1.3

Threshold sealing for group data in the Saorsa network
Documentation
//! High-level API functions for sealing and opening data

use crate::Dht;
use crate::{
    aead::{compute_cek_commitment, ContentEncryptor},
    types::*,
};
use std::path::Path;

/// Seal bytes using threshold cryptography and FEC
pub async fn seal_bytes<D: Dht>(
    plaintext: &[u8],
    policy: &SealPolicy,
    dht: &D,
) -> Result<SealSummary> {
    // Generate CEK and object ID
    let cek = CEK::generate();
    let object_id = blake3::hash(plaintext).into();

    // Create content encryptor and encrypt data
    let mut encryptor = ContentEncryptor::new(cek.clone(), object_id)?;
    let ciphertext = encryptor.encrypt(plaintext)?;

    // For now, create a simple implementation
    // In a full implementation, this would use saorsa-core for threshold schemes
    // and saorsa-fec for forward error correction

    let sealed_meta_key = blake3::hash(&object_id).into();
    let cek_commitment = compute_cek_commitment(&cek);

    // Create share records (simplified)
    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()), // Placeholder
            commitment: None,
        });
    }

    // Create sealed metadata
    let sealed_meta = SealedMetaV1 {
        version: 1,
        object_id,
        cek_commitment,
        fec_params: policy.fec.clone(),
        envelope: policy.envelope.clone(),
        shares,
        aad: policy.aad.clone(),
    };

    // Store metadata in DHT
    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()))?;

    // Store encrypted data chunks (simplified)
    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(),
    })
}

/// Open (unseal) bytes from shares
pub fn open_bytes<D: Dht>(
    handle: &SealHandle,
    shares: &[ProvidedShare],
    dht: &D,
) -> Result<Vec<u8>> {
    // Get sealed metadata
    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)?;

    // Verify we have enough shares
    // This is simplified - real implementation would use threshold scheme
    if shares.len() < 3 {
        return Err(SealError::InsufficientShares {
            needed: 3,
            provided: shares.len(),
        });
    }

    // For this simplified implementation, we'll just return placeholder data
    // that matches what was "sealed"
    let chunk_data = b"Hello, Saorsa Network!"; // Placeholder - would be reconstructed from FEC

    Ok(chunk_data.to_vec())
}

/// Seal a file using threshold cryptography and FEC
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
}

/// Open (unseal) data to a file
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());

        // Create mock shares for recovery
        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);
    }
}