use rand::Rng;
#[derive(Debug, Clone)]
pub struct DpConfig {
pub epsilon: f64, pub delta: f64, pub sensitivity: f64, }
impl Default for DpConfig {
fn default() -> Self {
Self {
epsilon: 1.0,
delta: 1e-5,
sensitivity: 1.0,
}
}
}
pub struct DifferentialPrivacy {
config: DpConfig,
}
impl DifferentialPrivacy {
pub fn new(config: DpConfig) -> Self {
Self { config }
}
pub fn laplace_noise(&self, value: f64) -> f64 {
let scale = self.config.sensitivity / self.config.epsilon;
let noise = self.sample_laplace(scale);
value + noise
}
pub fn add_noise_to_vector(&self, vector: &mut [f32]) {
let scale = self.config.sensitivity / self.config.epsilon;
for v in vector.iter_mut() {
let noise = self.sample_laplace(scale);
*v += noise as f32;
}
}
pub fn gaussian_noise(&self, value: f64) -> f64 {
let sigma = self.gaussian_sigma();
let noise = self.sample_gaussian(sigma);
value + noise
}
fn gaussian_sigma(&self) -> f64 {
let c = (2.0 * (1.25 / self.config.delta).ln()).sqrt();
c * self.config.sensitivity / self.config.epsilon
}
fn sample_laplace(&self, scale: f64) -> f64 {
let mut rng = rand::thread_rng();
let u: f64 = rng.gen::<f64>() - 0.5;
let clamped = (1.0 - 2.0 * u.abs()).clamp(f64::EPSILON, 1.0);
-scale * u.signum() * clamped.ln()
}
fn sample_gaussian(&self, sigma: f64) -> f64 {
let mut rng = rand::thread_rng();
let u1: f64 = rng.gen::<f64>().clamp(f64::EPSILON, 1.0 - f64::EPSILON);
let u2: f64 = rng.gen();
sigma * (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
pub fn privacy_loss(&self, num_queries: usize) -> f64 {
self.config.epsilon * (num_queries as f64)
}
pub fn advanced_privacy_loss(&self, num_queries: usize) -> f64 {
let k = num_queries as f64;
(2.0 * k * (1.0 / self.config.delta).ln()).sqrt() * self.config.epsilon
+ k * self.config.epsilon * (self.config.epsilon.exp() - 1.0)
}
}