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₁.Fintype] [spec₁.Inhabited] [spec₂.Fintype] [spec₂.Inhabited] {σ₁ σ₂ : 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' {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.Fintype] [spec₁.Inhabited] [spec₂.Fintype] [spec₂.Inhabited] {σ₁ σ₂ : 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.relTriple_simulateQ_run'_of_impl_evalDist_eq {ι : Type u} {spec : OracleSpec ι} {α : Type} {ι₁ ι₂ : Type u} {spec₁ : OracleSpec ι₁} {spec₂ : OracleSpec ι₂} [spec₁.Fintype] [spec₁.Inhabited] [spec₂.Fintype] [spec₂.Inhabited] {σ : 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₁.Fintype] [spec₁.Inhabited] [spec₂.Fintype] [spec₂.Inhabited] {ω₁ ω₂ : 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₁.Fintype] [spec₁.Inhabited] {ω : 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₁.Fintype] [spec₁.Inhabited] {ω : 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₁.Fintype] [spec₁.Inhabited] {ω : 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₁.Fintype] [spec₁.Inhabited] [spec₂.Fintype] [spec₂.Inhabited] {ω₁ ω₂ : 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.tvDist_simulateQ_le_probEvent_bad {ι : Type u} {spec : OracleSpec ι} {α : Type} [spec.Fintype] [spec.Inhabited] {σ : 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.Fintype] [spec.Inhabited] {σ : 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} {ι : Type u} {spec : OracleSpec ι} [spec.Fintype] [spec.Inhabited] (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} {ι : Type u} {spec : OracleSpec ι} [spec.Fintype] [spec.Inhabited] (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].

ε-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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.

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'.Fintype] [spec'.Inhabited] {σ : 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'.Fintype] [spec'.Inhabited] {σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {σ : 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'.Fintype] [spec'.Inhabited] {σ : 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'.Fintype] [spec'.Inhabited] {σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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'.Fintype] [spec'.Inhabited] {α σ : 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.ofReal_tvDist_simulateQ_run_le_queryBound_mul_slack_plus_probEvent_bad {ι : Type} {spec : OracleSpec ι} {ι' : Type} {spec' : OracleSpec ι'} [spec'.Fintype] [spec'.Inhabited] {α σ : 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.