blob: 575310a79796fa69064803f5dcb73933a3bbb0b5 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "accounting/privacy_loss_mechanism.h"
#include "base/status_macros.h"
namespace differential_privacy {
namespace accounting {
base::StatusOr<std::unique_ptr<LaplacePrivacyLoss>> LaplacePrivacyLoss::Create(
double parameter, double sensitivity) {
if (parameter <= 0) {
return absl::InvalidArgumentError("parameter should be positive.");
}
return absl::WrapUnique(new LaplacePrivacyLoss(parameter, sensitivity));
}
base::StatusOr<std::unique_ptr<LaplacePrivacyLoss>> LaplacePrivacyLoss::Create(
const EpsilonDelta& epsilon_delta) {
RETURN_IF_ERROR(epsilon_delta.Validate());
constexpr double sensitivity = 1;
const double parameter = sensitivity / epsilon_delta.epsilon;
return Create(parameter, sensitivity);
}
double LaplacePrivacyLoss::InversePrivacyLoss(double privacy_loss) const {
if (privacy_loss > sensitivity_ / parameter_) {
return -std::numeric_limits<double>::infinity();
}
if (privacy_loss <= -sensitivity_ / parameter_) {
return std::numeric_limits<double>::infinity();
}
return 0.5 * (sensitivity_ - privacy_loss * parameter_);
}
double LaplacePrivacyLoss::NoiseCdf(double x) const {
return boost::math::cdf(distribution_, x);
}
double LaplacePrivacyLoss::PrivacyLoss(double x) const {
return (abs(x - sensitivity_) - abs(x)) / parameter_;
}
PrivacyLossTail LaplacePrivacyLoss::PrivacyLossDistributionTail() const {
ProbabilityMassFunctionOf<double> pmf;
// When x <= 0, the privacy loss is simply sensitivity / parameter; this
// happens with probability 0.5.
pmf[sensitivity_ / parameter_] = 0.5;
// When x >= sensitivity, the privacy loss is simply
// - sensitivity / parameter; this happens with probability
// 1 - CDF(sensitivity) = CDF(-sensitivity).
pmf[-sensitivity_ / parameter_] =
boost::math::cdf(distribution_, -sensitivity_);
return PrivacyLossTail{0, sensitivity_, pmf};
}
base::StatusOr<std::unique_ptr<GaussianPrivacyLoss>>
GaussianPrivacyLoss::Create(double standard_deviation, double sensitivity,
EstimateType estimate_type,
double mass_truncation_bound) {
if (standard_deviation <= 0) {
return absl::InvalidArgumentError("standard_deviation should be positive.");
}
return absl::WrapUnique(new GaussianPrivacyLoss(
standard_deviation, sensitivity, estimate_type, mass_truncation_bound));
}
base::StatusOr<std::unique_ptr<GaussianPrivacyLoss>>
GaussianPrivacyLoss::Create(const EpsilonDelta& epsilon_delta,
EstimateType estimate_type,
double mass_truncation_bound) {
RETURN_IF_ERROR(epsilon_delta.Validate());
constexpr double sensitivity = 1;
// Use binary search to find the smallest possible standard deviation of the
// Gaussian noise for which the protocol is (epsilon, delta)-differentially
// private.
// The initial standard deviation is set to
// sqrt(2 * ln(1.5/delta) * sensitivity / epsilon. It is known that, when
// epsilon is no more than one, the Gaussian mechanism with this standard
// deviation is (epsilon, delta)-DP. See e.g. Appendix A in Dwork and Roth
// book, "The Algorithmic Foundations of Differential Privacy".
double initial_standard_deviation =
sqrt(2 * std::log(1.5 / epsilon_delta.delta)) * sensitivity /
epsilon_delta.epsilon;
BinarySearchParameters search_parameters = {
/*lower_bound=*/0,
/*upper_bound=*/std::numeric_limits<double>::infinity(),
/*.initial_guess=*/initial_standard_deviation};
auto compute_delta = [estimate_type,
epsilon_delta](double standard_deviation) {
auto privacy_loss = GaussianPrivacyLoss::Create(
standard_deviation, sensitivity, estimate_type,
/*mass_truncation_bound=*/0);
return privacy_loss.value()->GetDeltaForEpsilon(epsilon_delta.epsilon);
};
auto inverse_result = InverseMonotoneFunction(
compute_delta, epsilon_delta.delta, search_parameters);
return GaussianPrivacyLoss::Create(inverse_result.value(), sensitivity,
estimate_type, mass_truncation_bound);
}
base::StatusOr<std::unique_ptr<GaussianPrivacyLoss>>
GaussianPrivacyLoss::Compose(int num_times) {
// The composition with itself num_times is the same as the
// GaussianPrivacyLoss with sensitivity scaled up by a factor of square root
// of num_times.
return GaussianPrivacyLoss::Create(standard_deviation_,
sensitivity_ * sqrt(num_times),
estimate_type_, mass_truncation_bound_);
}
double GaussianPrivacyLoss::InversePrivacyLoss(double privacy_loss) const {
return 0.5 * sensitivity_ -
privacy_loss * (pow(standard_deviation_, 2) / sensitivity_);
}
double GaussianPrivacyLoss::NoiseCdf(double x) const {
return boost::math::cdf(distribution_, x);
}
double GaussianPrivacyLoss::PrivacyLoss(double x) const {
return 0.5 * sensitivity_ * (sensitivity_ - 2 * x) /
pow(standard_deviation_, 2);
}
PrivacyLossTail GaussianPrivacyLoss::PrivacyLossDistributionTail() const {
// We set lower_x_truncation so that CDF(lower_x_truncation) =
// 0.5 * exp(log_mass_truncation_bound), and then set upper_x_truncation
// to be -lower_x_truncation.
const double p = 0.5 * std::exp(mass_truncation_bound_);
const double lower_x_truncation = boost::math::quantile(distribution_, p);
const double upper_x_truncation = -lower_x_truncation;
ProbabilityMassFunctionOf<double> pmf;
if (estimate_type_ == EstimateType::kPessimistic) {
pmf[std::numeric_limits<double>::infinity()] = p;
pmf[PrivacyLoss(upper_x_truncation)] = p;
} else {
pmf[PrivacyLoss(lower_x_truncation)] = p;
}
return PrivacyLossTail{lower_x_truncation, upper_x_truncation, pmf};
}
base::StatusOr<std::unique_ptr<DiscreteLaplacePrivacyLoss>>
DiscreteLaplacePrivacyLoss::Create(double parameter, double sensitivity) {
if (parameter <= 0) {
return absl::InvalidArgumentError("parameter should be positive.");
}
if (sensitivity != ceil(sensitivity)) {
return absl::InvalidArgumentError(
"sensitivity for discrete Laplace mechanism should be an integer.");
}
return absl::WrapUnique(
new DiscreteLaplacePrivacyLoss(parameter, sensitivity));
}
base::StatusOr<std::unique_ptr<DiscreteLaplacePrivacyLoss>>
DiscreteLaplacePrivacyLoss::Create(const EpsilonDelta& epsilon_delta,
const double sensitivity) {
RETURN_IF_ERROR(epsilon_delta.Validate());
if (sensitivity != ceil(sensitivity) || sensitivity <= 0) {
return absl::InvalidArgumentError(
"sensitivity for discrete Laplace mechanism should be a positive "
"integer.");
}
const double parameter = epsilon_delta.epsilon / sensitivity;
return Create(parameter, sensitivity);
}
double DiscreteLaplacePrivacyLoss::InversePrivacyLoss(
double privacy_loss) const {
if (privacy_loss > sensitivity_ * parameter_) {
return -std::numeric_limits<double>::infinity();
}
if (privacy_loss <= -sensitivity_ * parameter_) {
return std::numeric_limits<double>::infinity();
}
return floor(0.5 * (sensitivity_ - privacy_loss / parameter_));
}
double DiscreteLaplacePrivacyLoss::NoiseCdf(double x) const {
double floor_x = floor(x);
if (floor_x < 0) {
return exp(parameter_ * (floor_x + 1)) / (exp(parameter_) + 1);
} else {
return 1 - exp(-parameter_ * floor_x) / (exp(parameter_) + 1);
}
}
double DiscreteLaplacePrivacyLoss::PrivacyLoss(double x) const {
return (abs(x - sensitivity_) - abs(x)) * parameter_;
}
PrivacyLossTail DiscreteLaplacePrivacyLoss::PrivacyLossDistributionTail()
const {
ProbabilityMassFunctionOf<double> pmf;
// When x <= 0, the privacy loss is simply sensitivity * parameter; this
// happens with probability CDF(0).
pmf[sensitivity_ * parameter_] = NoiseCdf(0);
// When x >= sensitivity, the privacy loss is simply
// - sensitivity * parameter; this happens with probability
// 1 - CDF(sensitivity - 1) = CDF(-sensitivity).
pmf[-sensitivity_ * parameter_] = NoiseCdf(-sensitivity_);
return PrivacyLossTail{1, sensitivity_ - 1, pmf};
}
base::StatusOr<std::unique_ptr<DiscreteGaussianPrivacyLoss>>
DiscreteGaussianPrivacyLoss::Create(double sigma, int sensitivity,
int truncation_bound) {
if (sigma <= 0) {
return absl::InvalidArgumentError("sigma should be positive.");
}
if (truncation_bound < 0) {
// Tail bound from Canonne et al. ensures that the mass that gets
// truncated is at most 1e-30. (See Proposition 1 in the supplementary
// material.)
truncation_bound = ceil(11.6 * sigma);
}
ProbabilityMassFunction noise_pmf;
CumulativeDensityFunction noise_cdf;
for (int x = -truncation_bound; x <= truncation_bound; ++x) {
noise_pmf[x] = exp(-0.5 * pow(x, 2) / pow(sigma, 2));
noise_cdf[x] = noise_cdf[x - 1] + noise_pmf[x];
}
for (int x = -truncation_bound; x <= truncation_bound; ++x) {
noise_pmf[x] /= noise_cdf[truncation_bound];
noise_cdf[x] /= noise_cdf[truncation_bound];
}
return absl::WrapUnique(new DiscreteGaussianPrivacyLoss(
sigma, sensitivity, truncation_bound, noise_pmf, noise_cdf));
}
base::StatusOr<std::unique_ptr<DiscreteGaussianPrivacyLoss>>
DiscreteGaussianPrivacyLoss::Create(const EpsilonDelta& epsilon_delta,
int sensitivity, int truncation_bound) {
// Use binary search to find the smallest possible sigma of the Discrete
// Gaussian noise for which the protocol is (epsilon, delta)-differentially
// private.
// The initial standard deviation is set to
// sqrt(2 * ln(1.5/delta)) * sensitivity / epsilon. It is known that, when
// epsilon is no more than one, the continuous Gaussian mechanism with this
// sigma is (epsilon, delta)-DP. See e.g. Appendix A in Dwork and Roth
// book, "The Algorithmic Foundations of Differential Privacy".
double initial_sigma = sqrt(2 * std::log(1.5 / epsilon_delta.delta)) *
sensitivity / epsilon_delta.epsilon;
BinarySearchParameters search_parameters = {
.lower_bound = 0,
.upper_bound = std::numeric_limits<double>::infinity(),
.initial_guess = initial_sigma};
auto compute_delta = [sensitivity, epsilon_delta, truncation_bound](
double sigma) -> base::StatusOr<double> {
ASSIGN_OR_RETURN(std::unique_ptr<DiscreteGaussianPrivacyLoss> privacy_loss,
DiscreteGaussianPrivacyLoss::Create(sigma, sensitivity,
truncation_bound));
return privacy_loss->GetDeltaForEpsilon(epsilon_delta.epsilon);
};
ASSIGN_OR_RETURN(double sigma,
InverseMonotoneFunction(compute_delta, epsilon_delta.delta,
search_parameters));
return DiscreteGaussianPrivacyLoss::Create(sigma, sensitivity,
truncation_bound);
}
double DiscreteGaussianPrivacyLoss::InversePrivacyLoss(
double privacy_loss) const {
return floor(0.5 * sensitivity_ -
privacy_loss * (pow(sigma_, 2) / sensitivity_));
}
double DiscreteGaussianPrivacyLoss::NoiseCdf(double x) const {
if (x >= truncation_bound_) return 1;
if (x < -truncation_bound_) return 0;
return noise_cdf_.at(floor(x));
}
double DiscreteGaussianPrivacyLoss::PrivacyLoss(double x) const {
if (x >= sensitivity_ - truncation_bound_)
return 0.5 * sensitivity_ * (sensitivity_ - 2 * x) / pow(sigma_, 2);
return std::numeric_limits<double>::infinity();
}
PrivacyLossTail DiscreteGaussianPrivacyLoss::PrivacyLossDistributionTail()
const {
// When x < -truncation_bound + sensitivity, the privacy loss is infinity.
// Due to truncation, x > truncation_bound never occurs.
return PrivacyLossTail{static_cast<double>(sensitivity_ - truncation_bound_),
static_cast<double>(truncation_bound_),
{{std::numeric_limits<double>::infinity(),
NoiseCdf(sensitivity_ - truncation_bound_ - 1)}}};
}
double DiscreteGaussianPrivacyLoss::StandardDeviation() const {
double variance = 0;
for (auto [x, p] : noise_pmf_) variance += (p * pow(x, 2));
return sqrt(variance);
}
} // namespace accounting
} // namespace differential_privacy