Documentation

CatCryptCore.Relational.Rules

pRHL Proof Rules #

This file contains the core proof rules for probabilistic relational Hoare logic.

Structural Rules #

theorem CatCrypt.Relational.rHoare_conseq {α : Type u_1} {β : Type u_2} {Φ Φ' : RPre} {Ψ Ψ' : RPost α β} {c₁ : Core.SPComp α} {c₂ : Core.SPComp β} (hPre : ∀ (h₁ h₂ : Core.Heap), Φ' h₁ h₂Φ h₁ h₂) (hPost : ∀ (a₁ : α) (h₁ : Core.Heap) (a₂ : β) (h₂ : Core.Heap), Ψ a₁ h₁ a₂ h₂Ψ' a₁ h₁ a₂ h₂) (h : rHoare Φ c₁ c₂ Ψ) :
rHoare Φ' c₁ c₂ Ψ'

Consequence rule: strengthen precondition, weaken postcondition

theorem CatCrypt.Relational.rHoare_symm {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} {c₁ : Core.SPComp α} {c₂ : Core.SPComp β} (h : rHoare (fun (h₁ h₂ : Core.Heap) => Φ h₂ h₁) c₂ c₁ fun (a₂ : β) (h₂ : Core.Heap) (a₁ : α) (h₁ : Core.Heap) => Ψ a₁ h₁ a₂ h₂) :
rHoare Φ c₁ c₂ Ψ

Symmetry rule

theorem CatCrypt.Relational.rHoare_symm_eq {α : Type u_5} {c₁ c₂ : Core.SPComp α} (h : rHoare eqPre c₁ c₂ eqPost) :
rHoare eqPre c₂ c₁ eqPost

Symmetry specialized to the equality pre/postconditions: swap the two programs.

Return Rules #

theorem CatCrypt.Relational.rHoare_ret {α : Type u_1} {β : Type u_2} {Φ : RPre} {a : α} {b : β} {Ψ : RPost α β} (h : ∀ (h₁ h₂ : Core.Heap), Φ h₁ h₂Ψ a h₁ b h₂) :
rHoare Φ (pure a) (pure b) Ψ

Return rule: pure values with postcondition derivable from precondition

theorem CatCrypt.Relational.rHoare_ret_same {α : Type u_1} {Φ : RPre} {a : α} :
rHoare Φ (pure a) (pure a) fun (a₁ : α) (h₁ : Core.Heap) (a₂ : α) (h₂ : Core.Heap) => Φ h₁ h₂ a₁ = a₂

Return same value on both sides preserves precondition and establishes equality

Bind Rule #

theorem CatCrypt.Relational.rHoare_bind {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} {Φ : RPre} {Ψ : RPost α β} {Θ : RPost γ δ} {c₁ : Core.SPComp α} {c₂ : Core.SPComp β} {f₁ : αCore.SPComp γ} {f₂ : βCore.SPComp δ} (hc : rHoare Φ c₁ c₂ Ψ) (hf : ∀ (a : α) (b : β), rHoare (fun (h₁ h₂ : Core.Heap) => Ψ a h₁ b h₂) (f₁ a) (f₂ b) Θ) :
rHoare Φ (c₁.bind f₁) (c₂.bind f₂) Θ

Bind rule: sequential composition of judgments

Reflexivity and Transitivity #

Reflexivity: same code with equal heaps gives equal results

theorem CatCrypt.Relational.rHoare_trans {α : Type u_1} (c₁ c₂ c₃ : Core.SPComp α) (h₁₂ : rHoare eqPre c₁ c₂ eqPost) (h₂₃ : rHoare eqPre c₂ c₃ eqPost) :
rHoare eqPre c₁ c₃ eqPost

Transitivity for equal-heap coupling.

When composing through an intermediate computation, if:

  • c₁ and c₂ produce equal results with equal heaps
  • c₂ and c₃ produce equal results with equal heaps Then c₁ and c₃ also produce equal results with equal heaps.

This is used in hybrid argument proofs where we compose through intermediate games.

Sampling Rules #

theorem CatCrypt.Relational.rHoare_sample_same {Φ : RPre} (α : Type u_5) [Fintype α] [Nonempty α] :
rHoare Φ (Core.SPComp.sample α) (Core.SPComp.sample α) fun (a₁ : α) (h₁ : Core.Heap) (a₂ : α) (h₂ : Core.Heap) => Φ h₁ h₂ a₁ = a₂

Same distribution, diagonal coupling

theorem CatCrypt.Relational.rHoare_sample_bij {Φ : RPre} (α : Type u_5) (β : Type u_6) [Fintype α] [Fintype β] [Nonempty α] [Nonempty β] (f : α β) :
rHoare Φ (Core.SPComp.sample α) (Core.SPComp.sample β) fun (a : α) (h₁ : Core.Heap) (b : β) (h₂ : Core.Heap) => Φ h₁ h₂ f a = b

Bijection coupling

theorem CatCrypt.Relational.rHoare_sample_l {σ : Type u_5} {τ : Type u_6} {ρ : Type u_7} {Φ : RPre} {Ψ : RPost τ ρ} [Fintype σ] [Nonempty σ] {c₂ : Core.SPComp ρ} {f : σCore.SPComp τ} (h : ∀ (a : σ), rHoare Φ (f a) c₂ Ψ) :
rHoare Φ ((Core.SPComp.sample σ).bind f) c₂ Ψ

One-sided sample on the left.

Key idea: Use averaging coupling.

  • For each sampled value a, we have a coupling from h a
  • Rewrite right side as (uniform σ).bind (fun _ => c₂ h₂) using uniform_bind_const
  • Apply liftR_bind with the diagonal coupling on σ
theorem CatCrypt.Relational.rHoare_sample_r {σ : Type u_5} {τ : Type u_6} {ρ : Type u_7} {Φ : RPre} {Ψ : RPost ρ τ} [Fintype σ] [Nonempty σ] {c₁ : Core.SPComp ρ} {f : σCore.SPComp τ} (h : ∀ (a : σ), rHoare Φ c₁ (f a) Ψ) :
rHoare Φ c₁ ((Core.SPComp.sample σ).bind f) Ψ

One-sided sample on the right.

Key idea: Use averaging coupling (symmetric to sample_l).

State Rules #

theorem CatCrypt.Relational.rHoare_get_sync {Φ : RPre} (l : Core.Location) (hSync : ∀ (h₁ h₂ : Core.Heap), Φ h₁ h₂h₁.get l = h₂.get l) :
rHoare Φ (Core.SPComp.get l) (Core.SPComp.get l) fun (v₁ : l.ty) (h₁ : Core.Heap) (v₂ : l.ty) (h₂ : Core.Heap) => Φ h₁ h₂ v₁ = v₂

Synchronized get: same location, synchronized values

theorem CatCrypt.Relational.rHoare_set_sync {Φ : RPre} (l : Core.Location) (v : l.ty) (hInv : ∀ (h₁ h₂ : Core.Heap), Φ h₁ h₂Φ (h₁.set l v) (h₂.set l v)) :
rHoare Φ (Core.SPComp.set l v) (Core.SPComp.set l v) fun (x : Unit) (h₁ : Core.Heap) (x_1 : Unit) (h₂ : Core.Heap) => Φ h₁ h₂

Synchronized set: same location and value

theorem CatCrypt.Relational.rHoare_get_l {β γ : Type} {Φ : RPre} {Ψ : RPost β γ} (l : Core.Location) {c₂ : Core.SPComp γ} {f : l.tyCore.SPComp β} (h : ∀ (v : l.ty), rHoare (fun (h₁ h₂ : Core.Heap) => Φ h₁ h₂ h₁.get l = v) (f v) c₂ Ψ) :
rHoare Φ (Core.SPComp.get l >>= f) c₂ Ψ

One-sided get on the left. Note: β is constrained to Type (universe 0) since l.ty : Type.

theorem CatCrypt.Relational.rHoare_get_r {β γ : Type} {Φ : RPre} {Ψ : RPost γ β} (l : Core.Location) {c₁ : Core.SPComp γ} {f : l.tyCore.SPComp β} (h : ∀ (v : l.ty), rHoare (fun (h₁ h₂ : Core.Heap) => Φ h₁ h₂ h₂.get l = v) c₁ (f v) Ψ) :
rHoare Φ c₁ (Core.SPComp.get l >>= f) Ψ

One-sided get on the right. Note: β is constrained to Type (universe 0) since l.ty : Type.

theorem CatCrypt.Relational.rHoare_put_l {τ : Type u_5} {ρ : Type u_6} {Φ : RPre} {Ψ : RPost τ ρ} (l : Core.Location) (v : l.ty) {c₂ : Core.SPComp ρ} {f : UnitCore.SPComp τ} (h : rHoare (fun (h₁ h₂ : Core.Heap) => Φ h₁ h₂) (f ()) c₂ Ψ) :
rHoare (fun (h₁ h₂ : Core.Heap) => Φ (h₁.set l v) h₂) ((Core.SPComp.set l v).bind f) c₂ Ψ

One-sided put on the left

theorem CatCrypt.Relational.rHoare_put_r {τ : Type u_5} {ρ : Type u_6} {Φ : RPre} {Ψ : RPost ρ τ} (l : Core.Location) (v : l.ty) {c₁ : Core.SPComp ρ} {f : UnitCore.SPComp τ} (h : rHoare (fun (h₁ h₂ : Core.Heap) => Φ h₁ h₂) c₁ (f ()) Ψ) :
rHoare (fun (h₁ h₂ : Core.Heap) => Φ h₁ (h₂.set l v)) c₁ ((Core.SPComp.set l v).bind f) Ψ

One-sided put on the right

Failure and Assertion Rules #

theorem CatCrypt.Relational.rHoare_fail {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} :

Failure on both sides: when both programs fail, any postcondition holds vacuously. Note: With coupling-based semantics, failure on one side requires failure on the other for a valid coupling to exist (since marginals must match).

theorem CatCrypt.Relational.rHoare_assert {Φ : RPre} (P : Prop) [Decidable P] (hP : ∀ (h₁ h₂ : Core.Heap), Φ h₁ h₂P) :
rHoare Φ (Core.SPComp.assert P) (Core.SPComp.assert P) fun (x : Unit) (h₁ : Core.Heap) (x_1 : Unit) (h₂ : Core.Heap) => Φ h₁ h₂

Assertion: if precondition implies P, assertion succeeds

Conditional Rules #

theorem CatCrypt.Relational.rHoare_ite {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} (b : Bool) {c₁t c₁f : Core.SPComp α} {c₂t c₂f : Core.SPComp β} (ht : rHoare Φ c₁t c₂t Ψ) (hf : rHoare Φ c₁f c₂f Ψ) :
rHoare Φ (if b = true then c₁t else c₁f) (if b = true then c₂t else c₂f) Ψ

Synchronized conditional: when both sides branch on the same Bool value, prove each branch separately.

This is the standard pRHL if rule. After a synchronized get, both sides evaluate the same condition, so we case-split on the shared value.

theorem CatCrypt.Relational.rHoare_if {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} {b₁ b₂ : Bool} {c₁t c₁f : Core.SPComp α} {c₂t c₂f : Core.SPComp β} (hb : b₁ = b₂) (ht : rHoare Φ c₁t c₂t Ψ) (hf : rHoare Φ c₁f c₂f Ψ) :
rHoare Φ (if b₁ = true then c₁t else c₁f) (if b₂ = true then c₂t else c₂f) Ψ

Synchronized conditional with different conditions: when conditions agree (provable from equality or other means), prove each branch separately.

theorem CatCrypt.Relational.rHoare_dite {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} (P : Prop) [Decidable P] {c₁t : PCore.SPComp α} {c₁f : ¬PCore.SPComp α} {c₂t : PCore.SPComp β} {c₂f : ¬PCore.SPComp β} (ht : ∀ (hp : P), rHoare Φ (c₁t hp) (c₂t hp) Ψ) (hf : ∀ (hp : ¬P), rHoare Φ (c₁f hp) (c₂f hp) Ψ) :
rHoare Φ (dite P c₁t c₁f) (dite P c₂t c₂f) Ψ

Synchronized decidable conditional: both sides branch on the same decidable proposition P. The branch proofs may depend on P or ¬P.

Swap Rules #

These rules allow reordering independent operations in a computation. This is useful when the order of operations on the left and right sides differ.

theorem CatCrypt.Relational.rHoare_swap_lhs {γ : Type u_3} {δ : Type u_4} {Φ : RPre} {Ψ : RPost γ δ} {c₁ c₁' : Core.SPComp γ} {c₂ : Core.SPComp δ} (heq : ∀ (h : Core.Heap), c₁ h = c₁' h) (h : rHoare Φ c₁' c₂ Ψ) :
rHoare Φ c₁ c₂ Ψ

Swap on left: if two SPComp programs are equal (as functions from Heap), we can replace one with the other on the left side of a relational judgment.

theorem CatCrypt.Relational.rHoare_swap_rhs {γ : Type u_3} {δ : Type u_4} {Φ : RPre} {Ψ : RPost γ δ} {c₁ : Core.SPComp γ} {c₂ c₂' : Core.SPComp δ} (heq : ∀ (h : Core.Heap), c₂ h = c₂' h) (h : rHoare Φ c₁ c₂' Ψ) :
rHoare Φ c₁ c₂ Ψ

Swap on right: symmetric to swap_lhs

theorem CatCrypt.Relational.SPComp.swap_sample_sample (α β γ : Type) [Fintype α] [Fintype β] [Nonempty α] [Nonempty β] (f : αβCore.SPComp γ) (h : Core.Heap) :
(Core.SPComp.sample α >>= fun (x : α) => do let yCore.SPComp.sample β f x y) h = (Core.SPComp.sample β >>= fun (y : β) => do let xCore.SPComp.sample α f x y) h

Swap two consecutive samples. The order of two independent sampling operations can be exchanged because sampling from independent distributions commutes.

Uses the Fubini property for finite distributions.

theorem CatCrypt.Relational.SPComp.swap_sample_get (α β : Type) [Fintype α] [Nonempty α] (l : Core.Location) (f : αl.tyCore.SPComp β) (h : Core.Heap) :
(Core.SPComp.sample α >>= fun (x : α) => do let vCore.SPComp.get l f x v) h = (Core.SPComp.get l >>= fun (v : l.ty) => do let xCore.SPComp.sample α f x v) h

Swap sample followed by get on the left. Sample and get from a location commute because sampling doesn't affect the heap and get doesn't affect the distribution.

theorem CatCrypt.Relational.SPComp.swap_get_sample (α β : Type) [Fintype α] [Nonempty α] (l : Core.Location) (f : l.tyαCore.SPComp β) (h : Core.Heap) :
(Core.SPComp.get l >>= fun (v : l.ty) => do let xCore.SPComp.sample α f v x) h = (Core.SPComp.sample α >>= fun (x : α) => do let vCore.SPComp.get l f v x) h

Swap get followed by sample on the left. Get and sample commute because get is deterministic and doesn't affect the distribution of sample.

theorem CatCrypt.Relational.SPComp.swap_get_get (α : Type) (l₁ l₂ : Core.Location) (f : l₁.tyl₂.tyCore.SPComp α) (h : Core.Heap) :
(Core.SPComp.get l₁ >>= fun (v₁ : l₁.ty) => do let v₂Core.SPComp.get l₂ f v₁ v₂) h = (Core.SPComp.get l₂ >>= fun (v₂ : l₂.ty) => do let v₁Core.SPComp.get l₁ f v₁ v₂) h

Swap two consecutive gets from different locations. Get operations from independent locations can be reordered.

theorem CatCrypt.Relational.SPComp.swap_set_sample (α β : Type) [Fintype α] [Nonempty α] (l : Core.Location) (v : l.ty) (f : αCore.SPComp β) (h : Core.Heap) :
(Core.SPComp.set l v >>= fun (x : PUnit.{1}) => do let xCore.SPComp.sample α f x) h = (Core.SPComp.sample α >>= fun (x : α) => do Core.SPComp.set l v f x) h

Swap set followed by sample. Set and sample commute because set is deterministic and sample doesn't depend on heap.

theorem CatCrypt.Relational.SPComp.swap_sample_set (α β : Type) [Fintype α] [Nonempty α] (l : Core.Location) (v : l.ty) (f : αCore.SPComp β) (h : Core.Heap) :
(Core.SPComp.sample α >>= fun (x : α) => do Core.SPComp.set l v f x) h = (Core.SPComp.set l v >>= fun (x : PUnit.{1}) => do let xCore.SPComp.sample α f x) h

Swap sample followed by set. Sample and set commute because sample doesn't affect the heap value being set.

theorem CatCrypt.Relational.SPComp.swap_set_get (α : Type) (l₁ l₂ : Core.Location) (v : l₁.ty) (hne : l₁.id l₂.id) (f : l₂.tyCore.SPComp α) (h : Core.Heap) :
(Core.SPComp.set l₁ v >>= fun (x : PUnit.{1}) => do let xCore.SPComp.get l₂ f x) h = (Core.SPComp.get l₂ >>= fun (x : l₂.ty) => do Core.SPComp.set l₁ v f x) h

Swap set followed by get from a different location. Set and get from independent locations can be reordered.

theorem CatCrypt.Relational.SPComp.swap_get_set (α : Type) (l₁ l₂ : Core.Location) (v : l₂.ty) (hne : l₁.id l₂.id) (f : l₁.tyCore.SPComp α) (h : Core.Heap) :
(Core.SPComp.get l₁ >>= fun (x : l₁.ty) => do Core.SPComp.set l₂ v f x) h = (Core.SPComp.set l₂ v >>= fun (x : PUnit.{1}) => do let xCore.SPComp.get l₁ f x) h

Swap get followed by set to a different location. Get and set to independent locations can be reordered.

theorem CatCrypt.Relational.SPComp.swap_set_set (α : Type) (l₁ l₂ : Core.Location) (v₁ : l₁.ty) (v₂ : l₂.ty) (hne : l₁.id l₂.id) (k : Core.SPComp α) (h : Core.Heap) :
(Core.SPComp.set l₁ v₁ >>= fun (x : PUnit.{1}) => do Core.SPComp.set l₂ v₂ k) h = (Core.SPComp.set l₂ v₂ >>= fun (x : PUnit.{1}) => do Core.SPComp.set l₁ v₁ k) h

Swap two consecutive sets to different locations. Set operations to independent locations can be reordered.

Post-Processing and Factorization #

These rules establish key inequalities for security reductions. The post-processing lemma states that applying the same function to two distributions cannot increase the advantage of distinguishing them.

theorem CatCrypt.Relational.advantage_factorization {α β : Type} (f : αCore.SPComp β) (G₁ G₂ : Core.SPComp α) (A : βCore.SPComp Bool) :
Crypto.AdvantageA (G₁.bind f) (G₂.bind f) A = Crypto.AdvantageA G₁ G₂ fun (x : α) => (f x).bind A

Factorization: rewriting advantage in terms of intermediate computations.

If two games can be written as the same function f applied to different base computations, the advantage equals the advantage on the base computations with the composed distinguisher fun x => (f x).bind A.

This is an equality, not just an inequality, because bind is associative — post-composing both games with the same f neither increases nor decreases distinguishing advantage (it merely moves f into the distinguisher). The one-directional "post-processing cannot increase advantage" bound is the immediate le_of_eq consequence.

Advantage is symmetric: swapping the games doesn't change the advantage. This follows from the symmetric definition using max of both directions.

Contract Rules #

These rules establish equality between SPComp programs by eliminating redundant operations. They are used by contract tactics to simplify code before relational reasoning.

@[simp]
theorem CatCrypt.Relational.contract_get {α : Type} (l : Core.Location) (f : l.tyl.tyCore.SPComp α) :
(do let xCore.SPComp.get l let yCore.SPComp.get l f x y) = do let xCore.SPComp.get l f x x

Contract duplicate gets: getting the same location twice in sequence is equivalent to getting once and using the value twice.

x ← get l; y ← get l; f x y = x ← get l; f x x

Note: α is constrained to Type (universe 0) since l.ty : Type.

@[simp]
theorem CatCrypt.Relational.contract_put {α : Type} (l : Core.Location) (v v' : l.ty) (k : Core.SPComp α) :

Contract duplicate puts: putting to the same location twice in sequence is equivalent to just putting the second value.

put l v; put l v'; k = put l v'; k

Note: α is constrained to Type (universe 0) since l.ty : Type.

@[simp]
theorem CatCrypt.Relational.contract_put_get {α : Type} (l : Core.Location) (v : l.ty) (f : l.tyCore.SPComp α) :
(do Core.SPComp.set l v let xCore.SPComp.get l f x) = do Core.SPComp.set l v f v

Contract put-get: getting a location immediately after putting to it returns the value that was put.

put l v; x ← get l; f x = put l v; f v

Note: α is constrained to Type (universe 0) since l.ty : Type.

Dead Code Elimination Rules #

These rules eliminate unused computations from SPComp programs. They are used by the ProofFrog-inspired dead code elimination tactics.

theorem CatCrypt.Relational.SDistr.bind_const_eq {α β : Type} [Fintype α] [Nonempty α] (d : Prob.SDistr β) :
((Prob.SDistr.uniform α).bind fun (x : α) => d) = d

Const sample: sampling and ignoring the result is equivalent to identity.

This lemma is used by dead_sample to eliminate unused samples.

@[simp]
theorem CatCrypt.Relational.dead_sample {α β : Type} [Fintype α] [Nonempty α] (k : Core.SPComp β) :
((Core.SPComp.sample α).bind fun (x : α) => k) = k

Dead sample: if a sample result is not used, the sample can be eliminated.

let _ ← sample α; k = k

Note: This is sound because sampling from a uniform distribution and discarding the result is equivalent to doing nothing (lossless sampling).

@[simp]
theorem CatCrypt.Relational.dead_get {α : Type} (l : Core.Location) (k : Core.SPComp α) :
((Core.SPComp.get l).bind fun (x : l.ty) => k) = k

Dead get: if a get result is not used, the get can be eliminated.

let _ ← get l; k = k

Note: This is sound because get is a pure read with no side effects.

@[simp]
theorem CatCrypt.Relational.dead_pure {α β : Type} (v : α) (k : Core.SPComp β) :
((pure v).bind fun (x : α) => k) = k

Dead pure: if a pure result is not used, the pure can be eliminated.

let _ ← pure v; k = k

Copy Propagation Rules #

These rules help with copy propagation by providing lemmas for substituting known values.

theorem CatCrypt.Relational.get_value_eq {α : Type} (l : Core.Location) (f : l.tyCore.SPComp α) (h : Core.Heap) :
(Core.SPComp.get l >>= fun (x : l.ty) => f x) h = f (h.get l) h

Get followed by using the value: the continuation sees the heap value.

After x ← get l, we know x = h.get l. This lemma is used for copy propagation where we substitute the known value.

theorem CatCrypt.Relational.set_get_same {α : Type} (l : Core.Location) (v : l.ty) (f : l.tyCore.SPComp α) :
(do Core.SPComp.set l v let xCore.SPComp.get l f x) = do Core.SPComp.set l v f v

After set, the location has the set value.

(set l v; get l) returns v.

Unreachable Code Rules #

These rules help identify and eliminate unreachable code paths.

theorem CatCrypt.Relational.rHoare_false {α : Type u_1} {β : Type u_2} {Ψ : RPost α β} {c₁ : Core.SPComp α} {c₂ : Core.SPComp β} :
rHoare (fun (x x_1 : Core.Heap) => False) c₁ c₂ Ψ

If the precondition is False, any relational judgment holds trivially.

If we have an assertion #assert false, the code is unreachable.

theorem CatCrypt.Relational.rHoare_rewrite_lhs {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} {c₁ c₁' : Core.SPComp α} {c₂ : Core.SPComp β} (heq : c₁ = c₁') (h : rHoare Φ c₁' c₂ Ψ) :
rHoare Φ c₁ c₂ Ψ

Rewriting on LHS is sound for rHoare when the codes are equal.

theorem CatCrypt.Relational.rHoare_rewrite_rhs {α : Type u_1} {β : Type u_2} {Φ : RPre} {Ψ : RPost α β} {c₁ : Core.SPComp α} {c₂ c₂' : Core.SPComp β} (heq : c₂ = c₂') (h : rHoare Φ c₁ c₂' Ψ) :
rHoare Φ c₁ c₂ Ψ

Rewriting on RHS is sound for rHoare when the codes are equal.

Combined Coupling Steps #

These lemmas combine rHoare_bind + rHoare_sample_bij/rHoare_sample_same into a single step, eliminating the need for explicit intermediate postconditions.

theorem CatCrypt.Relational.rHoare_bij_step {α : Type u_5} {β : Type u_6} {γ : Type u_7} {δ : Type u_8} {Φ : RPre} {Ψ : RPost γ δ} [Fintype α] [Fintype β] [Nonempty α] [Nonempty β] (f : α β) {k₁ : αCore.SPComp γ} {k₂ : βCore.SPComp δ} (h : ∀ (a : α), rHoare Φ (k₁ a) (k₂ (f a)) Ψ) :
rHoare Φ ((Core.SPComp.sample α).bind k₁) ((Core.SPComp.sample β).bind k₂) Ψ

Combined bijection coupling step.

Combines rHoare_bind + rHoare_sample_bij + precondition adjustment in one step. When both sides sample and you have a bijection f, this reduces to proving the continuation for each sampled value a with the right side receiving f a.

Replaces the common 4-8 line pattern:

apply rHoare_bind (Ψ := fun a _ b _ => Φ h₁ h₂ ∧ f a = b)
· exact rHoare_sample_bij α β f
· intro a b
  apply rHoare_conseq ...

with a single application.

theorem CatCrypt.Relational.rHoare_bij_step_bare_r {α : Type u_5} {β : Type u_6} {γ : Type u_7} {Φ : RPre} {Ψ : RPost γ β} [Fintype α] [Fintype β] [Nonempty α] [Nonempty β] (f : α β) {k₁ : αCore.SPComp γ} (h : ∀ (a : α), rHoare Φ (k₁ a) (pure (f a)) Ψ) :

Bijection step with bare sample on the right side.

theorem CatCrypt.Relational.rHoare_bij_step_bare_l {α : Type u_5} {β : Type u_6} {δ : Type u_7} {Φ : RPre} {Ψ : RPost α δ} [Fintype α] [Fintype β] [Nonempty α] [Nonempty β] (f : α β) {k₂ : βCore.SPComp δ} (h : ∀ (a : α), rHoare Φ (pure a) (k₂ (f a)) Ψ) :

Bijection step with bare sample on the left side.

theorem CatCrypt.Relational.rHoare_same_step {α : Type u_5} {γ : Type u_6} {δ : Type u_7} {Φ : RPre} {Ψ : RPost γ δ} [Fintype α] [Nonempty α] {k₁ : αCore.SPComp γ} {k₂ : αCore.SPComp δ} (h : ∀ (a : α), rHoare Φ (k₁ a) (k₂ a) Ψ) :
rHoare Φ ((Core.SPComp.sample α).bind k₁) ((Core.SPComp.sample α).bind k₂) Ψ

Combined same-distribution coupling step.

Combines rHoare_bind + rHoare_sample_same in one step. When both sides sample from the same distribution, this reduces to proving the continuation for each sampled value a shared by both sides.

Replaces the common pattern:

apply liftR_bind liftR_uniform_same
intro a a' haa'; subst haa'

with a single application.

Canonicalization Lemmas #

These lemmas help put SPComp programs into a canonical form for easier comparison and simplification.

theorem CatCrypt.Relational.bind_assoc_lr {α β γ : Type} (c : Core.SPComp α) (f : αCore.SPComp β) (g : βCore.SPComp γ) :
(c.bind f).bind g = c.bind fun (a : α) => (f a).bind g

Bind of bind can be reassociated (left-to-right).

theorem CatCrypt.Relational.pure_bind_lr {α β : Type} (a : α) (f : αCore.SPComp β) :
(pure a).bind f = f a

Pure on the left of bind simplifies.

Bind with pure on the right is identity.

FoldM Rule #

theorem CatCrypt.Relational.rHoare_foldM {α : Type u_1} {Inv : ααRPre} (steps₁ steps₂ : List (αCore.SPComp α)) (h_len : steps₁.length = steps₂.length) (h_step : ∀ (i : ) (hi : i < steps₁.length) (a₁ a₂ : α), rHoare (Inv a₁ a₂) (steps₁[i] a₁) (steps₂[i] a₂) fun (b₁ : α) (h₁ : Core.Heap) (b₂ : α) (h₂ : Core.Heap) => Inv b₁ b₂ h₁ h₂) (a₁ a₂ : α) :
rHoare (Inv a₁ a₂) (Core.SPComp.foldM a₁ steps₁) (Core.SPComp.foldM a₂ steps₂) fun (b₁ : α) (h₁ : Core.Heap) (b₂ : α) (h₂ : Core.Heap) => Inv b₁ b₂ h₁ h₂

Relational Hoare rule for foldM: if each step preserves the invariant, then foldM preserves it.

Inv a₁ a₂ is the loop invariant parameterized by the accumulators on both sides. The precondition establishes Inv a₁ a₂, and each step must preserve it.