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) #

stable0.12.29gdprsoc2
{
  {
    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) #

stable0.12.29gdprsoc2
{
  {
    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.