Documentation

VCVio.ProgramLogic.Relational.SimulateQ

Relational simulateQ Rules #

This file provides the highest-leverage theorems for game-hopping proofs: relational coupling through oracle simulation, and the "identical until bad" lemma.

Main results #

Relational simulateQ rules #

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (R_state : σ₁σ₂Prop) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂), R_state s₁ s₂RelTriple ((impl₁ t).run s₁) ((impl₂ t).run s₂) fun (p₁ : spec.Range t × σ₁) (p₂ : spec.Range t × σ₂) => p₁.1 = p₂.1 R_state p₁.2 p₂.2) (s₁ : σ₁) (s₂ : σ₂) (hs : R_state s₁ s₂) :
RelTriple ((simulateQ impl₁ oa).run s₁) ((simulateQ impl₂ oa).run s₂) fun (p₁ : α × σ₁) (p₂ : α × σ₂) => p₁.1 = p₂.1 R_state p₁.2 p₂.2

Core relational simulateQ theorem with state invariant. If two oracle implementations produce equal outputs and preserve a state invariant R_state, then the full simulation also preserves the invariant and output equality.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run_mono {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (R_state : σ₁σ₂Prop) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂), R_state s₁ s₂RelTriple ((impl₁ t).run s₁) ((impl₂ t).run s₂) fun (p₁ : spec.Range t × σ₁) (p₂ : spec.Range t × σ₂) => R_state p₁.2 p₂.2 ∀ (ob : spec.Range tOracleComp spec α), RelTriple ((simulateQ impl₁ (ob p₁.1)).run p₁.2) ((simulateQ impl₂ (ob p₂.1)).run p₂.2) fun (q₁ : α × σ₁) (q₂ : α × σ₂) => R_state q₁.2 q₂.2) (s₁ : σ₁) (s₂ : σ₂) (hs : R_state s₁ s₂) :
RelTriple ((simulateQ impl₁ oa).run s₁) ((simulateQ impl₂ oa).run s₂) fun (p₁ : α × σ₁) (p₂ : α × σ₂) => R_state p₁.2 p₂.2

Monotone relational simulateQ. A generalization of relTriple_simulateQ_run that does not require equal per-query outputs. Instead, each per-query coupling must (a) preserve the state invariant and (b) supply, for the same free-monad continuation applied to the two (possibly different) coupled outputs, a recoupling of the two continued simulations preserving the invariant. This is the right shape when the two handlers genuinely diverge on the answer returned to the caller (e.g. an eager vs. deferred-sampling random-oracle read), so output equality cannot be maintained and the coupling must be rebuilt across the branch point.

The continuation hypothesis is self-referential by design: discharging it is exactly the construction of the divergent-branch coupling, which is the hard probabilistic content this lemma isolates from the free-monad bookkeeping.

theorem OracleComp.ProgramLogic.Relational.probEvent_le_of_relTriple_imp {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {β : Type} {oa : OracleComp spec₁ α} {ob : OracleComp spec₂ β} {R : αβProp} {P : αProp} {Q : βProp} (h : RelTriple oa ob R) (himp : ∀ (a : α) (b : β), R a bP aQ b) :

Marginal stochastic dominance from a coupling. If oa and ob are related by a coupling whose post-relation R carries an event implication P a → Q b, then the marginal probability of P on the left is at most that of Q on the right. This is the one-sided (inequality) counterpart of evalDist_map_eq_of_relTriple: where the latter extracts a distributional equality from output equality, this extracts a probability inequality from an output implication. The proof reads off both marginals from the single coupling distribution c and applies pointwise monotonicity of probEvent on c.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run' {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (R_state : σ₁σ₂Prop) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂), R_state s₁ s₂RelTriple ((impl₁ t).run s₁) ((impl₂ t).run s₂) fun (p₁ : spec.Range t × σ₁) (p₂ : spec.Range t × σ₂) => p₁.1 = p₂.1 R_state p₁.2 p₂.2) (s₁ : σ₁) (s₂ : σ₂) (hs : R_state s₁ s₂) :
RelTriple ((simulateQ impl₁ oa).run' s₁) ((simulateQ impl₂ oa).run' s₂) (EqRel α)

Projection: relational simulateQ preserving only output equality.

theorem OracleComp.ProgramLogic.Relational.probEvent_le_of_relTriple_simulateQ_run {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (rState : σ₁σ₂Prop) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂), rState s₁ s₂RelTriple ((impl₁ t).run s₁) ((impl₂ t).run s₂) fun (p₁ : spec.Range t × σ₁) (p₂ : spec.Range t × σ₂) => p₁.1 = p₂.1 rState p₁.2 p₂.2) (s₁ : σ₁) (s₂ : σ₂) (hs : rState s₁ s₂) {p : α × σ₁Prop} {q : α × σ₂Prop} (himp : ∀ (z₁ : α × σ₁) (z₂ : α × σ₂), z₁.1 = z₂.1rState z₁.2 z₂.2p z₁q z₂) :
probEvent ((simulateQ impl₁ oa).run s₁) p probEvent ((simulateQ impl₂ oa).run s₂) q

probEvent monotonicity between two relational simulateQ runs. Simulating the same adversary oa under two StateT implementations related per query by rState (via relTriple_simulateQ_run), any event implication that holds along the run postcondition z₁.1 = z₂.1 ∧ rState z₁.2 z₂.2 transports to Pr[p | run impl₁] ≤ Pr[q | run impl₂]. The simulateQ form of probEvent_le_of_relTriple; the events range over the full (output, state) pair, so it covers output events, state events, and their conjunctions over the two (output, state) spaces (the output type is shared; the state spaces σ₁/σ₂ differ).

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run'_of_impl_evalDist_eq {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ : Type} (impl₁ : QueryImpl spec (StateT σ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ (OracleComp spec₂))) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain) (s : σ), 𝒟[(impl₁ t).run s] = 𝒟[(impl₂ t).run s]) (s₁ s₂ : σ) (hs : s₁ = s₂) :
RelTriple ((simulateQ impl₁ oa).run' s₁) ((simulateQ impl₂ oa).run' s₂) (EqRel α)

Exact-distribution specialization of relTriple_simulateQ_run'.

If corresponding oracle calls have identical full (output, state) distributions whenever the states are equal, then the simulated computations have identical output distributions. This packages the common pattern "prove per-query evalDist equality, then use Eq as the state invariant" into a single theorem.

WriterT analogue #

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run_writerT {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {ω₁ ω₂ : Type} [Monoid ω₁] [Monoid ω₂] (impl₁ : QueryImpl spec (WriterT ω₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (WriterT ω₂ (OracleComp spec₂))) (R_writer : ω₁ω₂Prop) (hR_one : R_writer 1 1) (hR_mul : ∀ (w₁ w₁' : ω₁) (w₂ w₂' : ω₂), R_writer w₁ w₂R_writer w₁' w₂'R_writer (w₁ * w₁') (w₂ * w₂')) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain), RelTriple (impl₁ t).run (impl₂ t).run fun (p₁ : spec.Range t × ω₁) (p₂ : spec.Range t × ω₂) => p₁.1 = p₂.1 R_writer p₁.2 p₂.2) :
RelTriple (simulateQ impl₁ oa).run (simulateQ impl₂ oa).run fun (p₁ : α × ω₁) (p₂ : α × ω₂) => p₁.1 = p₂.1 R_writer p₁.2 p₂.2

WriterT analogue of relTriple_simulateQ_run.

If two writer-transformed oracle implementations produce outputs related by a reflexive-and-closed relation R_writer on the accumulated logs, then the full simulation preserves output equality together with the accumulated-log relation.

hR_one witnesses reflexivity at the empty accumulator (the run-start value), and hR_mul closes R_writer under the monoid multiplication used by WriterT's bind. Together these make R_writer a monoid congruence on the two writer spaces, which is precisely the structural requirement for whole-program accumulation.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run_writerT_of_impl_eq {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ : Type u} {spec₁ : OracleSpec ι₁} [spec₁.IsUniformSpec] {ω : Type} [Monoid ω] (impl₁ impl₂ : QueryImpl spec (WriterT ω (OracleComp spec₁))) (himpl_eq : ∀ (t : spec.Domain), (impl₁ t).run = (impl₂ t).run) (oa : OracleComp spec α) :
RelTriple (simulateQ impl₁ oa).run (simulateQ impl₂ oa).run (EqRel (α × ω))

WriterT analogue of relTriple_simulateQ_run_of_impl_eq_preservesInv.

If two writer-transformed oracle implementations agree pointwise on .run (i.e. every per-query increment is identical as an OracleComp), then the whole simulations yield identical (output, accumulator) distributions.

WriterT handlers are stateless (.run takes no argument), so the hypothesis is a plain equality rather than an invariant-gated implication. The postcondition is strict equality on α × ω.

theorem OracleComp.ProgramLogic.Relational.probOutput_simulateQ_run_writerT_eq_of_impl_eq {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ : Type u} {spec₁ : OracleSpec ι₁} [spec₁.IsUniformSpec] {ω : Type} [Monoid ω] (impl₁ impl₂ : QueryImpl spec (WriterT ω (OracleComp spec₁))) (himpl_eq : ∀ (t : spec.Domain), (impl₁ t).run = (impl₂ t).run) (oa : OracleComp spec α) (z : α × ω) :
Pr[= z | (simulateQ impl₁ oa).run] = Pr[= z | (simulateQ impl₂ oa).run]

Output-probability projection of relTriple_simulateQ_run_writerT_of_impl_eq: two WriterT handlers with pointwise-equal .run yield identical (output, accumulator) probability distributions.

theorem OracleComp.ProgramLogic.Relational.evalDist_simulateQ_run_writerT_eq_of_impl_eq {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ : Type u} {spec₁ : OracleSpec ι₁} [spec₁.IsUniformSpec] {ω : Type} [Monoid ω] (impl₁ impl₂ : QueryImpl spec (WriterT ω (OracleComp spec₁))) (himpl_eq : ∀ (t : spec.Domain), (impl₁ t).run = (impl₂ t).run) (oa : OracleComp spec α) :
𝒟[(simulateQ impl₁ oa).run] = 𝒟[(simulateQ impl₂ oa).run]

evalDist equality projection of relTriple_simulateQ_run_writerT_of_impl_eq.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run_writerT' {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {ω₁ ω₂ : Type} [Monoid ω₁] [Monoid ω₂] (impl₁ : QueryImpl spec (WriterT ω₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (WriterT ω₂ (OracleComp spec₂))) (R_writer : ω₁ω₂Prop) (hR_one : R_writer 1 1) (hR_mul : ∀ (w₁ w₁' : ω₁) (w₂ w₂' : ω₂), R_writer w₁ w₂R_writer w₁' w₂'R_writer (w₁ * w₁') (w₂ * w₂')) (oa : OracleComp spec α) (himpl : ∀ (t : spec.Domain), RelTriple (impl₁ t).run (impl₂ t).run fun (p₁ : spec.Range t × ω₁) (p₂ : spec.Range t × ω₂) => p₁.1 = p₂.1 R_writer p₁.2 p₂.2) :
RelTriple (Prod.fst <$> (simulateQ impl₁ oa).run) (Prod.fst <$> (simulateQ impl₂ oa).run) (EqRel α)

Projection of relTriple_simulateQ_run_writerT onto the output component.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run_of_impl_eq_preservesInv {α ι : Type} {spec : OracleSpec ι} {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT σ ProbComp)) (Inv : σProp) (oa : OracleComp spec α) (himpl_eq : ∀ (t : spec.Domain) (s : σ), Inv s(impl₁ t).run s = (impl₂ t).run s) (hpres₂ : ∀ (t : spec.Domain) (s : σ), Inv szsupport ((impl₂ t).run s), Inv z.2) (s : σ) (hs : Inv s) :
RelTriple ((simulateQ impl₁ oa).run s) ((simulateQ impl₂ oa).run s) fun (p₁ p₂ : α × σ) => p₁ = p₂ Inv p₁.2

If two stateful oracle implementations agree on every query while Inv holds, and the second implementation preserves Inv, then the full simulations have identical (output, state) distributions from any invariant-satisfying initial state.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run_eqRel_of_impl_eq_preservesInv {α ι : Type} {spec : OracleSpec ι} {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT σ ProbComp)) (Inv : σProp) (oa : OracleComp spec α) (himpl_eq : ∀ (t : spec.Domain) (s : σ), Inv s(impl₁ t).run s = (impl₂ t).run s) (hpres₂ : ∀ (t : spec.Domain) (s : σ), Inv szsupport ((impl₂ t).run s), Inv z.2) (s : σ) (hs : Inv s) :
RelTriple ((simulateQ impl₁ oa).run s) ((simulateQ impl₂ oa).run s) (EqRel (α × σ))

Exact-equality specialization of relTriple_simulateQ_run_of_impl_eq_preservesInv.

This weakens the stronger invariant-carrying postcondition to plain equality on (output, state), which is the shape consumed directly by probability-transport lemmas and theorem-driven rvcgen steps.

theorem OracleComp.ProgramLogic.Relational.probOutput_simulateQ_run_eq_of_impl_eq_preservesInv {α ι : Type} {spec : OracleSpec ι} {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT σ ProbComp)) (Inv : σProp) (oa : OracleComp spec α) (himpl_eq : ∀ (t : spec.Domain) (s : σ), Inv s(impl₁ t).run s = (impl₂ t).run s) (hpres₂ : ∀ (t : spec.Domain) (s : σ), Inv szsupport ((impl₂ t).run s), Inv z.2) (s : σ) (hs : Inv s) (z : α × σ) :
Pr[= z | (simulateQ impl₁ oa).run s] = Pr[= z | (simulateQ impl₂ oa).run s]

Output-probability projection of relTriple_simulateQ_run_of_impl_eq_preservesInv.

theorem OracleComp.ProgramLogic.Relational.probOutput_simulateQ_run_eq_of_impl_eq_queryBound {α ι : Type} {spec : OracleSpec ι} {σ : Type} {B : Type u_1} (impl₁ impl₂ : QueryImpl spec (StateT σ ProbComp)) (Inv : σBProp) (canQuery : spec.DomainBProp) (cost : spec.DomainBB) (oa : OracleComp spec α) (budget : B) (hbound : oa.IsQueryBound budget canQuery cost) (himpl_eq : ∀ (t : spec.Domain) (s : σ) (b : B), Inv s bcanQuery t b(impl₁ t).run s = (impl₂ t).run s) (hpres₂ : ∀ (t : spec.Domain) (s : σ) (b : B), Inv s bcanQuery t bzsupport ((impl₂ t).run s), Inv z.2 (cost t b)) (s : σ) (hs : Inv s budget) (z : α × σ) :
Pr[= z | (simulateQ impl₁ oa).run s] = Pr[= z | (simulateQ impl₂ oa).run s]

Query-bounded exact-output transport for simulateQ.

If oa satisfies a structural query bound IsQueryBound budget canQuery cost, the two implementations agree on every query that the bound permits, and the second implementation preserves a budget-indexed invariant Inv, then the full simulated computations have identical output-state probabilities from any initial state satisfying Inv.

theorem OracleComp.ProgramLogic.Relational.relTriple_simulateQ_run'_of_query_map_eq {α ι : Type} {spec : OracleSpec ι} {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ ProbComp)) (impl₂ : QueryImpl spec (StateT σ₂ ProbComp)) (proj : σ₁σ₂) (hproj : ∀ (t : spec.Domain) (s : σ₁), Prod.map id proj <$> (impl₁ t).run s = (impl₂ t).run (proj s)) (oa : OracleComp spec α) (s : σ₁) :
RelTriple ((simulateQ impl₁ oa).run' s) ((simulateQ impl₂ oa).run' (proj s)) (EqRel α)

Relational transport corollary of OracleComp.run'_simulateQ_eq_of_query_map_eq (SimSemantics/StateProjection.lean): under the same per-step projection hypothesis, the two output distributions are related by equality.

"Identical until bad" fundamental lemma #

theorem OracleComp.ProgramLogic.Relational.probEvent_marginal_simulateQ_mono {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (R : σ₁σ₂Prop) (bad₁ : σ₁Prop) (bad₂ : σ₂Prop) (h_base : ∀ (s₁ : σ₁) (s₂ : σ₂), R s₁ s₂bad₁ s₁bad₂ s₂) (h_step : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂), R s₁ s₂∀ {γ : Type} (k₁ : spec.Range t × σ₁OracleComp spec₁ (γ × σ₁)) (k₂ : spec.Range t × σ₂OracleComp spec₂ (γ × σ₂)), (∀ (u : spec.Range t) (s₁' : σ₁) (s₂' : σ₂), R s₁' s₂'(probEvent (k₁ (u, s₁')) fun (z : γ × σ₁) => bad₁ z.2) probEvent (k₂ (u, s₂')) fun (z : γ × σ₂) => bad₂ z.2)(probEvent ((impl₁ t).run s₁ >>= k₁) fun (z : γ × σ₁) => bad₁ z.2) probEvent ((impl₂ t).run s₂ >>= k₂) fun (z : γ × σ₂) => bad₂ z.2) (oa : OracleComp spec α) (s₁ : σ₁) (s₂ : σ₂) (hR : R s₁ s₂) :
(probEvent ((simulateQ impl₁ oa).run s₁) fun (z : α × σ₁) => bad₁ z.2) probEvent ((simulateQ impl₂ oa).run s₂) fun (z : α × σ₂) => bad₂ z.2

Marginal stochastic dominance through simulateQ (self-referential / Fubini form).

The marginal counterpart of relTriple_simulateQ_run_mono. Where the latter demands a pointwise per-step coupling whose support respects an output-and-state relation, this lemma demands only a marginal per-step inequality: at every query and every pair of R-related states, the one-step run of impl₁ followed by any left tail k₁ has bad-marginal at most the one-step run of impl₂ followed by any right tail k₂, provided the two tails are themselves marginally bad-dominated from every pair of R-related successor states.

This is the right shape when the two handlers genuinely diverge on the answer distribution at a single step (e.g. an eager deterministic ghost read vs. a deferred-sampling read), so no pointwise coupling can dominate the bad flag at that step, yet the marginal bad mass — the tsum over the deferred draw, taken before the divergent continuation is applied — is still ordered (Fubini / tsum-swap). The per-step premise is self-referential by design: discharging it at the divergent step is exactly the marginal draw-commutation, the hard content this lemma isolates from the free-monad bookkeeping.

The base hypothesis h_base (R s₁ s₂ → bad₁ s₁ → bad₂ s₂) discharges the pure leaf, where no further step can repair the bad flag: there the bad marginal is exactly the indicator of the current state, so R must already carry the bad implication.

theorem OracleComp.ProgramLogic.Relational.probEvent_dist_simulateQ_mono {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] {σ₁ σ₂ : Type} (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (Rrun : {γ : Type} → OracleComp spec₁ (γ × σ₁)OracleComp spec₂ (γ × σ₂)Prop) (bad₁ : σ₁Prop) (bad₂ : σ₂Prop) (h_pure : ∀ {γ : Type} (a : γ) (s₁ : σ₁) (s₂ : σ₂), Rrun (pure (a, s₁)) (pure (a, s₂))) (h_bind : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂) {γ : Type} (k₁ : spec.Range t × σ₁OracleComp spec₁ (γ × σ₁)) (k₂ : spec.Range t × σ₂OracleComp spec₂ (γ × σ₂)), (∀ (u : spec.Range t) (s₁' : σ₁) (s₂' : σ₂), Rrun (k₁ (u, s₁')) (k₂ (u, s₂')))Rrun ((impl₁ t).run s₁ >>= k₁) ((impl₂ t).run s₂ >>= k₂)) (h_bad : ∀ {γ : Type} (r₁ : OracleComp spec₁ (γ × σ₁)) (r₂ : OracleComp spec₂ (γ × σ₂)), Rrun r₁ r₂(probEvent r₁ fun (z : γ × σ₁) => bad₁ z.2) probEvent r₂ fun (z : γ × σ₂) => bad₂ z.2) (oa : OracleComp spec α) (s₁ : σ₁) (s₂ : σ₂) :
(probEvent ((simulateQ impl₁ oa).run s₁) fun (z : α × σ₁) => bad₁ z.2) probEvent ((simulateQ impl₂ oa).run s₂) fun (z : α × σ₂) => bad₂ z.2

Distribution-level stochastic dominance through simulateQ.

The distribution-level sibling of probEvent_marginal_simulateQ_mono. Where the latter carries a pointwise state relation R : σ₁ → σ₂ → Prop and discharges the per-query step at every pair of R-related states, this lemma carries a relation Rrun directly on the two run distributions (the whole OracleComp spec₁ (γ × σ₁) / OracleComp spec₂ (γ × σ₂) computations), generic over the output type γ. This is the shape needed when the per-step recoupling is inherently joint-law — e.g. an eager handler that has already committed sampled keys into its state versus a deferred-sampling handler that only carries a pending count, so that no pointwise state predicate relates the two successor states yet the two run distributions are related by a coupling over the deferred draw.

The induction is pure free-monad bookkeeping; the entire probabilistic content is isolated into the two premises:

  • h_pure seeds the relation at the pure leaves (the run distributions are the two point masses pure (a, s₁) / pure (a, s₂));
  • h_bind is the distribution-level bind congruence: given a query t and any two tails k₁ k₂ whose per-output continuations are already Rrun-related, the one-step runs followed by those tails are again Rrun-related. Discharging h_bind at a divergent step is the marginal draw-commutation, the hard content this lemma isolates.

Once the relation is established along the whole run, h_bad reads off the ordered bad marginals.

s₁ : σ₁, s₂ : σ₂ are the initial states and hbase : Rrun (pure …) …-free: instead the seed is supplied through h_pure, applied at the actual reachable leaves during the induction, so no separate base hypothesis on s₁ s₂ is required beyond what h_pure/h_bind carry.

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_le_probEvent_bad {ι : Type u} {spec : OracleSpec ι} {α : Type} [spec.IsUniformSpec] {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT σ (OracleComp spec))) (bad : σProp) (oa : OracleComp spec α) (s₀ : σ) (h_init : ¬bad s₀) (h_agree : ∀ (t : spec.Domain) (s : σ), ¬bad s(impl₁ t).run s = (impl₂ t).run s) (h_mono₁ : ∀ (t : spec.Domain) (s : σ), bad sxsupport ((impl₁ t).run s), bad x.2) (h_mono₂ : ∀ (t : spec.Domain) (s : σ), bad sxsupport ((impl₂ t).run s), bad x.2) :
tvDist ((simulateQ impl₁ oa).run' s₀) ((simulateQ impl₂ oa).run' s₀) (probEvent ((simulateQ impl₁ oa).run s₀) (bad Prod.snd)).toReal

The fundamental lemma of game playing: if two oracle implementations agree whenever a "bad" flag is unset, then the total variation distance between the two simulations is bounded by the probability that bad gets set.

Both implementations must satisfy a monotonicity condition: once bad s holds, it must remain true in all reachable successor states. Without this, the theorem is false — an implementation could enter a bad state (where agreement is not required), diverge, and then return to a non-bad state, producing different outputs with Pr[bad] = 0. Monotonicity is needed on both sides because the proof establishes pointwise equality Pr[= (x,s) | sim₁] = Pr[= (x,s) | sim₂] for all ¬bad s, which requires ruling out bad-to-non-bad transitions in each implementation independently.

Distributional "identical until bad" #

The _dist variant weakens the agreement hypothesis from definitional equality (impl₁ t).run s = (impl₂ t).run s) to distributional equality (∀ p, Pr[= p | (impl₁ t).run s] = Pr[= p | (impl₂ t).run s]). This is needed when the two implementations differ intensionally but agree on output probabilities.

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_le_probEvent_bad_dist {ι : Type u} {spec : OracleSpec ι} {α : Type} [spec.IsUniformSpec] {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT σ (OracleComp spec))) (bad : σProp) (oa : OracleComp spec α) (s₀ : σ) :
¬bad s₀∀ (h_agree_dist : ∀ (t : spec.Domain) (s : σ), ¬bad s∀ (p : spec.Range t × σ), Pr[= p | (impl₁ t).run s] = Pr[= p | (impl₂ t).run s]) (h_mono₁ : ∀ (t : spec.Domain) (s : σ), bad sxsupport ((impl₁ t).run s), bad x.2) (h_mono₂ : ∀ (t : spec.Domain) (s : σ), bad sxsupport ((impl₂ t).run s), bad x.2), tvDist ((simulateQ impl₁ oa).run' s₀) ((simulateQ impl₂ oa).run' s₀) (probEvent ((simulateQ impl₁ oa).run s₀) (bad Prod.snd)).toReal

Distributional variant of tvDist_simulateQ_le_probEvent_bad: weakens the agreement hypothesis from definitional equality to distributional equality (pointwise equal output probabilities).

"Identical until bad" with an output bad flag #

These variants record the bad event in the output state of each oracle step (not the input). The state has shape σ × Bool with the second component a monotone bad flag, and the two implementations may disagree on the very step that flips the flag. The standard pointwise agreement hypothesis of tvDist_simulateQ_le_probEvent_bad{,_dist} is too strong here: at the firing step, the input is non-bad but the outputs already differ. The output-bad pattern is the exact shape of QueryImpl.withProgramming (which sets bad := true only on policy-firing steps) and the programming_collision_bound argument that builds on it.

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_le_probEvent_output_bad {ι : Type u} {spec : OracleSpec ι} {α : Type} [spec.IsUniformSpec] {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec))) (oa : OracleComp spec α) (s₀ : σ) (h_agree_good : ∀ (t : spec.Domain) (s : σ) (u : spec.Range t) (s' : σ), Pr[= (u, s', false) | (impl₁ t).run (s, false)] = Pr[= (u, s', false) | (impl₂ t).run (s, false)]) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (h_mono₂ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₂ t).run p), z.2.2 = true) :
tvDist ((simulateQ impl₁ oa).run' (s₀, false)) ((simulateQ impl₂ oa).run' (s₀, false)) (probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true).toReal

"Identical until bad" with the bad flag tracked at the output of each oracle step. TV-distance between two state-extended simulations is bounded by the probability of the flag firing in the run of impl₁.

Compared to tvDist_simulateQ_le_probEvent_bad{,_dist}, this version weakens the agreement hypothesis: the two implementations need only agree on non-bad output transitions from non-bad input states. They may disagree arbitrarily on the very step that flips the flag.

Both implementations must satisfy bad-input monotonicity: once b = true in the input state of a step, every reachable output also has b = true.

theorem OracleComp.ProgramLogic.Relational.identical_until_bad_with_flag {ι : Type u} {spec : OracleSpec ι} {α : Type} [spec.IsUniformSpec] {σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec))) (oa : OracleComp spec α) (s₀ : σ) (h_agree_good : ∀ (t : spec.Domain) (s : σ) (u : spec.Range t) (s' : σ), Pr[= (u, s', false) | (impl₁ t).run (s, false)] = Pr[= (u, s', false) | (impl₂ t).run (s, false)]) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (h_mono₂ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₂ t).run p), z.2.2 = true) :
tvDist ((simulateQ impl₁ oa).run' (s₀, false)) ((simulateQ impl₂ oa).run' (s₀, false)) (probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true).toReal

Ergonomic wrapper of tvDist_simulateQ_le_probEvent_output_bad for the very common case where the underlying oracle implementations live in StateT σ (OracleComp spec) and have been lifted to StateT (σ × Bool) (OracleComp spec) by attaching a bad flag.

This is the exact shape consumed by the QueryImpl.withProgramming collision-bound argument: the impls agree on (s, false) input modulo the rare programming-fired step, and the bound is the probability of any policy hit during the run.

ε-perturbed "identical until bad" with output bad flag #

These lemmas generalize tvDist_simulateQ_le_probEvent_output_bad from EXACT agreement on the no-bad path to ε-CLOSE agreement: the per-step TV distance between the two oracle implementations may be at most ε (instead of zero) on the no-bad path. Combined with a query bound q on the computation, the total bound becomes q*ε + Pr[bad].

The standard "identical until bad" bound (Pr[bad]) is recovered as the special case ε = 0.

Application: HVZK simulation in Fiat-Shamir, where the simulated transcript is only ε-close to the real transcript per query (not exactly equal), but a "programming collision" event captures the catastrophic failure mode (collision between programmed hash entries). The total reduction loss is qS·ε + Pr[collision].

Exact identical-until-bad with output bad flag: joint heterogeneous variant #

tvDist_simulateQ_le_probEvent_output_bad fixes the inner monad to OracleComp spec over the same spec as the simulated computation, and projects the conclusion to the output marginal. The variant here generalizes the inner monad to OracleComp spec' and keeps the conclusion on the joint output-and-state distribution, which is what a game with a state-dependent continuation (e.g. a final verification step reading the run's cache) consumes.

theorem OracleComp.ProgramLogic.Relational.probEvent_output_bad_eq' {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (h_agree_good : ∀ (t : spec.Domain) (s : σ) (u : spec.Range t) (s' : σ), Pr[= (u, s', false) | (impl₁ t).run (s, false)] = Pr[= (u, s', false) | (impl₂ t).run (s, false)]) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (h_mono₂ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₂ t).run p), z.2.2 = true) (oa : OracleComp spec α) (s₀ : σ) :
(probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true) = probEvent ((simulateQ impl₂ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true

Bad-event equality for exact identical-until-bad, with the inner monad over an arbitrary uniform spec spec'. Two state-extended implementations that agree on every non-bad output transition from a non-bad input state (h_agree_good) and are bad-input monotone (h_mono₁, h_mono₂) flip the output bad flag with exactly the same probability. This is the equality counterpart of tvDist_simulateQ_run_le_probEvent_output_bad (which bounds only the TV distance): the bad marginals coincide because the two runs differ only on the already-bad trajectory, where both flags read true.

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_run_le_probEvent_output_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (oa : OracleComp spec α) (s₀ : σ) (h_agree_good : ∀ (t : spec.Domain) (s : σ) (u : spec.Range t) (s' : σ), Pr[= (u, s', false) | (impl₁ t).run (s, false)] = Pr[= (u, s', false) | (impl₂ t).run (s, false)]) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (h_mono₂ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₂ t).run p), z.2.2 = true) :
tvDist ((simulateQ impl₁ oa).run (s₀, false)) ((simulateQ impl₂ oa).run (s₀, false)) (probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true).toReal

"Identical until bad" with an output bad flag, on the joint output-and-state distribution, with the inner monad over an arbitrary uniform spec spec'.

Two state-extended oracle implementations that agree on non-bad output transitions from non-bad input states (and are bad-input monotone) produce simulated runs whose joint output-and-state distributions are within the probability of the flag firing in the run of impl₁. Unlike tvDist_simulateQ_le_probEvent_output_bad, the conclusion keeps the final state, so a state-dependent continuation (e.g. verification against the final cache) can be appended on both sides.

ε-perturbed identical-until-bad: helper lemmas (in dependency order) #

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_le_qeps_plus_probEvent_output_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) {ε : } ( : 0 ε) (h_step_tv : ∀ (t : spec.Domain) (s : σ), tvDist ((impl₁ t).run (s, false)) ((impl₂ t).run (s, false)) ε) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (oa : OracleComp spec α) {q : } (h_qb : oa.IsTotalQueryBound q) (s₀ : σ) :
tvDist ((simulateQ impl₁ oa).run' (s₀, false)) ((simulateQ impl₂ oa).run' (s₀, false)) q * ε + (probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true).toReal

ε-perturbed identical-until-bad with output bad flag.

If two stateful oracle implementations are ε-CLOSE in TV distance per step on the no-bad path (instead of exactly equal as in tvDist_simulateQ_le_probEvent_output_bad), and the computation makes at most q queries, then the TV distance between the two simulations is bounded by q*ε + Pr[bad].

Only impl₁ requires bad-flag monotonicity (since the bound uses Pr[bad | sim₁]); the "true" branch in the inductive proof exploits monotonicity to push Pr[bad | sim₁] = 1 which dominates the trivial tvDist ≤ 1 bound.

The ε = 0 case recovers the existing identical-until-bad bound (modulo the upgraded agreement hypothesis from definitional equality to TV-distance equality, which is equivalent for distributions over the same type).

Selective ε-perturbed identical-until-bad #

A refinement of tvDist_simulateQ_le_qeps_plus_probEvent_output_bad where the per-step ε bound applies only to a designated subset S of queries (the "costly" or "perturbed" queries), and the impls are pointwise equal on the complement (the "free" queries). The bound counts only the charged queries, giving a tight q · ε instead of q_total · ε.

This is essential for cryptographic reductions where, e.g., signing-oracle queries are ε-close to a simulator (HVZK guarantee) but uniform / RO queries are exactly equal (both sides forward through the same RO cache). Direct application of the uniform-ε lemma would give (qS + qH) · ε, but for tight bounds we want q · ε.

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) {ε : } ( : 0 ε) (S : ιProp) [DecidablePred S] (h_step_tv_S : ∀ (t : ι), S t∀ (s : σ), tvDist ((impl₁ t).run (s, false)) ((impl₂ t).run (s, false)) ε) (h_step_eq_nS : ∀ (t : ι), ¬S t∀ (p : σ × Bool), (impl₁ t).run p = (impl₂ t).run p) (h_mono₁ : ∀ (t : ι) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (oa : OracleComp spec α) {qS : } (h_qb : oa.IsQueryBoundP S qS) (s₀ : σ) :
tvDist ((simulateQ impl₁ oa).run (s₀, false)) ((simulateQ impl₂ oa).run (s₀, false)) qS * ε + (probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true).toReal

Selective ε-perturbed identical-until-bad with output bad flag.

Like tvDist_simulateQ_le_qeps_plus_probEvent_output_bad, but the per-step ε bound applies only to queries t satisfying a designated predicate S (the "costly" queries), and the impls are pointwise equal on ¬ S (the "free" queries). The bound counts only the charged queries (via IsQueryBoundP oa S qS), giving the tight q · ε instead of the trivial q_total · ε from the uniform-ε lemma.

The intended use is for cryptographic reductions: e.g., for Fiat-Shamir signing-oracle swaps, the "costly" queries are signing queries (HVZK gives per-query ε bound) and the "free" queries are the underlying spec queries (uniform sampling and RO caching, where both sides forward through the same baseSim).

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_le_queryBound_mul_slack_plus_probEvent_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) {ε : } ( : 0 ε) (S : ιProp) [DecidablePred S] (h_step_tv_S : ∀ (t : ι), S t∀ (s : σ), tvDist ((impl₁ t).run (s, false)) ((impl₂ t).run (s, false)) ε) (h_step_eq_nS : ∀ (t : ι), ¬S t∀ (p : σ × Bool), (impl₁ t).run p = (impl₂ t).run p) (h_mono₁ : ∀ (t : ι) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (oa : OracleComp spec α) {qS : } (h_qb : oa.IsQueryBoundP S qS) (s₀ : σ) :
tvDist ((simulateQ impl₁ oa).run' (s₀, false)) ((simulateQ impl₂ oa).run' (s₀, false)) qS * ε + (probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true).toReal

Selective ε-perturbed identical-until-bad with output bad flag.

Like tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad, but projected to the computation output via StateT.run'.

Query-bounded TV budget without a bad event #

When the two implementations agree exactly off the charged queries and no bad event is tracked, the selective bound simplifies to a pure per-query budget qS * ε on the joint output-and-state distribution, with no bad-flag plumbing in the state.

theorem OracleComp.ProgramLogic.Relational.tvDist_simulateQ_run_le_queryBoundP_mul {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT σ (OracleComp spec'))) {ε : } ( : 0 ε) (S : ιProp) [DecidablePred S] (h_step_tv_S : ∀ (t : ι), S t∀ (s : σ), tvDist ((impl₁ t).run s) ((impl₂ t).run s) ε) (h_step_eq_nS : ∀ (t : ι), ¬S t∀ (s : σ), (impl₁ t).run s = (impl₂ t).run s) (oa : OracleComp spec α) {qS : } (h_qb : oa.IsQueryBoundP S qS) (s₀ : σ) :
tvDist ((simulateQ impl₁ oa).run s₀) ((simulateQ impl₂ oa).run s₀) qS * ε

Query-bounded total-variation budget for simulateQ.

If two stateful oracle implementations agree exactly on every query outside a designated set S, and on S-queries are within total-variation distance ε on the joint answer-and-state distribution — uniformly in the carried state — then simulating any computation making at most qS queries to S keeps the joint output-and-state distributions within qS * ε, from any shared starting state.

This is the bad-event-free counterpart of tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad: the per-query budgets telescope across the simulation by the triangle inequality, the hybrid for the i-th charged query swapping which implementation answers it. Typical use: a signing oracle whose real and simulated bodies are within ε from every shared random-oracle cache, with all remaining oracles handled identically on both sides.

State-dep ε-perturbed identical-until-bad #

A further refinement of tvDist_simulateQ_le_queryBound_mul_slack_plus_probEvent_bad where the per-step ε bound is allowed to depend on the input state s : σ to the impl. The bound on tvDist is then expressed as the expected sum of ε s over the trace of charged queries fired during the simulation, captured by the recursive function expectedQuerySlack.

This is essential for cryptographic reductions where the per-step gap depends on a varying state quantity (e.g., for Fiat-Shamir signing-oracle swaps the gap is ζ_zk + |s.cache| · β, growing with cache size, with no uniform constant ε). The constant-ε lemma tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad is a corollary.

To sidestep summability obligations, expectedQuerySlack is valued in ℝ≥0∞ and the bridge lemma is stated in ℝ≥0∞ via ENNReal.ofReal (tvDist …).

noncomputable def OracleComp.ProgramLogic.Relational.expectedQuerySlackStep {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (t : spec.Domain) (k : spec.Range tσ × BoolENNReal) (qS : ) (p : σ × Bool) :

Per-query_bind step of expectedQuerySlack. Given the impl, the charged-query predicate S, the per-state query slack ε, the query symbol t, and the IH continuation k : Range t → ℕ → (σ × Bool) → ℝ≥0∞, returns the expected cost contributed by performing the query t from state p with budget qS:

  • if the bad flag is set in p, return 0 (the Pr[bad] term swallows the deficit);
  • if t is a uncharged query (¬ S t), forward through the impl with budget unchanged;
  • if t is a charged query and the budget is exhausted, return 0 (vacuous via IsQueryBound);
  • if t is a charged query with positive budget, pay ε p.1 immediately, then forward through the impl with budget decremented to qS - 1.
Instances For
    noncomputable def OracleComp.ProgramLogic.Relational.expectedQuerySlack {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) {α : Type} :
    OracleComp spec ασ × BoolENNReal

    Recursive expected accumulated query slack over the charged queries fired during (simulateQ impl oa).run p. Defined by recursion on oa via OracleComp.construct.

    Instances For
      @[simp]
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_pure {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (x : α) (qS : ) (p : σ × Bool) :
      expectedQuerySlack impl S ε (pure x) qS p = 0
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_query_bind {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (t : spec.Domain) (cont : spec.Range tOracleComp spec α) (qS : ) (p : σ × Bool) :
      expectedQuerySlack impl S ε (liftM (OracleSpec.query t) >>= cont) qS p = expectedQuerySlackStep impl S ε t (fun (u : spec.Range t) => expectedQuerySlack impl S ε (cont u)) qS p
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_bind_eq_of_right_zero {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) {β : Type} (oa : OracleComp spec α) (ob : αOracleComp spec β) (hzero : ∀ (x : α) (qS : ) (p : σ × Bool), expectedQuerySlack impl S ε (ob x) qS p = 0) (qS : ) (p : σ × Bool) :
      expectedQuerySlack impl S ε (oa >>= ob) qS p = expectedQuerySlack impl S ε oa qS p
      @[simp]
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlackStep_bad_eq_zero {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (t : spec.Domain) (k : spec.Range tσ × BoolENNReal) (qS : ) (s : σ) :
      expectedQuerySlackStep impl S ε t k qS (s, true) = 0
      @[simp]
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_bad_eq_zero {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (oa : OracleComp spec α) (qS : ) (s : σ) :
      expectedQuerySlack impl S ε oa qS (s, true) = 0
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlackStep_costly_pos {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (t : spec.Domain) (k : spec.Range tσ × BoolENNReal) (qS : ) (s : σ) (hS : S t) (hqS : 0 < qS) :
      expectedQuerySlackStep impl S ε t k qS (s, false) = ε s + ∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run (s, false)] * k z.1 (qS - 1) z.2
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlackStep_free {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] (ε : σENNReal) (t : spec.Domain) (k : spec.Range tσ × BoolENNReal) (qS : ) (s : σ) (hS : ¬S t) :
      expectedQuerySlackStep impl S ε t k qS (s, false) = ∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run (s, false)] * k z.1 qS z.2

      Pointwise monotonicity of expectedQuerySlack in ε #

      If ε ≤ ε' pointwise (as functions σ → ℝ≥0∞), then expectedQuerySlack impl S ε oa qS p ≤ expectedQuerySlack impl S ε' oa qS p. The analogous monotonicity in the continuation k (for expectedQuerySlackStep) is the step-level lemma, used in the inductive step of expectedQuerySlack_mono. These lemmas are used to bound a state-dependent ε by a constant upper bound so the constant-ε bound expectedQuerySlack_const_le_queryBudget_mul applies.

      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlackStep_mono {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] {ε ε' : σENNReal} ( : ∀ (s : σ), ε s ε' s) (t : spec.Domain) {k k' : spec.Range tσ × BoolENNReal} (hk : ∀ (u : spec.Range t) (qS : ) (p : σ × Bool), k u qS p k' u qS p) (qS : ) (p : σ × Bool) :
      expectedQuerySlackStep impl S ε t k qS p expectedQuerySlackStep impl S ε' t k' qS p
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_mono {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] {ε ε' : σENNReal} ( : ∀ (s : σ), ε s ε' s) (oa : OracleComp spec α) (qS : ) (p : σ × Bool) :
      expectedQuerySlack impl S ε oa qS p expectedQuerySlack impl S ε' oa qS p

      Invariant support congruence for expectedQuerySlack #

      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_eq_of_inv {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (S : spec.DomainProp) [DecidablePred S] {ε ε' : σENNReal} (Inv : σProp) ( : ∀ (s : σ), Inv sε s = ε' s) (h_pres : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = falseInv p.1zsupport ((impl t).run p), Inv z.2.1) (oa : OracleComp spec α) (qS : ) (p : σ × Bool) (hp : p.2 = falseInv p.1) :
      expectedQuerySlack impl S ε oa qS p = expectedQuerySlack impl S ε' oa qS p

      If two per-state query slack functions agree on an invariant and the real handler preserves that invariant from no-bad states, then expectedQuerySlack is insensitive to their values on unreachable states.

      The input hypothesis is phrased as p.2 = false → Inv p.1 so that bad states remain vacuous: expectedQuerySlack is definitionally zero once the bad flag is set.

      Helper lemma: per-summand IH bound implies the bind-sum bound #

      Per-step inductive helpers #

      Inductive auxiliary lemma #

      Public bridge lemmas #

      theorem OracleComp.ProgramLogic.Relational.ofReal_tvDist_simulateQ_run_le_expectedQuerySlack_plus_probEvent_output_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (chargedQuery : spec.DomainProp) [DecidablePred chargedQuery] (querySlack : σENNReal) (h_step_tv_charged : ∀ (t : spec.Domain), chargedQuery t∀ (s : σ), ENNReal.ofReal (tvDist ((impl₁ t).run (s, false)) ((impl₂ t).run (s, false))) querySlack s) (h_step_eq_uncharged : ∀ (t : spec.Domain), ¬chargedQuery t∀ (p : σ × Bool), (impl₁ t).run p = (impl₂ t).run p) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (oa : OracleComp spec α) {queryBudget : } (h_qb : oa.IsQueryBoundP chargedQuery queryBudget) (p : σ × Bool) :
      ENNReal.ofReal (tvDist ((simulateQ impl₁ oa).run p) ((simulateQ impl₂ oa).run p)) expectedQuerySlack impl₁ chargedQuery querySlack oa queryBudget p + probEvent ((simulateQ impl₁ oa).run p) fun (z : α × σ × Bool) => z.2.2 = true

      State-dep ε-perturbed identical-until-bad with output bad flag (joint state).

      Like tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad, but the per-step ε bound is allowed to depend on the input state s : σ to the impl. The q · ε term is replaced by the expected accumulated query slack over the trace of charged queries fired during simulation, captured by expectedQuerySlack.

      Statement is in ℝ≥0∞ to sidestep summability obligations on the query-slack trace.

      theorem OracleComp.ProgramLogic.Relational.ofReal_tvDist_simulateQ_le_expectedQuerySlack_plus_probEvent_output_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (chargedQuery : spec.DomainProp) [DecidablePred chargedQuery] (querySlack : σENNReal) (h_step_tv_charged : ∀ (t : spec.Domain), chargedQuery t∀ (s : σ), ENNReal.ofReal (tvDist ((impl₁ t).run (s, false)) ((impl₂ t).run (s, false))) querySlack s) (h_step_eq_uncharged : ∀ (t : spec.Domain), ¬chargedQuery t∀ (p : σ × Bool), (impl₁ t).run p = (impl₂ t).run p) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (oa : OracleComp spec α) {queryBudget : } (h_qb : oa.IsQueryBoundP chargedQuery queryBudget) (s₀ : σ) :
      ENNReal.ofReal (tvDist ((simulateQ impl₁ oa).run' (s₀, false)) ((simulateQ impl₂ oa).run' (s₀, false))) expectedQuerySlack impl₁ chargedQuery querySlack oa queryBudget (s₀, false) + probEvent ((simulateQ impl₁ oa).run (s₀, false)) fun (z : α × σ × Bool) => z.2.2 = true

      State-dep ε-perturbed identical-until-bad with output bad flag (projected output).

      Composing the joint-state lemma with the projection Prod.fst : α × σ × Bool → α, which can only decrease TV distance (data-processing inequality tvDist_map_le).

      Constant-ε corollary (Phase A2 regression) #

      Specializing expectedQuerySlack to a constant query-slack function fun _ => ε and using IsQueryBoundP to bound the number of charged queries, the accumulated slack is dominated by q · ε. Combined with the state-dep main lemma this re-derives the selective constant-ε bound in ENNReal form.

      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_const_le_queryBudget_mul {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (chargedQuery : spec.DomainProp) [DecidablePred chargedQuery] (ε : ENNReal) (oa : OracleComp spec α) {queryBudget : } (h_qb : oa.IsQueryBoundP chargedQuery queryBudget) (p : σ × Bool) :
      expectedQuerySlack impl chargedQuery (fun (x : σ) => ε) oa queryBudget p queryBudget * ε
      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_resource_le {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (chargedQuery growthQuery : spec.DomainProp) [DecidablePred chargedQuery] [DecidablePred growthQuery] (R : σENNReal) (ζ β : ENNReal) (h_growth : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = falsechargedQuery t growthQuery tzsupport ((impl t).run p), R z.2.1 R p.1 + 1) (h_free : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = false¬chargedQuery t¬growthQuery tzsupport ((impl t).run p), R z.2.1 R p.1) (oa : OracleComp spec α) {qS qH : } (h_qS : oa.IsQueryBoundP chargedQuery qS) (h_qH : oa.IsQueryBoundP growthQuery qH) (s : σ) :
      expectedQuerySlack impl chargedQuery (fun (s : σ) => ζ + R s * β) oa qS (s, false) qS * ζ + qS * (R s + qS + qH) * β

      State-dependent resource bound for expectedQuerySlack.

      If each charged query pays ζ + R s * β, and the resource R can increase by at most one on charged or growth queries and never increases otherwise, then a computation with at most qS charged queries and at most qH growth queries has accumulated slack at most qS * ζ + qS * (R s + qS + qH) * β.

      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_expected_resource_le {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (chargedQuery growthQuery : spec.DomainProp) [DecidablePred chargedQuery] [DecidablePred growthQuery] (R : σENNReal) (ζ β g : ENNReal) (h_charged : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = falsechargedQuery t∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run p] * R z.2.1 R p.1 + g) (h_growth : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = false¬chargedQuery tgrowthQuery tzsupport ((impl t).run p), R z.2.1 R p.1 + 1) (h_free : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = false¬chargedQuery t¬growthQuery tzsupport ((impl t).run p), R z.2.1 R p.1) (oa : OracleComp spec α) {qS qH : } (h_qS : oa.IsQueryBoundP chargedQuery qS) (h_qH : oa.IsQueryBoundP growthQuery qH) (s : σ) :
      expectedQuerySlack impl chargedQuery (fun (s : σ) => ζ + R s * β) oa qS (s, false) qS * ζ + (qS * R s + qS * qH + (qS.choose 2) * g) * β

      Expected-growth resource bound for expectedQuerySlack.

      Like expectedQuerySlack_resource_le, but a charged query may grow the resource by more than one in support, as long as it grows by at most g in expectation under the handler. Growth queries grow the resource by at most one in support, and free queries never grow it. The accumulated slack of a computation with at most qS charged and qH growth queries is then at most qS·ζ + (qS·R s + qS·qH + C(qS,2)·g)·β, the binomial cross term coming from the expected resource increase of earlier charged queries.

      theorem OracleComp.ProgramLogic.Relational.expectedQuerySlack_charged_read_expected_growth_le {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (chargedQuery growthQuery : spec.DomainProp) [DecidablePred chargedQuery] [DecidablePred growthQuery] (R : σENNReal) (β g : ENNReal) (h_charged : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = falsechargedQuery tzsupport ((impl t).run p), R z.2.1 R p.1) (h_growth : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = false¬chargedQuery tgrowthQuery t∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run p] * R z.2.1 R p.1 + g) (h_free : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = false¬chargedQuery t¬growthQuery tzsupport ((impl t).run p), R z.2.1 R p.1) (oa : OracleComp spec α) {qS qH : } (h_qS : oa.IsQueryBoundP chargedQuery qS) (h_qH : oa.IsQueryBoundP growthQuery qH) (s : σ) :
      expectedQuerySlack impl chargedQuery (fun (s : σ) => R s * β) oa qS (s, false) qS * (R s + qH * g) * β

      Charged-read / expected-growth resource bound for expectedQuerySlack.

      A variant of expectedQuerySlack_expected_resource_le for the situation where the charged queries never grow the resource (they only read it), while a separate class of growth queries grows the resource by at most g in expectation (and may grow it by arbitrarily much in support). Free queries never grow it.

      Each charged query pays R s · β at the state s reached when it fires. Since the charged queries do not grow R, and the growth queries grow it by at most g in expectation, the resource seen by any charged query is at most R s₀ + qH · g in expectation, where s₀ is the starting state and qH bounds the growth queries. Folding the qS charged reads against this expected cap gives accumulated slack at most qS · (R s₀ + qH · g) · β, with no (qS choose 2) cross-term and no dependence on the in-support growth of the resource (which expectedQuerySlack_expected_resource_le would charge through its h_growth/h_charged ≤ R p.1 + g shape).

      This is the fold used by the ghost-read collision charge of the Fiat-Shamir-with-aborts Prog → Trans hop, where the charged queries are the adversary's random-oracle reads (which only grow the real cache, leaving the ghost cache R untouched) and the growth queries are the signing queries (which grow the ghost cache by the number of rejected attempts, up to maxAttempts − 1 in support but at most ∑_{a} p^a ≤ 1/(1−p) in expectation).

      theorem OracleComp.ProgramLogic.Relational.ofReal_tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {α σ : Type} (impl₁ impl₂ : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ε : ENNReal) (chargedQuery : spec.DomainProp) [DecidablePred chargedQuery] (h_step_tv_charged : ∀ (t : spec.Domain), chargedQuery t∀ (s : σ), ENNReal.ofReal (tvDist ((impl₁ t).run (s, false)) ((impl₂ t).run (s, false))) ε) (h_step_eq_uncharged : ∀ (t : spec.Domain), ¬chargedQuery t∀ (p : σ × Bool), (impl₁ t).run p = (impl₂ t).run p) (h_mono₁ : ∀ (t : spec.Domain) (p : σ × Bool), p.2 = truezsupport ((impl₁ t).run p), z.2.2 = true) (oa : OracleComp spec α) {queryBudget : } (h_qb : oa.IsQueryBoundP chargedQuery queryBudget) (p : σ × Bool) :
      ENNReal.ofReal (tvDist ((simulateQ impl₁ oa).run p) ((simulateQ impl₂ oa).run p)) queryBudget * ε + probEvent ((simulateQ impl₁ oa).run p) fun (z : α × σ × Bool) => z.2.2 = true

      Constant-ε version of the bridge as a corollary of the state-dep version.

      This is the ENNReal-form analogue of the existing real-valued tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad. It demonstrates that the state-dep version subsumes the constant-ε version: instantiate ε := fun _ => ENNReal.ofReal ε_const and bound expectedQuerySlack by queryBudget * ENNReal.ofReal ε_const.

      Heterogeneous-state bad + slack simulateQ rule #

      A fully heterogeneous (σ₁ ≠ σ₂, spec₁ ≠ spec₂) one-directional simulateQ induction rule carrying both a monotone bad event on side 1 and per-charged-query slack ε.

      Unlike the tvDist-based bounds above, this rule does not require the two simulations to have the same output/state type: the conclusion is a one-directional Pr[= true] inequality

      Pr[= true | run' impl₁] ≤ Pr[= true | run' impl₂] + Pr[bad] + q · ε,

      which is exactly the shape consumed by cross-domain crypto reductions that couple a per-tag random oracle against a per-session one. The accounting term q · ε comes from the charged-query budget IsQueryBoundP oa charged q.

      theorem OracleComp.ProgramLogic.Relational.probOutput_simulateQ_run'_le_add_bad_add_slack {ι : Type} {spec : OracleSpec ι} {ι₁ ι₂ : Type} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} {σ₁ σ₂ : Type} [spec₁.IsUniformSpec] [spec₂.IsUniformSpec] (impl₁ : QueryImpl spec (StateT σ₁ (OracleComp spec₁))) (impl₂ : QueryImpl spec (StateT σ₂ (OracleComp spec₂))) (R : σ₁σ₂Prop) (bad : σ₁Prop) (charged : spec.DomainProp) [DecidablePred charged] (ε : ENNReal) (hmono : ∀ (t : spec.Domain) (s₁ : σ₁), bad s₁zsupport ((impl₁ t).run s₁), bad z.2) (hstep : ∀ (t : spec.Domain) (s₁ : σ₁) (s₂ : σ₂), R s₁ s₂¬bad s₁∀ (k₁ : spec.Range t × σ₁OracleComp spec₁ (Bool × σ₁)) (k₂ : spec.Range t × σ₂OracleComp spec₂ (Bool × σ₂)) (c : ENNReal), (∀ (u : spec.Range t) (s₁' : σ₁) (s₂' : σ₂), R s₁' s₂'(probEvent (k₁ (u, s₁')) fun (z : Bool × σ₁) => z.1 = true) (probEvent (k₂ (u, s₂')) fun (z : Bool × σ₂) => z.1 = true) + probEvent (k₁ (u, s₁')) (bad Prod.snd) + c)(probEvent ((impl₁ t).run s₁ >>= k₁) fun (z : Bool × σ₁) => z.1 = true) (probEvent ((impl₂ t).run s₂ >>= k₂) fun (z : Bool × σ₂) => z.1 = true) + probEvent ((impl₁ t).run s₁ >>= k₁) (bad Prod.snd) + (c + if charged t then ε else 0)) (oa : OracleComp spec Bool) {q : } (hbound : oa.IsQueryBoundP charged q) (s₁ : σ₁) (s₂ : σ₂) (hR : R s₁ s₂) :
      Pr[= true | (simulateQ impl₁ oa).run' s₁] Pr[= true | (simulateQ impl₂ oa).run' s₂] + probEvent ((simulateQ impl₁ oa).run s₁) (bad Prod.snd) + q * ε

      Heterogeneous-state bad + slack simulateQ rule.

      Couples two stateful oracle simulations with different state types σ₁, σ₂ and different base specs spec₁, spec₂, related by a coupling invariant R. It carries a monotone bad event bad on side 1 together with per-charged-query slack ε, charged queries being designated by the predicate charged. If the computation oa makes at most q charged queries (IsQueryBoundP oa charged q), then

      Pr[= true | run' impl₁ oa] ≤ Pr[= true | run' impl₂ oa] + Pr[bad] + q · ε.

      The per-query premise hstep is the bind-level coupling step: from R-related, non-bad states, one query head together with any pair of continuations satisfying a continuation bound yields the head-bind bound, paying ε for charged queries. This packages exactly the obligation a concrete cross-domain reduction must discharge for its oracle pair.

      Only impl₁ requires bad monotonicity (hmono), since the bound is one-directional and mentions Pr[bad] only on side 1.

      Single-world resource-charged bad accumulator #

      A single-world accumulator bounding Pr[flag = true] for a stateful simulation whose state σ × Bool carries a monotone resource R : σ → ℝ≥0∞ and a never-reset bad flag. Unlike the identical-until-bad theorems above, which bound only the TV distance between two worlds and treat Pr[output bad] as an additive remainder term they never bound, this lemma bounds the bad-flag mass directly, by the resource-weighted query slack expectedQuerySlack impl charged (fun s => R s · ε).

      The per-step hypotheses are:

      Folding the resulting expectedQuerySlack against a resource bound (e.g. via expectedQuerySlack_resource_le / expectedQuerySlack_expected_resource_le) yields a closed-form bilinear bound.

      theorem OracleComp.ProgramLogic.Relational.probEvent_bad_simulateQ_run_le_expectedQuerySlack {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (charged : spec.DomainProp) [DecidablePred charged] (R : σENNReal) (ε : ENNReal) (h_charged_step : ∀ (t : spec.Domain) (s : σ), charged t∀ (k : spec.Range t × σ × BoolOracleComp spec' (γ × σ × Bool)), (probEvent ((impl t).run (s, false) >>= k) fun (z : γ × σ × Bool) => z.2.2 = true) R s * ε + ∑' (z : spec.Range t × σ), Pr[= (z.1, z.2, false) | (impl t).run (s, false)] * probEvent (k (z.1, z.2, false)) fun (w : γ × σ × Bool) => w.2.2 = true) (h_free_step : ∀ (t : spec.Domain) (s : σ), ¬charged t∀ (k : spec.Range t × σ × BoolOracleComp spec' (γ × σ × Bool)), (probEvent ((impl t).run (s, false) >>= k) fun (z : γ × σ × Bool) => z.2.2 = true) ∑' (z : spec.Range t × σ), Pr[= (z.1, z.2, false) | (impl t).run (s, false)] * probEvent (k (z.1, z.2, false)) fun (w : γ × σ × Bool) => w.2.2 = true) (oa : OracleComp spec γ) {qS : } :
      oa.IsQueryBoundP charged qS∀ (s : σ), (probEvent ((simulateQ impl oa).run (s, false)) fun (z : γ × σ × Bool) => z.2.2 = true) expectedQuerySlack impl charged (fun (s : σ) => R s * ε) oa qS (s, false)

      Single-world resource-charged bad accumulator.

      For simulateQ impl oa over a state σ × Bool (resource σ, never-reset bad flag), if

      • every charged step pays a flip charge R s · ε (h_charged_step), routing any further bad mass through its good (non-flagged) output states, while
      • every free step pays nothing and introduces no bad mass (h_free_step),

      then the probability the flag is set after the whole run from a non-bad state is bounded by the resource-weighted query slack expectedQuerySlack impl charged (fun s => R s * ε) oa qS (s, false). This is the single-world, output-event analogue of probEvent_fst_simulateQ_run_le_add_bad_add_slack: the inductive structure (good branch reduced through the head bind by the per-step premise, bad output states discarded since their slack is 0) is similar, but the conclusion bounds Pr[bad] itself rather than carrying it as an additive remainder.

      Averaged-state-measure bad accumulator #

      The single-world resource accumulator probEvent_bad_simulateQ_run_le_expectedQuerySlack charges a flip cost R s · ε at a fixed reachable state s. That is exactly the right shape for a handler that draws the hidden randomness at the read (the lazy / deferred-sampling handler), where the per-state read charge is genuinely the averaged guessing mass R s · ε < 1.

      It is the wrong shape for an eager handler that commits the hidden draw upstream (at signing time) and then reads it back deterministically: at a committed state s the read-hit indicator 1_{mc ∈ slot(s)} is 0 or 1, never ε. The averaging that produces ε happened earlier, at the commit draw, and cannot be localized to any fixed read state.

      The fix carried here is to average not over a single fixed state but over a state measure ν : σ × Bool → ℝ≥0∞ — the law of the eager handler's slot under the pending upstream draws. The averaged bad mass

      avgBadM impl ν oa := ∑' p, ν p · Pr[bad | (simulateQ impl oa).run p]

      telescopes through the free monad exactly like expectedQuerySlack, but the read step's charge is now ∑' p, ν p · 1_{mc ∈ slot(p)} = Pr_{p∼ν}[mc ∈ slot(p)], a genuine probability over the state law. When ν is the pushforward of the upstream commit draws, this collapses (by Fubini / tsum-swap over the pending draws) to the same mass the lazy handler charges at the read — probOutput_lazyGhostFire_one is its single-pending base case. This is the missing-framework analogue of expectedQuerySlack: it carries a state-law plus an averaged-output invariant rather than a per-state resource charge.

      This section builds the reusable telescoping scaffold (avgBadM, its pure/query_bind unfoldings, and the output-grouped step law avgBadM_query_bind_eq_tsum_output) and isolates the read-step charge as the standalone Fubini lemma (tsum_tsum_postStepOutM_mul) the instantiation must match against the lazy run.

      Bare-measure averaged bad mass #

      The scaffold is stated over a bare measure ν : σ × Bool → ℝ≥0∞ rather than a probability law: the telescoping identities and the free-monad induction only ever use ν p as an ℝ≥0∞ weight, and an aborting signing step produces a sub-probability post-step state law (its total mass drops by the rejection mass), which a PMF-typed average could not carry.

      A caller (e.g. the deferred-sampling charge route) telescopes with avgBadM_query_bind_eq_tsum_output directly at the sub-probability state laws produced by the aborting sign step.

      noncomputable def OracleComp.ProgramLogic.Relational.avgBadM {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (oa : OracleComp spec γ) :

      Bare-measure averaged bad mass. The per-state bad mass of a run, averaged against an arbitrary measure ν : σ × Bool → ℝ≥0∞ rather than a probability law. No total-mass constraint is needed by the telescoping, so the average carries the sub-probability post-step laws emitted by an aborting step.

      Instances For
        theorem OracleComp.ProgramLogic.Relational.avgBadM_pure_state {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (p₀ : σ × Bool) (oa : OracleComp spec γ) :
        avgBadM impl (fun (p : σ × Bool) => if p = p₀ then 1 else 0) oa = probEvent ((simulateQ impl oa).run p₀) fun (z : γ × σ × Bool) => z.2.2 = true

        avgBadM at a Dirac (single-point indicator) measure is the plain per-state bad probability.

        theorem OracleComp.ProgramLogic.Relational.avgBadM_eq_tsum_pure {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (oa : OracleComp spec γ) :
        avgBadM impl ν oa = ∑' (s' : σ × Bool), ν s' * avgBadM impl (fun (p : σ × Bool) => if p = s' then 1 else 0) oa

        Linearity of avgBadM in the state measure. The averaged bad mass over ν is the ν-weighted sum of the per-state (Dirac) bad masses.

        theorem OracleComp.ProgramLogic.Relational.avgBadM_pure {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (x : γ) :
        avgBadM impl ν (pure x) = ∑' (p : σ × Bool), ν p * if p.2 = true then 1 else 0

        Pure base case of avgBadM. With no queries, the bad mass is exactly the carried bad mass of the measure ν — the ν-mass on states with the flag already set.

        theorem OracleComp.ProgramLogic.Relational.avgBadM_query_bind_eq {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (t : spec.Domain) (cont : spec.Range tOracleComp spec γ) :
        avgBadM impl ν (liftM (OracleSpec.query t) >>= cont) = ∑' (p : σ × Bool), ν p * ∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run p] * probEvent ((simulateQ impl (cont z.1)).run z.2) fun (w : γ × σ × Bool) => w.2.2 = true

        One-step telescoping of avgBadM (joint-law form). Moves one query off the front and exposes the post-step joint law, holding for any impl and any measure ν. No probabilistic content — pure rearrangement.

        noncomputable def OracleComp.ProgramLogic.Relational.postStepJointM {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (t : spec.Domain) (z : spec.Range t × σ × Bool) :

        Post-step joint measure of a query step (bare-measure form). The measure over (output, post-state) produced by averaging the per-state impl step Pr[= z | (impl t).run p] against the state measure ν. Stated directly as a tsum since ν need not be a probability law.

        Instances For
          theorem OracleComp.ProgramLogic.Relational.avgBadM_telescope_eq_tsum_postStep {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (t : spec.Domain) (cont : spec.Range tOracleComp spec γ) :
          (∑' (p : σ × Bool), ν p * ∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run p] * probEvent ((simulateQ impl (cont z.1)).run z.2) fun (w : γ × σ × Bool) => w.2.2 = true) = ∑' (z : spec.Range t × σ × Bool), postStepJointM impl ν t z * avgBadM impl (fun (p : σ × Bool) => if p = z.2 then 1 else 0) (cont z.1)

          Output-grouped telescoping of the bare-measure average. The telescoped one-step average regroups as a single tsum over the post-step joint measure postStepJointM impl ν t, weighting each (output, post-state) pair by the Dirac bad mass at the post-state. Pure tsum-Fubini.

          noncomputable def OracleComp.ProgramLogic.Relational.postStepOutM {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (t : spec.Domain) (u : spec.Range t) (s : σ × Bool) :

          Per-output post-step state measure. Grouping the post-step joint measure postStepJointM impl ν t by the query output u: the resulting state measure assigns to each post-state s the joint mass of producing (u, s). The state coordinate of the output-u slice of the post-step joint measure.

          Instances For
            theorem OracleComp.ProgramLogic.Relational.avgBadM_query_bind_eq_tsum_output {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ γ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (t : spec.Domain) (cont : spec.Range tOracleComp spec γ) :
            avgBadM impl ν (liftM (OracleSpec.query t) >>= cont) = ∑' (u : spec.Range t), avgBadM impl (postStepOutM impl ν t u) (cont u)

            Output-grouped one-step telescoping of avgBadM. The telescoped one-step average regroups as a tsum over the query output u, each weighted by the averaged bad mass of the continuation cont u run from the per-output post-step state measure postStepOutM impl ν t u. This is the form the threaded-charge induction consumes: it applies the inductive hypothesis once per output, at a genuine state measure (not a Dirac), so the per-target charge of the post-step measure can be bounded as a measure (avoiding the -of- blow-up of the per-post-state Dirac grouping).

            theorem OracleComp.ProgramLogic.Relational.tsum_tsum_postStepOutM_mul {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.IsUniformSpec] {σ : Type} (impl : QueryImpl spec (StateT (σ × Bool) (OracleComp spec'))) (ν : σ × BoolENNReal) (t : spec.Domain) (F : σ × BoolENNReal) :
            ∑' (u : spec.Range t) (s : σ × Bool), postStepOutM impl ν t u s * F s = ∑' (p : σ × Bool), ν p * ∑' (z : spec.Range t × σ × Bool), Pr[= z | (impl t).run p] * F z.2

            Weighted post-step rearrangement. Summing any post-state functional F against the post-step measure (over output u and post-state s) equals the ν-average of the per-state expected value of F after one impl step. The Fubini bridge used to push a per-state charge bound (e.g. ghost-size or membership-charge growth) through the post-step measure.