File size: 7,185 Bytes
518343a
 
 
 
 
 
 
 
 
 
 
 
 
 
e956545
518343a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e956545
518343a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// SPDX-License-Identifier: Apache-2.0
// © 2026 Lutar, Stephen P. — SZL Holdings
// ORCID: 0009-0001-0110-4173
//
// Layer 6 — a11oy policy gate for RDPSequentialComposition (G38)
//
// Policy rationale:
//   For a pipeline of k steps each satisfying (α, εᵢ)-RDP, the sequential
//   composition is (α, Σεᵢ)-RDP (Mironov 2017, Proposition 1).
//   Converting to (ε_dp, δ)-DP: ε_dp = Σεᵢ + ln(1/δ)/(α-1).
//   The gate accepts a chained receipt only if ε_dp ≤ declared budget ceiling.
//
//   Lean theorem cited: `rdpSequentialCompositionAdditivity`
//   Lean file: Lutar/DP/RDPComposition.lean
//   Lean commit SHA: b675cd84caa17080671570c153484c817f8769ac
//   Lean status: theorem (2 sorries for measure-theoretic Rényi divergence;
//                budget arithmetic predicate `rdpBudgetValid` has 0 sorries)
//   Severity: ENFORCED
//
// References:
//   Mironov (2017). "Rényi Differential Privacy."
//   IEEE CSF 2017. arXiv:1702.07476. DOI:10.1109/CSF.2017.11
//   Rényi (1961). Proc. 4th Berkeley Symposium 1, 547–561.
//   https://projecteuclid.org/euclid.bsmsp/1200512181

// ── Inline formula ─────────────────────────────────────────────────────────────
// G38: ε_total = Σᵢ εᵢ  (composition, same α)
//      ε_dp = ε_total + ln(1/δ) / (α - 1)  (conversion to (ε,δ)-DP)
//      Gate passes iff ε_dp ≤ budget_threshold.

export interface RDPCompositionGateConfig {
  /**
   * Default Rényi order α if not declared per-receipt.
   * Must be > 1. Default: 8.0 (common choice for Gaussian mechanism).
   */
  defaultAlpha?: number;
  /**
   * Default δ for RDP → DP conversion if not declared.
   * Default: 1e-5.
   */
  defaultDelta?: number;
}

export interface RDPCompositionGateOpts {
  /** Rényi order α > 1. */
  rdp_alpha: number;
  /** Per-step RDP ε values (one per sequential mechanism call). */
  rdp_epsilon_steps: number[];
  /** δ for the (ε, δ)-DP conversion. */
  dp_delta: number;
  /** Declared ε budget ceiling. */
  budget_epsilon_threshold: number;
}

export interface RDPCompositionDecision {
  allow:                    boolean;
  rationale:                string;
  formula:                  string;
  leanTheorem:              string;
  leanFile:                 string;
  leanCommitSha:            string;
  rdp_alpha:                number;
  rdp_epsilon_total:        number;
  dp_epsilon_converted:     number;
  dp_delta:                 number;
  budget_epsilon_threshold: number;
  rdp_budget_valid:         boolean;
  step_count:               number;
  dsse_extension: {
    rdp_accounting: {
      alpha:                  number;
      steps:                  number[];
      epsilon_total_rdp:      number;
      delta:                  number;
      epsilon_dp_converted:   number;
      budget_threshold:       number;
      budget_valid:           boolean;
      lean_theorem_sha:       string;
    };
  };
}

const LEAN_THEOREM = "rdpSequentialCompositionAdditivity";
const LEAN_FILE    = "Lutar/DP/RDPComposition.lean";
const LEAN_COMMIT  = "b675cd84caa17080671570c153484c817f8769ac";
const FORMULA_STR  =
  "ε_dp = Σᵢεᵢ + ln(1/δ)/(α−1)  [Mironov 2017 Prop.1+3; arXiv:1702.07476; DOI:10.1109/CSF.2017.11]";

/**
 * Compute total (α, Σεᵢ)-RDP cost from steps, then convert to (ε_dp, δ)-DP.
 * Mironov 2017 Proposition 1 (composition) and Proposition 3 (conversion).
 */
function computeRDPBudget(
  alpha: number,
  epsilonSteps: number[],
  delta: number
): { epsilon_total_rdp: number; epsilon_dp_converted: number } {
  // Composition: (α, ε₁)-RDP + (α, ε₂)-RDP = (α, ε₁+ε₂)-RDP
  const epsilon_total_rdp = epsilonSteps.reduce((s, e) => s + e, 0);
  // Conversion: (α, ε_rdp)-RDP → (ε_rdp + ln(1/δ)/(α-1), δ)-DP
  const epsilon_dp_converted = epsilon_total_rdp + Math.log(1 / delta) / (alpha - 1);
  return { epsilon_total_rdp, epsilon_dp_converted };
}

/**
 * RDPSequentialComposition (G38) policy gate.
 *
 * Verifies that the cumulative RDP privacy budget across k sequential
 * mechanism calls, converted to (ε, δ)-DP, does not exceed the declared ceiling.
 *
 * Lean theorem: `rdpSequentialCompositionAdditivity` (Lutar/DP/RDPComposition.lean)
 * Reference: Mironov (2017) arXiv:1702.07476.
 */
export function rdpCompositionGate(
  config: RDPCompositionGateConfig = {}
): (opts: RDPCompositionGateOpts) => RDPCompositionDecision {
  const defaultAlpha = config.defaultAlpha ?? 8.0;
  const defaultDelta = config.defaultDelta ?? 1e-5;

  return (opts: RDPCompositionGateOpts): RDPCompositionDecision => {
    const {
      rdp_alpha,
      rdp_epsilon_steps,
      dp_delta,
      budget_epsilon_threshold,
    } = opts;

    // --- Input validation ---
    if (!Number.isFinite(rdp_alpha) || rdp_alpha <= 1) {
      throw new Error(`RDPCompositionGate: rdp_alpha must be > 1; got ${rdp_alpha}`);
    }
    if (!Array.isArray(rdp_epsilon_steps) || rdp_epsilon_steps.length === 0) {
      throw new Error("RDPCompositionGate: rdp_epsilon_steps must be a non-empty array");
    }
    for (const e of rdp_epsilon_steps) {
      if (!Number.isFinite(e) || e < 0) {
        throw new Error(`RDPCompositionGate: all RDP ε steps must be ≥ 0; got ${e}`);
      }
    }
    if (!Number.isFinite(dp_delta) || dp_delta <= 0 || dp_delta >= 1) {
      throw new Error(`RDPCompositionGate: dp_delta must be in (0,1); got ${dp_delta}`);
    }

    // --- Core accounting ---
    const { epsilon_total_rdp, epsilon_dp_converted } = computeRDPBudget(
      rdp_alpha, rdp_epsilon_steps, dp_delta
    );
    const rdp_budget_valid = epsilon_dp_converted <= budget_epsilon_threshold;

    const rationale = rdp_budget_valid
      ? `Lean:${LEAN_THEOREM} — RDP budget valid. ε_rdp=${epsilon_total_rdp.toFixed(6)}, ε_dp=${epsilon_dp_converted.toFixed(6)} ≤ budget=${budget_epsilon_threshold}. α=${rdp_alpha}, k=${rdp_epsilon_steps.length} steps.`
      : `Lean:${LEAN_THEOREM} — DENY: ε_dp=${epsilon_dp_converted.toFixed(6)} exceeds budget=${budget_epsilon_threshold}. (α=${rdp_alpha}, Σεᵢ=${epsilon_total_rdp.toFixed(6)}, δ=${dp_delta})`;

    return {
      allow:                    rdp_budget_valid,
      rationale,
      formula:                  FORMULA_STR,
      leanTheorem:              LEAN_THEOREM,
      leanFile:                 LEAN_FILE,
      leanCommitSha:            LEAN_COMMIT,
      rdp_alpha,
      rdp_epsilon_total:        epsilon_total_rdp,
      dp_epsilon_converted:     epsilon_dp_converted,
      dp_delta,
      budget_epsilon_threshold,
      rdp_budget_valid,
      step_count:               rdp_epsilon_steps.length,
      dsse_extension: {
        rdp_accounting: {
          alpha:                  rdp_alpha,
          steps:                  rdp_epsilon_steps,
          epsilon_total_rdp,
          delta:                  dp_delta,
          epsilon_dp_converted,
          budget_threshold:       budget_epsilon_threshold,
          budget_valid:           rdp_budget_valid,
          lean_theorem_sha:       LEAN_COMMIT,
        },
      },
    };
  };
}