ProgramDenotation.range and glob foundations — **LEGACY DetermFootprint theory #
(quarantined)**
Deprecated / quarantined. This is the deterministic
DetermFootprint/inRangeprogram-range theory, superseded byFootprint+ProbProgramRange(theinFootprintanalogues — they are countability-free and a read's range does not collapse). Retained only forCounterExamples, which need theDetermFootprintpathology — nothing in the main development imports this file; new code should useFootprint/inFootprint. The probabilistic wp-layer lives inProbProgramRange; the range-free generic material that used to live here (liftF,loop_n,up_to_bad,IgnoresLens,Lens.lift/factor, …) is now inWeakestPreconditions.
This file defines
liftF, which embeds a deterministic state-updatef : s → sas aProgramDenotation s Unit,ProgramDenotation.inRange p R, capturing thatp's reads and writes live in the regionR,ProgramDenotation.range p, the smallest suchR(viasInf),ProgramDenotation.range', the family-version fora → ProgramDenotation s b.
The definition uses the commutant Rᶜ (Compl instance from DetermFootprint.lean): p lies
in R iff p commutes with everything outside R. By the bicommutant closure of DetermFootprint,
this is equivalent to "the actions of p (lifted to deterministic updates) lie in R".
A program is in a DetermFootprint R iff it commutes with every update in Rᶜ
(the commutant of R). By bicommutant closure, this is equivalent to
"every state-transition p can perform lies in R".
The two sides are compared as ProgramDenotation s a: on the left, f runs before p
and p's return is preserved; on the right, p's return is captured, then
f runs, then the saved return is produced.
Equations
- p.inRange R = ∀ f ∈ Rᶜ.updates, (do GaudisCrypt.liftF f p) = do let x ← p GaudisCrypt.liftF f pure x
Instances For
The smallest DetermFootprint in which p lives.
Instances For
Family version: the smallest DetermFootprint in which every progs x lives.
Equivalently the supremum ⨆ x, (progs x).range.
Equations
- GaudisCrypt.ProgramDenotation.range' progs = sInf {R : DetermFootprint s | ∀ (x : a), (progs x).inRange R}
Instances For
The type of A's global variables: the quotient of state by
(A.range)ᶜ-orbit equivalence. Two states have the same Globals value
iff they differ only by an update outside A's range — i.e., they are
indistinguishable from A's perspective. Use this anywhere
Quotient (A.range)ᶜ.orbit_setoid would otherwise appear.
Instances For
Family-version type: the globals of the parameterized family progs.
Equations
Instances For
The global variables of A — a Getter projecting state s onto the data
A can observe or modify. Built from A.range via the DetermFootprint-level
touched_getter (which uses the commutant Rᶜ-orbit equivalence).
Equations
- A.glob = A.range.touched_getter
Instances For
Family version of glob.
Equations
Instances For
Structural lemmas #
pure x is in every range — it touches no state.
Bind composition: if p and every f x live in R, then so does p >>= f.
Monotonicity: a larger range still contains the program.
Primitive inRange lemmas #
These say that a primitive program (uniform, set, get) lives in the obvious range.
ProgramDenotation.uniformOfFinset lives in the trivial range (it doesn't touch
state — it only samples its return value).
ProgramDenotation.set is in L.compl.range when the setter v is disjoint
from the reader L. Common one-liner replacing
inRange_mono (inRange_set _ _) (Lens.range_le_compl_of_disjoint v L).
ProgramDenotation.get is in L.compl.range when the reader v is disjoint
from L. Common one-liner replacing
inRange_mono (inRange_get _) (Lens.range_le_compl_of_disjoint v L).
loop_n n body stays in the same range as body.
inRange lifted to the SubProbability level: at state σ, applying a commutant update
f ∈ Rᶜ before p gives the same distribution as running p first and then applying
f to the state coordinate of each outcome.
wp form of inRange: shifting the input state by f ∈ Rᶜ is equivalent to
post-composing f on the state coordinate of the postcondition.
Lens-preservation strengthening: if prog modifies only the complement
of L, then on the support of prog σ every output state has the same
L.get as σ. We can therefore strengthen the postcondition with an
if L.get = L.get σ then F else 0 check without changing the wp value.
Proved by a double-shift via ProgramDenotation.wp_shift_input: shifting F and the
strengthened post by f := L.liftFunction (Function.const _ (L.get σ)) (which
forces L.get to L.get σ) makes both inner posts identical, so the
wp values match.
L-ignoring is preserved when post-composing with an L-disjoint program.
wp is invariant under L.set v on input when p is L-disjoint and
F is invariant under L.set v on its state argument. The intuition:
writing v into L before p is invisible because p doesn't read
L, and F doesn't see the L-content of the output.
Note: the hypothesis on F is single-value (only requires invariance
at this v), not the full IgnoresLens (invariance at every value).
Callers that have the stronger IgnoresLens L F can supply
fun aσ => h_F aσ v.
Conditional set is wp-invisible at posts that ignore the set value.
if c then set L v else pure () has wp equal to F ((), σ) for any
post F that doesn't observe L.set v. Both branches converge:
when c holds, set L v is invisible by h_F; otherwise pure ()
is a no-op. Captures the "conditional tracking write" pattern.
Read-modify-write on L is wp-invisible at L-ignoring posts.
get L >>= fun a => set L (g a) has the same wp as pure () for any
pure modification g : γ → γ of the lens value, provided the post F
doesn't read L. Captures the "tracking variable updated in place"
pattern: the modification is invisible if downstream code doesn't
observe L.
Vanishing-post zero: if p is L-disjoint, F vanishes on every state
where L.get = v, and the input state already has L.get σ = v, then
p.wp F σ = 0. Captures the standard "bad-event vanishing" pattern in
security proofs: once the bad flag is set, all post-outcomes count as bad
too (and the post assigns them 0), so the wp is 0.
Drop a dead write: prepending ProgramDenotation.set L v to a program rest that
doesn't touch L's range is a no-op for any post that ignores L's value.
Useful for cleaning up bookkeeping writes that downstream code doesn't read.
Conditional dead write: variant of wp_set_disjoint_no_op where the
set is gated by a Prop. Useful for the tracking-variable pattern in
cryptographic proofs, where an auxiliary flag is conditionally written
inside a loop body whose remainder doesn't read it.
Get-then-conditional-set is a no-op when the conditional set targets a
lens whose compl.range covers the rest. Captures the common shape
get L_get >>= fun cx => (if pred cx then set L_set v else pure) >>= rest
used in tracking-variable patterns.
Preservation under in-range: if prog modifies only the complement of L,
and the postcondition factors through L.get (i.e. depends only on L-content),
then prog.wp (P ∘ snd) σ ≤ P σ. The sub-probability mass of prog σ only
decreases the value below P σ.
Two-lens preservation: same idea as ProgramDenotation.wp_le_of_factors, but P
factors through the pair (L₁.get, L₂.get) and prog preserves both
lenses. Iterates wp_strengthen_lens_preserved over two lenses.
Three-lens preservation: same idea as ProgramDenotation.wp_le_of_factors, but
P factors through three lens-gets and prog preserves all three. Used
for indicators (e.g. OW's useful_preimage) that depend on multiple
independent pieces of state.
Orbit fact #
Outputs of p.inRange R started at σ must lie (a.e.) in the R-orbit of σ.
We state this as the measure of the "outside-orbit" set being zero.
The proof uses the SubProb-level invariance of (p σ).1 under (id × f) pushforward
for f ∈ Rᶜ.updates with f σ = σ (which follows from inRange_subprob). The key
observation: any f ∈ Rᶜ that "merges" an off-orbit class c' into the σ-class kills
the measure of c'.
For general DetermFootprint R, constructing such an f from Rᶜ requires the
Rᶜ-action on the orbit quotient to be rich enough to move any non-σ-class to
the σ-class. This holds at least for lens-derived ranges (R = l.range).
A DetermFootprint R collapses to σ if there is a single Rᶜ-update that fixes σ
and sends every state into the R-orbit of σ.
For lens-derived R = l.range, this is provided by l.compl.liftFunction (const [σ]):
a complement-set that "resets" any state's complement to match σ's.
For an abelian bicommutant-closed R, no such update exists.
Equations
Instances For
The orbit fact under the HasOrbitCollapse hypothesis: outcomes of p σ are
a.e. in R-orbit(σ).
Lens-derived ranges always collapse.
The general orbit fact, packaged with the HasOrbitCollapse precondition.
For arbitrary DetermFootprint R, the precondition needs to be supplied externally;
for lens-derived R, Lens.range_hasOrbitCollapse discharges it.
Headline payoff lemma: programs with disjoint ranges commute.
If p lives in R and q lives in R', and the two ranges are disjoint
(R ≤ R'ᶜ, equivalently every R-update commutes with every R'-update), then
p and q may be run in either order with the same (output, state) distribution.
Additional hypotheses:
hp_coll,hq_coll: for every starting stateσ, aRᶜ/R'ᶜ-update that "collapses" the orbit ofσto a single point. Lens-derived ranges discharge these viaLens.range_hasOrbitCollapse.[Countable a] [Countable b] [Countable s]: needed to discharge the AEMeasurable side condition ofMeasureTheory.lintegral_lintegral_swap— for countable types with top σ-algebra every function is measurable.
Proof outline:
R ≤ R'ᶜ⇒R.updates ⊆ R'ᶜ.updates(and symmetricallyR' ≤ Rᶜ).- Apply
ProgramDenotation.ext_of_wpand unfoldwp_bind/wp_pureon both sides. - For each outcome
(x, s_p)ofp σin the support: byinRange_orbit_of_collapse(usinghp_coll), there isu_p ∈ R.updateswithu_p σ = s_p. Choose viaClassical.choice. Symmetricallyv_qforq. - Step (a) — rewrite the inner
(q xs.2).expectedto(q σ).expected (post-shift)viainRange_subprob hqandlintegral_congr_ae(ae onhp_orbit). - Step (b) — Fubini swap via
MeasureTheory.lintegral_lintegral_swap. - Step (c) — rewrite
U xs ys.2 = V ys xs.2using disjoint commutativity, ae on bothhp_orbitandhq_orbit. - Step (d) — rewrite the inner
(p σ).expected (... V ys xs.2 ...)to(p ys.2).expected (...)viainRange_subprob hpandlintegral_congr_ae. - Result matches RHS by
rfl.
Thin wrapper that specialises the disjointness statement to each program's own
range. The user-facing signature mentions only p.range and q.range (no
auxiliary R, R'). The two inRange p p.range / inRange q q.range premises
must be discharged by the caller.
Lens-derived variant: when p and q live in lens-derived ranges, the
HasOrbitCollapse premises are discharged automatically by
Lens.range_hasOrbitCollapse. So the user only needs to supply
the inRange proofs and the disjointness of the lens ranges.
Lens lifting and factoring #
If Adv : ProgramDenotation s a is confined to a lens window L : Lens c s
(i.e. Adv.inRange L.range), then Adv is the lifting of some "inner"
program Adv' : ProgramDenotation c a along L. This is the converse to the
obvious direction that any lift lives in the lens's range.
Factorization theorem: every program confined to a lens window comes from running some inner program on the L-content.
The witness is L.factor Adv (which depends on an arbitrary "padding"
state); the equation Adv = L.lift (L.factor Adv) holds because
Adv.inRange L.range makes Adv insensitive to the padding's
outside content.
Lifting a confined program along a chained lens #
These close the lift_inRange_chain obligation used by the RO syntactic-
equivalence development. All ranges here are lens ranges, so the orbit
machinery is not needed — the facts are pure lens algebra plus the existing
inRange_get/inRange_set extractions.
A lift lives in its lens's range. For any inner program Q, the lift
M.lift Q is confined to M.range: it only touches the M-window.
Lift confines the footprint through the chained lens. A program P
confined to window v lifts (along L) to one confined to the composite
L ∘ v. Proof: P factors as v.lift (v.factor P) (by factor_of_inRange),
lift composition turns the double lift into a single (L.chain v) lift, and
lift_inRange_self confines that to (L.chain v).range.
A sampled value lives in every range. μ.toProgramDenotation only draws its
return value; it never touches the state, so it commutes with every update.