use crate::error::PrivacyError;
use crate::types::{DpMechanism, DpNoiseFrame, PrivacyProof, ProofScheme};
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::{string::String, vec::Vec};
pub const RENYI_ALPHA: u32 = 64;
pub const EPSILON_MAX: f64 = 1e-6;
pub const DELTA_MAX: f64 = 1e-5;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DpConfig {
pub renyi_alpha: u32,
pub epsilon_max: f64,
pub delta_max: f64,
pub buffer_cap: usize,
pub mechanism: DpMechanism,
}
impl Default for DpConfig {
fn default() -> Self {
Self {
renyi_alpha: RENYI_ALPHA,
epsilon_max: EPSILON_MAX,
delta_max: DELTA_MAX,
buffer_cap: 10_000,
mechanism: DpMechanism::Gaussian,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivacyQuery {
pub id: String,
pub sensitivity: f64,
pub epsilon: f64,
pub delta: f64,
pub timestamp_ms: u64,
}
pub struct DpEngine {
config: DpConfig,
epsilon_consumed: f64,
delta_consumed: f64,
buffer: Vec<PrivacyQuery>,
query_count: u64,
}
impl DpEngine {
pub fn new() -> Self {
Self::with_config(DpConfig::default())
}
pub fn with_config(config: DpConfig) -> Self {
Self {
config,
epsilon_consumed: 0.0,
delta_consumed: 0.0,
buffer: Vec::new(),
query_count: 0,
}
}
pub fn apply_dp(
&mut self,
query: PrivacyQuery,
chaos_perturbation: f64,
) -> Result<DpNoiseFrame, PrivacyError> {
if query.sensitivity <= 0.0 || query.sensitivity > 4096.0 {
return Err(PrivacyError::InvalidSensitivity(query.sensitivity));
}
let new_epsilon = self.epsilon_consumed + query.epsilon;
if new_epsilon > self.config.epsilon_max * 1000.0 {
return Err(PrivacyError::PrivacyBudgetExhausted {
epsilon: new_epsilon,
limit: self.config.epsilon_max * 1000.0,
});
}
let base_scale = match self.config.mechanism {
DpMechanism::Laplace => query.sensitivity / query.epsilon,
DpMechanism::Gaussian => {
let sigma_sq = 2.0 * (1.25 / query.delta.max(1e-10)).ln() / (query.epsilon * query.epsilon);
sigma_sq.sqrt()
}
};
let noise_scale = base_scale * (1.0 + chaos_perturbation.abs() * 0.05);
self.epsilon_consumed += query.epsilon;
self.delta_consumed += query.delta;
self.query_count += 1;
self.buffer.push(query.clone());
if self.buffer.len() >= self.config.buffer_cap * 9 / 10 {
self.flush_buffer();
}
let renyi_bound = self.compute_renyi_bound(query.epsilon, self.config.renyi_alpha);
Ok(DpNoiseFrame {
renyi_alpha: self.config.renyi_alpha,
bound: renyi_bound,
mechanism: self.config.mechanism,
epsilon: query.epsilon,
noise_scale,
})
}
pub fn noise_sample(
&self,
noise_scale: f64,
chaos_seed: &[u8; 32],
) -> f64 {
let seed_val = u64::from_le_bytes(chaos_seed[..8].try_into().unwrap_or([0u8; 8]));
let uniform = (seed_val as f64) / (u64::MAX as f64);
match self.config.mechanism {
DpMechanism::Laplace => {
let u = uniform - 0.5;
let sign = if u >= 0.0 { 1.0 } else { -1.0 };
let abs_u = u.abs().min(0.4999); -noise_scale * sign * (1.0 - 2.0 * abs_u).ln()
}
DpMechanism::Gaussian => {
let u1 = uniform.max(1e-10);
let u2 = ((seed_val.wrapping_mul(6364136223846793005)) as f64) / (u64::MAX as f64);
let z = (-2.0 * u1.ln()).sqrt() * (2.0 * core::f64::consts::PI * u2).cos();
noise_scale * z
}
}
}
pub fn check_budget(&self) -> Result<(), PrivacyError> {
if self.epsilon_consumed > self.config.epsilon_max * 900.0 {
return Err(PrivacyError::PrivacyBudgetExhausted {
epsilon: self.epsilon_consumed,
limit: self.config.epsilon_max * 1000.0,
});
}
Ok(())
}
pub fn prove_compliance(&self) -> PrivacyProof {
let mut hasher = Sha256::new();
hasher.update(self.epsilon_consumed.to_le_bytes());
hasher.update(self.delta_consumed.to_le_bytes());
hasher.update(self.query_count.to_le_bytes());
hasher.update(b"dp-compliance-v1");
let commitment: [u8; 32] = hasher.finalize().into();
PrivacyProof {
proof_bytes: hex::encode(commitment),
public_inputs: hex::encode(self.epsilon_consumed.to_le_bytes()),
scheme: ProofScheme::Snark,
security_bits: 128,
proof_size: 64,
chsh_value: 0.0,
lyapunov: 4.5,
}
}
pub fn epsilon_consumed(&self) -> f64 {
self.epsilon_consumed
}
pub fn query_count(&self) -> u64 {
self.query_count
}
pub fn reset_budget(&mut self) {
self.epsilon_consumed = 0.0;
self.delta_consumed = 0.0;
self.buffer.clear();
}
fn compute_renyi_bound(&self, epsilon: f64, alpha: u32) -> f64 {
(alpha as f64) * epsilon * epsilon / 2.0
}
fn flush_buffer(&mut self) {
self.buffer.clear();
}
}
impl Default for DpEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_query(eps: f64) -> PrivacyQuery {
PrivacyQuery {
id: "q1".into(),
sensitivity: 1.0,
epsilon: eps,
delta: 1e-5,
timestamp_ms: 0,
}
}
#[test]
fn test_apply_dp() {
let mut engine = DpEngine::new();
let frame = engine.apply_dp(make_query(1e-6), 0.1).unwrap();
assert!(frame.noise_scale > 0.0);
assert_eq!(frame.mechanism, DpMechanism::Gaussian);
}
#[test]
fn test_noise_sample_laplace() {
let config = DpConfig { mechanism: DpMechanism::Laplace, ..DpConfig::default() };
let engine = DpEngine::with_config(config);
let seed = [42u8; 32];
let noise = engine.noise_sample(1.0, &seed);
assert!(noise.is_finite());
}
#[test]
fn test_prove_compliance() {
let engine = DpEngine::new();
let proof = engine.prove_compliance();
assert!(!proof.proof_bytes.is_empty());
}
}