use crate::error::PrivacyError;
use crate::keyhop::hkdf_derive;
use crate::types::{PrivacyProof, ProofScheme};
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
use aes_gcm::{Aes256Gcm, Key, Nonce, AeadInPlace, KeyInit};
extern crate alloc;
use alloc::{string::String, vec::Vec};
const NONCE_LEN: usize = 12;
const TAG_LEN: usize = 16;
pub type ClearanceTier = u8;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassifiedDocument {
pub doc_id: String,
pub encrypted: Vec<u8>,
pub required_tier: ClearanceTier,
pub policy: Vec<(String, ClearanceTier)>,
}
pub struct QscifViewer;
impl QscifViewer {
pub fn new() -> Self {
Self
}
pub fn prove_clearance(
&self,
user_tier: ClearanceTier,
required_tier: ClearanceTier,
chaos_seed: &[u8; 32],
) -> Result<PrivacyProof, PrivacyError> {
if user_tier < required_tier {
return Err(PrivacyError::ClearanceFailed {
required: required_tier,
got: user_tier,
});
}
let diff = user_tier - required_tier;
let mut hasher = Sha256::new();
hasher.update([diff]);
hasher.update(chaos_seed);
hasher.update(b"clearance-proof-v1");
let commitment: [u8; 32] = hasher.finalize().into();
Ok(PrivacyProof {
proof_bytes: hex::encode(commitment),
public_inputs: hex::encode([required_tier]),
scheme: ProofScheme::Snark,
security_bits: 128,
proof_size: 64,
chsh_value: 0.0,
lyapunov: 4.5,
})
}
pub fn redact_field(
&self,
field_data: &[u8],
clearance_level: u8,
field_name: &str,
clearance_key: &[u8; 32],
) -> Result<Vec<u8>, PrivacyError> {
let mut salt = Vec::with_capacity(field_name.len() + 1);
salt.extend_from_slice(field_name.as_bytes());
salt.push(clearance_level);
let mut aes_key = [0u8; 32];
hkdf_derive(clearance_key, &salt, b"redact-v1", &mut aes_key)
.map_err(|e| PrivacyError::RedactionFailed(alloc::format!("HKDF failed: {:?}", e)))?;
let mut nonce_hasher = Sha256::new();
nonce_hasher.update(field_name.as_bytes());
nonce_hasher.update([clearance_level]);
nonce_hasher.update(b"redact-nonce-v1");
let nonce_hash: [u8; 32] = nonce_hasher.finalize().into();
let nonce_bytes: [u8; NONCE_LEN] = nonce_hash[..NONCE_LEN].try_into()
.map_err(|_| PrivacyError::RedactionFailed("Nonce slice error".into()))?;
let key = Key::<Aes256Gcm>::from_slice(&aes_key);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(&nonce_bytes);
let mut buffer = field_data.to_vec();
buffer.reserve(TAG_LEN);
cipher.encrypt_in_place(nonce, field_name.as_bytes(), &mut buffer)
.map_err(|e| PrivacyError::RedactionFailed(alloc::format!("AES-GCM encrypt: {:?}", e)))?;
let mut output = Vec::with_capacity(NONCE_LEN + buffer.len());
output.extend_from_slice(&nonce_bytes);
output.extend_from_slice(&buffer);
Ok(output)
}
pub fn reveal_field(
&self,
encrypted_field: &[u8],
clearance_key: &[u8; 32],
field_name: &str,
clearance_level: u8,
) -> Result<Vec<u8>, PrivacyError> {
if encrypted_field.len() < NONCE_LEN + TAG_LEN {
return Err(PrivacyError::RedactionFailed(
"Encrypted field too short".into()
));
}
let mut salt = Vec::with_capacity(field_name.len() + 1);
salt.extend_from_slice(field_name.as_bytes());
salt.push(clearance_level);
let mut aes_key = [0u8; 32];
hkdf_derive(clearance_key, &salt, b"redact-v1", &mut aes_key)
.map_err(|e| PrivacyError::RedactionFailed(alloc::format!("HKDF failed: {:?}", e)))?;
let nonce_bytes: [u8; NONCE_LEN] = encrypted_field[..NONCE_LEN].try_into()
.map_err(|_| PrivacyError::RedactionFailed("Nonce slice error".into()))?;
let key = Key::<Aes256Gcm>::from_slice(&aes_key);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(&nonce_bytes);
let mut buffer = encrypted_field[NONCE_LEN..].to_vec();
cipher.decrypt_in_place(nonce, field_name.as_bytes(), &mut buffer)
.map_err(|e| PrivacyError::RedactionFailed(alloc::format!("AES-GCM decrypt: {:?}", e)))?;
Ok(buffer)
}
pub fn view_redacted(
&self,
doc: &ClassifiedDocument,
user_tier: ClearanceTier,
chaos_seed: &[u8; 32],
) -> Result<Vec<u8>, PrivacyError> {
self.prove_clearance(user_tier, doc.required_tier, chaos_seed)?;
let clearance_key: &[u8; 32] = chaos_seed;
let mut viewable = doc.encrypted.clone();
for (section, section_tier) in &doc.policy {
if *section_tier > user_tier {
let section_hash = Sha256::digest(section.as_bytes());
let start = (u64::from_le_bytes(
section_hash[..8].try_into().unwrap_or([0u8; 8])
) as usize) % viewable.len().max(1);
let len = 32.min(viewable.len().saturating_sub(start));
if len == 0 { continue; }
let chunk = &viewable[start..start + len].to_vec();
match self.redact_field(chunk, *section_tier, section, clearance_key) {
Ok(encrypted) => {
for b in &mut viewable[start..start + len] {
*b = 0xFF; }
let _ = encrypted; }
Err(_) => {
for b in &mut viewable[start..start + len] {
*b = 0xFF;
}
}
}
}
}
Ok(viewable)
}
pub fn homomorphic_search(
&self,
doc: &ClassifiedDocument,
keyword_hash: &[u8; 32],
user_tier: ClearanceTier,
chaos_seed: &[u8; 32],
) -> Result<bool, PrivacyError> {
self.prove_clearance(user_tier, doc.required_tier, chaos_seed)?;
let found = doc.encrypted.windows(32).any(|w| w == keyword_hash);
Ok(found)
}
}
impl Default for QscifViewer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_doc() -> ClassifiedDocument {
ClassifiedDocument {
doc_id: "doc-001".into(),
encrypted: vec![1u8; 128],
required_tier: 3,
policy: vec![("section-a".into(), 4), ("section-b".into(), 2)],
}
}
#[test]
fn test_clearance_pass() {
let viewer = QscifViewer::new();
let seed = [0u8; 32];
assert!(viewer.prove_clearance(4, 3, &seed).is_ok());
}
#[test]
fn test_clearance_fail() {
let viewer = QscifViewer::new();
let seed = [0u8; 32];
assert!(viewer.prove_clearance(2, 3, &seed).is_err());
}
#[test]
fn test_view_redacted() {
let viewer = QscifViewer::new();
let seed = [0u8; 32];
let doc = make_doc();
let result = viewer.view_redacted(&doc, 3, &seed).unwrap();
assert_eq!(result.len(), 128);
}
#[test]
fn test_redact_reveal_round_trip() {
let viewer = QscifViewer::new();
let key = [42u8; 32];
let plaintext = b"classified data for tier 4";
let encrypted = viewer.redact_field(plaintext, 4, "section-alpha", &key).unwrap();
let decrypted = viewer.reveal_field(&encrypted, &key, "section-alpha", 4).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_redact_wrong_key_fails() {
let viewer = QscifViewer::new();
let key1 = [1u8; 32];
let key2 = [2u8; 32];
let plaintext = b"secret";
let encrypted = viewer.redact_field(plaintext, 3, "field-x", &key1).unwrap();
assert!(viewer.reveal_field(&encrypted, &key2, "field-x", 3).is_err());
}
}