clutch.gen_diffpriv.lib.uniform
Reusable uniform-sampling library for the generic DP logic.
Importing this file and providing a SampleIn uniform_family S instance makes
the Uniform surface notation and the wp_couple_uniform mechanism rule
available at WHATEVER index the family occupies in the signature S — the
index is recovered from the SampleIn instance via sample_idx, never
hardcoded (mirroring lib/laplace.v).
The headline content is the 0-cost SHIFT coupling: two uniform draws on
{0,…,N} couple exactly (no ε, no δ) with the spec output equal to the
impl output rotated by k mod N+1. This is the foundation on which the
δ-bearing collision-avoidance variants (deferred to the crypto port) build.
A client development therefore needs only:
Definition Suni : Sig := ...; uniform_family; ....
[global] Instance : SampleIn uniform_family Suni := ltac:(solve_SampleIn).
Section foo. Context `{!diffprivGS Suni Σ}.
and then writes [Uniform N ()] in programs and applies [wp_couple_uniform]
in proofs.
From Stdlib Require Import ZArith.
From iris.proofmode Require Import proofmode.
From clutch.prob Require Import differential_privacy distribution couplings couplings_app couplings_dp.
(* re-export the client-facing layer so Require Import uniform alone provides
the WP rules, coupling seams, proof-mode tactics, notation and families. *)
From clutch.gen_diffpriv Require Export primitive_laws coupling_rules proofmode diffpriv_rules.
From clutch.gen_prob_lang Require Export families.
From iris.prelude Require Import options.
Local Open Scope R.
From iris.proofmode Require Import proofmode.
From clutch.prob Require Import differential_privacy distribution couplings couplings_app couplings_dp.
(* re-export the client-facing layer so Require Import uniform alone provides
the WP rules, coupling seams, proof-mode tactics, notation and families. *)
From clutch.gen_diffpriv Require Export primitive_laws coupling_rules proofmode diffpriv_rules.
From clutch.gen_prob_lang Require Export families.
From iris.prelude Require Import options.
Local Open Scope R.
Uniform N tape samples the uniform family at parameter N (a Z literal,
the inclusive bound) using sample tape tape (#() for a direct, tape-less
sample). The family's index in the ambient signature is recovered from the
SampleIn uniform_family _ instance — NOT hardcoded.
Value-form of Uniform (direct, tape-less): the bound already reduced to a
LitV (LitInt _), as it appears AFTER wp_pures/tp_pures. The coupling
rules below are stated on UniformV so their preconditions match
post-reduction goals; the couple_uniform tactic relies on this shape.
Notation UniformV N :=
(Sample (sample_idx (D := uniform_family))
(Val (LitV (LitInt N))) (Val (LitV LitUnit)))
(only parsing).
Section uniform.
Context {Sg : Sig} `{!SampleIn uniform_family Sg} `{!diffprivGS Sg Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang Sg)).
(* the family's index in Sg, recovered from the SampleIn instance *)
Local Notation uidx := (@sample_idx uniform_family Sg _).
(Sample (sample_idx (D := uniform_family))
(Val (LitV (LitInt N))) (Val (LitV LitUnit)))
(only parsing).
Section uniform.
Context {Sg : Sig} `{!SampleIn uniform_family Sg} `{!diffprivGS Sg Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang Sg)).
(* the family's index in Sg, recovered from the SampleIn instance *)
Local Notation uidx := (@sample_idx uniform_family Sg _).
The core uniform draw shift coupling, on the family's outcome type Z:
shifting the draw by k couples the two draws EXACTLY (cost (0,0)), with
the spec output equal to the impl output rotated by k mod N+1 (and the
impl output recorded in range 0,N). Family-level fact (independent of
Sg), discharged by the reusable DPcoupl_dunifP_shift.
Lemma DPcoupl_uniform_draw (N : nat) (k : Z) :
DPcoupl (sf_sample uniform_family N)
(sf_sample uniform_family N)
(λ z z', z' = (z + k) `mod` Z.of_nat (S N) ∧ 0 <= z <= Z.of_nat N)%Z 0 0.
Proof. simpl. apply DPcoupl_dunifP_shift. Qed.
DPcoupl (sf_sample uniform_family N)
(sf_sample uniform_family N)
(λ z z', z' = (z + k) `mod` Z.of_nat (S N) ∧ 0 <= z <= Z.of_nat N)%Z 0 0.
Proof. simpl. apply DPcoupl_dunifP_shift. Qed.
The uniform-shift mechanism at the WP level: sampling at bound z (impl)
vs the same bound z (spec), both Z literals #z with 0 ≤ z, couples
to outputs related by the cyclic shift out' = (out + k) mod (z+1), at ZERO
cost (no ↯m, no ↯). Obtained by instantiating the generic prog-couple
seam wp_couple_sample_gen with DPcoupl_uniform_draw — at the recovered
index uidx, so this one rule serves every signature containing
uniform_family.
Lemma wp_couple_uniform (z k : Z) K E :
(0 <= z)%Z →
{{{ ⤇ fill K (Sample uidx (Val (LitV (LitInt z))) (Val (LitV LitUnit))) }}}
Sample uidx (Val (LitV (LitInt z))) (Val (LitV LitUnit)) @ E
{{{ (out : Z), RET #out;
⤇ fill K #((out + k) `mod` (z + 1))%Z ∗ ⌜(0 <= out <= z)%Z⌝ }}}.
Proof.
iIntros (Hz Φ) "Hr HΦ".
iMod ecm_zero as "Hε".
(* z = Z.of_nat (Z.to_nat z) under 0 ≤ z, so the value param #z equals
sf_param_to_val uniform_family (Z.to_nat z); rewrite the WHOLE goal to
N so the program subject matches the wp_couple_sample_gen rule. *)
set (N := Z.to_nat z).
assert (HzN : z = Z.of_nat N) by (subst N; rewrite Z2Nat.id //).
rewrite HzN.
assert (Hbound : (Z.of_nat (S N) = Z.of_nat N + 1)%Z) by (rewrite Nat2Z.inj_succ; lia).
iApply (wp_couple_sample_gen Sg uidx
(sf_param_to_val uniform_family N)
(sf_param_to_val uniform_family N)
(dmap (sf_inj uniform_family) (sf_sample uniform_family N))
(dmap (sf_inj uniform_family) (sf_sample uniform_family N))
(λ v v', ∃ out : Z, v = #out ∧ v' = #((out + k) `mod` (Z.of_nat N + 1))%Z
∧ (0 <= out <= Z.of_nat N)%Z) K E 0
(sig_sample_at uniform_family Sg N)
(sig_sample_at uniform_family Sg N) _ with "[Hr Hε]").
{ (* the param value #N matches sf_param_to_val uniform_family N *)
simpl. iFrame. }
{ iIntros "!>" (v v') "(Hspec & %HR)".
destruct HR as (out & -> & -> & Hb). iApply ("HΦ" $! out with "[$Hspec //]"). }
(* the single DP obligation: lift the uniform draw coupling through sf_inj *)
Unshelve.
apply DPcoupl_map; [lra | lra | ].
eapply (DPcoupl_mono _ _ _ _
(λ z0 z', z' = (z0 + k) `mod` Z.of_nat (S N) ∧ 0 <= z0 <= Z.of_nat N)%Z
_ 0 0 0 0).
- intros; reflexivity.
- intros; reflexivity.
- intros x y (Hxy & Hrange). exists x. simpl.
split; [reflexivity|]. rewrite Hxy Hbound. split; [reflexivity | exact Hrange].
- lra.
- lra.
- apply DPcoupl_uniform_draw.
Qed.
End uniform.
(0 <= z)%Z →
{{{ ⤇ fill K (Sample uidx (Val (LitV (LitInt z))) (Val (LitV LitUnit))) }}}
Sample uidx (Val (LitV (LitInt z))) (Val (LitV LitUnit)) @ E
{{{ (out : Z), RET #out;
⤇ fill K #((out + k) `mod` (z + 1))%Z ∗ ⌜(0 <= out <= z)%Z⌝ }}}.
Proof.
iIntros (Hz Φ) "Hr HΦ".
iMod ecm_zero as "Hε".
(* z = Z.of_nat (Z.to_nat z) under 0 ≤ z, so the value param #z equals
sf_param_to_val uniform_family (Z.to_nat z); rewrite the WHOLE goal to
N so the program subject matches the wp_couple_sample_gen rule. *)
set (N := Z.to_nat z).
assert (HzN : z = Z.of_nat N) by (subst N; rewrite Z2Nat.id //).
rewrite HzN.
assert (Hbound : (Z.of_nat (S N) = Z.of_nat N + 1)%Z) by (rewrite Nat2Z.inj_succ; lia).
iApply (wp_couple_sample_gen Sg uidx
(sf_param_to_val uniform_family N)
(sf_param_to_val uniform_family N)
(dmap (sf_inj uniform_family) (sf_sample uniform_family N))
(dmap (sf_inj uniform_family) (sf_sample uniform_family N))
(λ v v', ∃ out : Z, v = #out ∧ v' = #((out + k) `mod` (Z.of_nat N + 1))%Z
∧ (0 <= out <= Z.of_nat N)%Z) K E 0
(sig_sample_at uniform_family Sg N)
(sig_sample_at uniform_family Sg N) _ with "[Hr Hε]").
{ (* the param value #N matches sf_param_to_val uniform_family N *)
simpl. iFrame. }
{ iIntros "!>" (v v') "(Hspec & %HR)".
destruct HR as (out & -> & -> & Hb). iApply ("HΦ" $! out with "[$Hspec //]"). }
(* the single DP obligation: lift the uniform draw coupling through sf_inj *)
Unshelve.
apply DPcoupl_map; [lra | lra | ].
eapply (DPcoupl_mono _ _ _ _
(λ z0 z', z' = (z0 + k) `mod` Z.of_nat (S N) ∧ 0 <= z0 <= Z.of_nat N)%Z
_ 0 0 0 0).
- intros; reflexivity.
- intros; reflexivity.
- intros x y (Hxy & Hrange). exists x. simpl.
split; [reflexivity|]. rewrite Hxy Hbound. split; [reflexivity | exact Hrange].
- lra.
- lra.
- apply DPcoupl_uniform_draw.
Qed.
End uniform.
couple_uniform k with "[…]" — the ergonomic coupling step. It reduces the
bound to a LitV (LitInt _) literal (wp_pures/tp_pures), focuses the
Sample on both sides (wp_bind/tp_bind), and applies the value-form
wp_couple_uniform inferring z/K/E from the goal — the author supplies only
the privacy choice k (cyclic shift) and the resource pattern. The single
side-condition (0 ≤ z, the bound's nonnegativity) is auto-discharged best
effort (lia/done); the postcondition continuation is the single remaining
goal. Mirrors couple_laplace.
Tactic Notation "couple_uniform" uconstr(k) "with" constr(pat) :=
wp_pures; tp_pures;
wp_bind (Sample _ _ _); tp_bind (Sample _ _ _);
(* unshelve (the tactical) turns the goal THIS iApply shelves — only the
0 ≤ z side-condition — into a regular front goal, without globally
un-shelving unrelated goals the way the Unshelve command would. *)
unshelve (iApply (wp_couple_uniform _ k _ _ _ with pat));
(* best-effort discharge of 0 ≤ z; the postcondition continuation is the
single goal left *)
try first [ lia | done | (simpl; lia) ].
Section uniform_canary.
Context {Sg : Sig} `{!SampleIn uniform_family Sg} `{!diffprivGS Sg Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang Sg)).
wp_pures; tp_pures;
wp_bind (Sample _ _ _); tp_bind (Sample _ _ _);
(* unshelve (the tactical) turns the goal THIS iApply shelves — only the
0 ≤ z side-condition — into a regular front goal, without globally
un-shelving unrelated goals the way the Unshelve command would. *)
unshelve (iApply (wp_couple_uniform _ k _ _ _ with pat));
(* best-effort discharge of 0 ≤ z; the postcondition continuation is the
single goal left *)
try first [ lia | done | (simpl; lia) ].
Section uniform_canary.
Context {Sg : Sig} `{!SampleIn uniform_family Sg} `{!diffprivGS Sg Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang Sg)).
CANARY: two surface-form Uniform #N #() draws couple, at zero cost, with
the spec output the impl output cyclically shifted by k mod N+1 — driven
entirely by the couple_uniform tactic. Stated over the Uniform notation
(i.e. expr, with an un-reduced bound) so it exercises the surface path a
program takes; demonstrates the convenience layer end to end.
Lemma wp_uniform_shift_canary (N k : Z) K E :
(0 <= N)%Z →
{{{ ⤇ fill K (Uniform #N #()) }}}
Uniform #N #() @ E
{{{ (out : Z), RET #out;
⤇ fill K #((out + k) `mod` (N + 1))%Z ∗ ⌜(0 <= out <= N)%Z⌝ }}}.
Proof.
iIntros (HN Φ) "Hr HΦ".
(* the whole proof is one ergonomic coupling step: couple_uniform reduces
the bound, focuses the Samples, applies wp_couple_uniform, discharges
the 0 ≤ N side-condition, and unifies the postcondition with HΦ. *)
couple_uniform k with "[$Hr]".
Qed.
End uniform_canary.
(0 <= N)%Z →
{{{ ⤇ fill K (Uniform #N #()) }}}
Uniform #N #() @ E
{{{ (out : Z), RET #out;
⤇ fill K #((out + k) `mod` (N + 1))%Z ∗ ⌜(0 <= out <= N)%Z⌝ }}}.
Proof.
iIntros (HN Φ) "Hr HΦ".
(* the whole proof is one ergonomic coupling step: couple_uniform reduces
the bound, focuses the Samples, applies wp_couple_uniform, discharges
the 0 ≤ N side-condition, and unifies the postcondition with HΦ. *)
couple_uniform k with "[$Hr]".
Qed.
End uniform_canary.