Differential privacy
Float-safe differential-privacy mechanisms with per-scope privacy budgeting. Differential privacy adds calibrated noise to an aggregate so the output is provably insensitive to any single record — but the guarantee is fragile: Mironov (2012) showed that a Laplace mechanism implemented with naive double-precision sampling lets an attacker distinguish neighbouring datasets with > 35% probability from a single output, silently destroying the promise. This module ships only mechanisms whose sampling is hardened against that class of attack:
- Laplace via the snapping mechanism (Mironov 2012): clamp to a bound, draw a CSPRNG sign + full-mantissa uniform, then round to a power-of-two grid — the rounding removes the exploitable low-order mantissa bits. Pure ε-differential privacy. - Discrete Gaussian (Canonne–Kamath–Steinke 2020): integer-exact rejection sampling built from Bernoulli(exp(−γ)) over exact rationals — no floating-point noise at all. (ε, δ)-differential privacy, integer-valued.
All randomness comes from b.crypto.generateBytes (SHAKE256 over the OS CSPRNG), never Math.random.
b.ai.dp.budget({ scope, epsilon, delta }) tracks a privacy budget per scope (per-user / per-tenant / per-query-class) and refuses a consume that would exceed it. Composition is accounted two ways:
- "basic" (default) — sum the per-release ε and δ. Always valid; conservative. - "rdp" — a Rényi DP accountant (Mironov 2017) tracks RDP across a grid of orders and converts to (ε, δ) at the scope's δ, giving a much tighter bound under repeated Gaussian releases. Requires delta > 0.
NIST SP 800-226 (2025) is the evaluation standard for these guarantees; Dwork & Roth, "The Algorithmic Foundations of Differential Privacy", is the canonical reference.
The exponential and sparse-vector mechanisms are deferred-with-condition: their float-safe constructions (the base-2 / permute-and-flip exponential mechanism, Ilvento 2019; a snapped sparse-vector) are a distinct effort, and shipping them float-unsafe would defeat the module's purpose. They re-open on operator demand with the named construction.
b.ai.dp.mechanism(opts) #
{
{
type: string, // "laplace" | "gaussian"
sensitivity: number, // required, > 0 (L1 for laplace, L1/integer for gaussian)
epsilon: number, // required, > 0 (per-release ε; ε ≤ 1 for the
// classic Gaussian calibration)
delta?: number, // gaussian only, required, 0 < δ < 1
bound?: number, // laplace only, required, > 0 — clamp bound B
}
}
Build a float-safe DP noise mechanism. type: "laplace" is the snapping mechanism (pure ε-DP, real-valued, needs a bound); type: "gaussian" is the discrete Gaussian (integer-valued, (ε, δ)-DP, needs delta). Pass the result to budget.consume(mechanism, value).
var lap = b.ai.dp.mechanism({ type: "laplace", sensitivity: 1, epsilon: 0.5, bound: 1000 });
var gss = b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.5, delta: 1e-6 });
b.ai.dp.budget(opts) #
{
{
scope: string, // required, the budget scope id
epsilon: number, // required, total ε budget (> 0)
delta?: number, // total δ budget (>= 0; required > 0 for rdp / gaussian)
accounting?: string, // "basic" (default) | "rdp"
audit?: boolean, // default: true
}
}
Track a differential-privacy budget for one scope (per-user / per-tenant / per-query-class) and refuse a release that would exceed it. Returns { consume, remaining, spent, reset }. consume(mechanism, value) adds the mechanism's noise, charges the accountant, and throws aiDp/budget-exhausted if the release would push the scope past its (ε, δ). With accounting: "rdp" the charge is accounted via Rényi DP for a tight composition bound (requires delta > 0); "basic" (default) sums per-release ε and δ.
var b1 = b.ai.dp.budget({ scope: "tenant-acme:daily", epsilon: 3, delta: 1e-6, accounting: "rdp" });
var m = b.ai.dp.mechanism({ type: "gaussian", sensitivity: 1, epsilon: 0.5, delta: 1e-6 });
var out = b1.consume(m, trueCount);
// → { value: , cost: { epsilon: 0.5, delta: 1e-6 }, remaining: { epsilon, delta } }
Last updated 2026-08-08T16:39:49.652Z by seeder.