Interpretation from Deep to Shallow Embedding #
This file defines the interpretation of the deep embedding (RawCode) into the shallow embedding (SPComp). This bridges structural reasoning in the deep embedding with probabilistic reasoning in the shallow embedding.
Main definitions #
RawCode.eval- Interpret RawCode into SPCompDeepRHoare- Relational Hoare logic for deep embedding
Theorems #
eval_ret- Evaluation respects pureeval_bind- Evaluation respects bindeval_get- Evaluation of get uses SPComp.get directlyeval_put- Evaluation of put uses SPComp.set directlyeval_rHoare- Deep relational judgments imply shallow ones
Design rationale #
The interpretation serves multiple purposes:
- Semantic interpretation: Give meaning to deep code via the shallow embedding
- Soundness: Show that structural transformations preserve semantics
- Completeness: Any shallow computation can be represented deeply
- Bridging: Connect structural proofs (deep) with distribution proofs (shallow)
Unified Heap Model #
Both the deep embedding (RawCode) and shallow embedding (SPComp) now use the same heap model (Core.Heap) and location type (Core.Location). This eliminates the need for any heap translation axioms.
The key insight is that RawCode.get and RawCode.put use Core.Location directly, so evaluation is a simple mapping:
RawCode.get ℓevaluates toSPComp.get ℓ(with ULift for universe handling)RawCode.put ℓ vevaluates toSPComp.set ℓ v(returning ULift Unit)
References #
- SSProve: theories/Crypt/rhl_semantics/free_prot/free_prot_semantics.v
- Larsen and Schürmann, Nominal State-Separating Proofs
Evaluation: Deep to Shallow #
The main interpretation function that gives semantics to deep code. Since RawCode now uses Core.Location directly, no heap translation is needed.
Interpret a RawCode computation into the shallow SPComp monad.
This is the semantic interpretation that gives meaning to deep code. Each constructor of RawCode is mapped to the corresponding SPComp operation.
The interpretation is noncomputable because:
- Sample uses uniform distribution (noncomputable)
- Heap operations use classical choice for encoding/decoding
Key insight: Since RawCode uses Core.Location directly, get and put operations map directly to SPComp.get and SPComp.set with no translation needed.
Equations
- (CatCrypt.Deep.RawCode.ret x_3).eval = CatCrypt.Core.SPComp.pure x_3
- (c.bind f).eval = c.eval.bind fun (x : α) => (f x).eval
- (CatCrypt.Deep.RawCode.sample x✝).eval = CatCrypt.Core.SPComp.sample x✝
- (CatCrypt.Deep.RawCode.get ℓ).eval = (CatCrypt.Core.SPComp.get ℓ).bind fun (v : ℓ.ty) => CatCrypt.Core.SPComp.pure { down := v }
- (CatCrypt.Deep.RawCode.put ℓ v).eval = (CatCrypt.Core.SPComp.set ℓ v).bind fun (x : Unit) => CatCrypt.Core.SPComp.pure { down := () }
- CatCrypt.Deep.RawCode.fail.eval = CatCrypt.Core.SPComp.fail
- (CatCrypt.Deep.RawCode.oracleCall op dom x✝ x_3).eval = CatCrypt.Core.SPComp.fail
- (CatCrypt.Deep.RawCode.embed c).eval = c
Instances For
Evaluation properties #
These theorems show that evaluation preserves the monad structure and basic operations.
Evaluation respects pure/return
This is definitionally true from the definition of eval.
Evaluation of sample gives uniform distribution
Definitionally true from the definition of eval.
Evaluation of fail gives SPComp.fail
Definitionally true from the definition of eval.
Evaluation of oracle call gives SPComp.fail (unlinked oracle)
Oracle calls that haven't been substituted (via substOracle / link)
evaluate to failure. After linking, oracle calls are replaced with
concrete implementations and this case never arises.
Evaluation of get reads from the heap and lifts to ULift.
This is definitionally true from the definition of eval.
Evaluation of put writes to the heap and returns ULift Unit.
This is definitionally true from the definition of eval.
Evaluation with Oracle Handler #
evalWith generalizes eval by taking an oracle handler instead of
mapping all oracle calls to failure. This is the key definition for
stating that substOracle commutes with eval.
Interpret RawCode into SPComp, using a custom oracle handler.
Like eval, but instead of mapping oracleCall to SPComp.fail,
it calls the provided oracle function. This allows us to state
the semantic correctness of substOracle:
(code.substOracle env).eval = code.evalWith (fun op dom codom x => (env op dom codom x).eval)
When oracle maps everything to SPComp.fail, this reduces to eval.
Equations
- CatCrypt.Deep.RawCode.evalWith oracle (CatCrypt.Deep.RawCode.ret x_3) = CatCrypt.Core.SPComp.pure x_3
- CatCrypt.Deep.RawCode.evalWith oracle (c.bind f) = (CatCrypt.Deep.RawCode.evalWith oracle c).bind fun (x : α) => CatCrypt.Deep.RawCode.evalWith oracle (f x)
- CatCrypt.Deep.RawCode.evalWith oracle (CatCrypt.Deep.RawCode.sample x✝) = CatCrypt.Core.SPComp.sample x✝
- CatCrypt.Deep.RawCode.evalWith oracle (CatCrypt.Deep.RawCode.get ℓ) = (CatCrypt.Core.SPComp.get ℓ).bind fun (v : ℓ.ty) => CatCrypt.Core.SPComp.pure { down := v }
- CatCrypt.Deep.RawCode.evalWith oracle (CatCrypt.Deep.RawCode.put ℓ v) = (CatCrypt.Core.SPComp.set ℓ v).bind fun (x : Unit) => CatCrypt.Core.SPComp.pure { down := () }
- CatCrypt.Deep.RawCode.evalWith oracle CatCrypt.Deep.RawCode.fail = CatCrypt.Core.SPComp.fail
- CatCrypt.Deep.RawCode.evalWith oracle (CatCrypt.Deep.RawCode.oracleCall op dom x✝ x_3) = oracle op dom x✝ x_3
- CatCrypt.Deep.RawCode.evalWith oracle (CatCrypt.Deep.RawCode.embed c) = c
Instances For
evalWith on code without oracle calls equals eval, regardless of oracle handler.
This is the semantic version of substOracle_eq_self: if the code never
invokes any oracle, the oracle handler is irrelevant.
Correctness of substOracle #
The central theorem: substituting oracle calls and then evaluating is the same
as evaluating with the oracle handler that evaluates the substituted code.
This is proved by structural induction on RawCode.
Correctness of substOracle: Evaluating code after oracle substitution is the same as evaluating the original code with an oracle handler that evaluates the substituted implementations.
This is the key semantic theorem for package linking. It says that
substOracle (a syntactic operation on the free monad) commutes with
eval (the semantic interpretation):
substOracle env ; eval = evalWith (eval ∘ env)
The proof is by structural induction on RawCode. Each case is
straightforward:
ret,sample,get,put,fail:substOraclepreserves these, so both sides agree definitionally.bind c k: by induction, the substitution distributes through bind.oracleCall op dom codom x:substOraclereplaces this withenv op dom codom x, andevalWithapplies the oracle handler, giving(env op dom codom x).evalon both sides.
evalWith composition #
The key theorem for link associativity: composing evalWith through substOracle produces the same result as a single evalWith with the composed handler.
Composing evalWith through substOracle: evaluating substituted code with a handler is the same as evaluating the original code with a composed handler.
This generalizes eval_substOracle by using an arbitrary handler instead of eval.
Two evalWith handlers that agree produce the same SPComp.
This is useful for showing that two different oracle environments produce the same evaluation when they agree on all oracle calls.
substOracle commutes with type casts (▸).
When h : α = β and c : RawCode β, substituting oracles in the
cast code h ▸ c : RawCode α is the same as casting the substituted code.
Proof: subst h makes the cast trivial.
evalWith commutes with type casts.
Like eval_cast but for evalWith with a custom oracle handler.
Proof: subst h makes the cast trivial.
evalWith for fail is just SPComp.fail.
Relational Hoare logic for deep embedding #
We define a relational Hoare logic for the deep embedding using the shallow embedding's rHoare. Since both use Core.Heap, no translation is needed.
Relational precondition (on Core.Heap)
Equations
Instances For
Relational postcondition
Equations
- CatCrypt.Deep.RPost α β = (α → CatCrypt.Core.Heap → β → CatCrypt.Core.Heap → Prop)
Instances For
Relational Hoare logic for deep embedding.
DeepRHoare pre c₁ c₂ post means:
For all heap pairs (h₁, h₂) satisfying pre,
after evaluating c₁ and c₂, the results satisfy post.
This is defined semantically using the shallow embedding: we evaluate the deep code to shallow code and use the existing rHoare. Since both embeddings use Core.Heap, no heap translation is needed.
Equations
- CatCrypt.Deep.DeepRHoare pre c₁ c₂ post = CatCrypt.Relational.rHoare pre c₁.eval c₂.eval post
Instances For
Deep relational judgments are exactly shallow relational judgments on evaluated code.
This is the key soundness theorem: proving something in the deep embedding's rHoare is equivalent to proving it in the shallow embedding (after evaluation).
The proof is immediate since DeepRHoare is defined in terms of rHoare.
Package evaluation #
Interpret deep packages to shallow packages.
NOTE: Package.lean uses a different structure with interfaces and ValidCode. This provides a simplified evaluation focusing on the core concepts. A full evaluation would need to handle interface matching and code validity.
Examples and Properties #
The evaluation equations are now proved as theorems using rfl, since
the definition of eval directly matches these equalities:
(RawCode.ret x).eval = SPComp.pure x(RawCode.fail).eval = SPComp.fail(c.bind f).eval = c.eval.bind (fun x => (f x).eval)(@RawCode.sample T _ _).eval = @SPComp.sample T _ _(RawCode.get ℓ).eval = SPComp.bind (SPComp.get ℓ) fun v => SPComp.pure ⟨v⟩(RawCode.put ℓ v).eval = SPComp.bind (SPComp.set ℓ v) fun _ => SPComp.pure ⟨()⟩
These are all marked with @[simp] for automatic simplification.
Design Notes #
Unified Heap Model #
The key insight that eliminates all heap translation axioms is using Core.Location and Core.Heap directly in the deep embedding:
Before (with axioms):
- Deep:
CatCrypt.Deep.Location(atom name + type) - Deep:
DeepHeap(heterogeneous,∀ ℓ, Option ℓ.ty) - Shallow:
CatCrypt.Core.Location(nat id + type) - Shallow:
Heap(homogeneous,Nat → Natwith encoding) - Required 8 axioms for translation between the two heap models
After (axiom-free):
- Both:
CatCrypt.Core.Location(nat id + type) - Both:
Core.Heap(homogeneous,Nat → Natwith encoding) - Direct evaluation:
RawCode.get ℓ→SPComp.get ℓ - No translation axioms needed!
Universe Handling #
Since Core.Location.ty : Type (universe 0) but RawCode : Type u → Type (u+1),
we use ULift to bridge the universes:
RawCode.get ℓ : RawCode (ULift.{u} ℓ.ty)RawCode.put ℓ v : RawCode (ULift.{u} Unit)
The evaluation wraps values in ULift.up and unwraps with .down as needed.