use num_bigint::{BigInt, BigUint, TryFromBigIntError};
use num_rational::{BigRational, Ratio};
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum DpError {
#[error(
"DP error: input value was not a valid privacy parameter. \
It should to be a non-negative, finite float."
)]
InvalidFloat,
#[error("DP error: input denominator was zero.")]
ZeroDenominator,
#[error("DP error: {0}")]
BigIntConversion(#[from] TryFromBigIntError<BigInt>),
#[error("invalid parameter: {0}")]
InvalidParameter(String),
}
#[derive(Clone, Debug)]
pub struct Rational(Ratio<BigUint>);
impl Rational {
pub fn from_unsigned<T>(n: T, d: T) -> Result<Self, DpError>
where
T: Into<u128>,
{
let d = d.into();
if d == 0 {
Err(DpError::ZeroDenominator)
} else {
Ok(Rational(Ratio::<BigUint>::new(n.into().into(), d.into())))
}
}
}
impl TryFrom<f32> for Rational {
type Error = DpError;
fn try_from(value: f32) -> Result<Self, DpError> {
match BigRational::from_float(value) {
Some(y) => Ok(Rational(Ratio::<BigUint>::new(
y.numer().clone().try_into()?,
y.denom().clone().try_into()?,
))),
None => Err(DpError::InvalidFloat)?,
}
}
}
pub trait DifferentialPrivacyBudget {}
pub trait DifferentialPrivacyDistribution {}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
pub struct ZCdpBudget {
epsilon: Ratio<BigUint>,
}
impl ZCdpBudget {
pub fn new(epsilon: Rational) -> Self {
Self { epsilon: epsilon.0 }
}
}
impl DifferentialPrivacyBudget for ZCdpBudget {}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Ord, PartialOrd)]
pub struct PureDpBudget {
epsilon: Ratio<BigUint>,
}
impl PureDpBudget {
pub fn new(epsilon: Rational) -> Result<Self, DpError> {
if epsilon.0.numer() == &BigUint::ZERO {
return Err(DpError::InvalidParameter("epsilon cannot be zero".into()));
}
Ok(Self { epsilon: epsilon.0 })
}
}
impl DifferentialPrivacyBudget for PureDpBudget {}
mod budget_serde {
use num_bigint::BigUint;
use num_rational::Ratio;
use serde::{de, Deserialize};
#[derive(Deserialize)]
pub struct PureDpBudget {
epsilon: Ratio<BigUint>,
}
impl<'de> Deserialize<'de> for super::PureDpBudget {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let helper = PureDpBudget::deserialize(deserializer)?;
super::PureDpBudget::new(super::Rational(helper.epsilon))
.map_err(|_| de::Error::custom("epsilon cannot be zero"))
}
}
}
pub trait DifferentialPrivacyStrategy {
type Budget: DifferentialPrivacyBudget;
type Distribution: DifferentialPrivacyDistribution;
type Sensitivity;
fn from_budget(b: Self::Budget) -> Self;
fn create_distribution(&self, s: Self::Sensitivity) -> Result<Self::Distribution, DpError>;
}
pub mod distributions;
mod rand_bigint;
#[cfg(test)]
mod tests {
use serde_json::json;
use super::PureDpBudget;
#[test]
fn budget_deserialization() {
serde_json::from_value::<PureDpBudget>(json!({"epsilon": [[1], [1]]})).unwrap();
serde_json::from_value::<PureDpBudget>(json!({"epsilon": [[0], [1]]})).unwrap_err();
serde_json::from_value::<PureDpBudget>(json!({"epsilon": [[1], [0]]})).unwrap_err();
}
}