From ed4f17634bd2493dc7ca4ac814976bda3c6ec173 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Fri, 13 Nov 2026 00:29:56 +0000 Subject: [PATCH 1/7] =?UTF-8?q?refactor(Crypto/KEM):=20one=20Fujisaki?= =?UTF-8?q?=E2=80=93Okamoto=20oracle=20dispatcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Name the shared G/H/decapsulation dispatch of the FO IND-CCA games, including the `c = cStar ⇒ none` refusal, as `ROQuery.dispatch` (generic in the monad), and build every lazy, eager and simulated FO handler from it. Name the filtered decapsulation of the lazy simulators `filterDecaps`. No game changes meaning and no headline statement changes. Part of #4817. --- .../Protocols/KEM/FujisakiOkamoto/Bot.lean | 104 +++++++------- .../KEM/FujisakiOkamoto/NoCheck.lean | 9 +- .../KEM/FujisakiOkamoto/NotBot/Challenge.lean | 50 +++---- .../FujisakiOkamoto/NotBot/Correctness.lean | 5 +- .../KEM/FujisakiOkamoto/NotBot/Fallback.lean | 4 +- .../KEM/FujisakiOkamoto/NotBot/Hybrids.lean | 35 ++--- .../KEM/FujisakiOkamoto/NotBot/Lazy.lean | 36 +++-- .../FujisakiOkamoto/NotBot/Pushforward.lean | 2 +- .../KEM/FujisakiOkamoto/Security.lean | 133 +++++++++--------- .../KEM/FujisakiOkamoto/SpreadNecessity.lean | 6 +- .../Crypto/FujisakiOkamoto.lean | 7 +- .../Crypto/FujisakiOkamotoBot.lean | 3 +- .../Crypto/FujisakiOkamotoNotBot.lean | 2 +- .../security-games-and-primitives.md | 4 +- 14 files changed, 192 insertions(+), 208 deletions(-) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean index 8cb4843f43..bd65815016 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean @@ -178,13 +178,8 @@ under the lazy random oracle `G`, and return `some (H(false, m', c))` on re-encr `none` on explicit rejection. -/ def roBotHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : StateT (ROCache M R CT K) ProbComp (ROResp R K q) := - match q with - | .G m => roHashG S.pke.sampleCoins m - | .H x => roHashH S.sampleKey x - | .decaps c => - if c = cStar then - return none - else + ROQuery.dispatch (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) + (fun c => match S.pke.dec sk c with | none => return none | some m' => do @@ -193,7 +188,8 @@ def roBotHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) let k ← roHashH S.sampleKey (false, m', c) return some k else - return none + return none) + cStar q /-- The executable ROM IND-CCA security game for `FO^⊥ = U^⊥ ∘ T` with bit `b : Bool`. When `b = false`, the adversary receives the real encapsulated key `kReal = H(false, m*, c*)`; when @@ -225,10 +221,8 @@ decapsulation queries for `c ≠ cStar` evaluate `S.decapsBot G H pk sk c`. -/ noncomputable def eagerBotHandler (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : PMF (ROResp R K q) := - match q with - | .G m => PMF.pure (G m) - | .H x => PMF.pure (H x) - | .decaps c => PMF.pure (if c = cStar then none else S.decapsBot G H pk sk c) + ROQuery.dispatch (PMF.pure ∘ G) (PMF.pure ∘ H) (fun c => PMF.pure (S.decapsBot G H pk sk c)) + cStar q /-- The eager random-oracle IND-CCA game for `FO^⊥`, defined directly from `S.pke.keyGen`, `S.encapsBot`, and `S.decapsBot` over eagerly sampled random functions `G : M → R` and @@ -252,17 +246,13 @@ private def roTablesOf (st : ROCache M R CT K) : ROTables M R CT K := /-- `roBotHandler` on represented `(tG, tH)` tables via `RandomOracle.answer`. -/ private noncomputable def answerBotROHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) (q : ROQuery M CT) : StateT (ROTables M R CT K) PMF (ROResp R K q) := fun t => - match q with - | .G m => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map - fun z => (z.1, (z.2, t.2)) - | .H x => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x t.2).map - fun z => (z.1, (t.1, z.2)) - | .decaps c => - if c = cStar then PMF.pure (none, t) - else + (cStar : CT) (q : ROQuery M CT) : StateT (ROTables M R CT K) PMF (ROResp R K q) := + ROQuery.dispatch + (fun m t => (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map + fun z => (z.1, (z.2, t.2))) + (fun x t => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x t.2).map + fun z => (z.1, (t.1, z.2))) + (fun c t => match S.pke.dec sk c with | none => PMF.pure (none, t) | some m' => @@ -271,7 +261,8 @@ private noncomputable def answerBotROHandler (S : Scheme PK SK M R CT K) (pk : P (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m', c) t.2).map fun z => (some z.1, (y.2, z.2)) else - PMF.pure (none, (y.2, t.2)) + PMF.pure (none, (y.2, t.2))) + cStar q private theorem roBotHandler_map_roTablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) (st : ROCache M R CT K) : @@ -288,9 +279,9 @@ private theorem roBotHandler_map_roTablesOf (S : Scheme PK SK M R CT K) (pk : PK (fun _ => rfl) | decaps c => by_cases hc : c = cStar - · simp only [roBotHandler, answerBotROHandler, hc, ↓reduceIte] + · simp only [roBotHandler, answerBotROHandler, hc, ↓reduceIte, ROQuery.dispatch] exact PMF.pure_map _ _ - · simp only [roBotHandler, answerBotROHandler, hc, ↓reduceIte] + · simp only [roBotHandler, answerBotROHandler, hc, ↓reduceIte, ROQuery.dispatch] cases hdec : S.pke.dec sk c with | none => exact PMF.pure_map _ _ @@ -342,13 +333,13 @@ private theorem answerBotROHandler_sim [Fintype M] [Finite R] [Fintype CT] [Fini cases q with | G m => simp only [answerBotROHandler, completeROTables, eagerBotStateHandler, eagerBotHandler, - pmf_norm] + pmf_norm, ROQuery.dispatch] rw [answer_bind_complete] · rfl · intros; rfl | H x => simp only [answerBotROHandler, completeROTables, eagerBotStateHandler, eagerBotHandler, - pmf_norm, PMF.map] + pmf_norm, PMF.map, ROQuery.dispatch] rw [answer_bind_complete₁] · pmf_swap · intros; rfl @@ -357,9 +348,10 @@ private theorem answerBotROHandler_sim [Fintype M] [Finite R] [Fintype CT] [Fini (fun x => (S.completeROTables x.2).map (x.1, ·)) = (S.completeROTables (tG, tH)).bind fun T => (S.eagerBotHandler T.1 T.2 pk sk cStar (.decaps c)).map (·, T) - dsimp only [answerBotROHandler, completeROTables, eagerBotHandler, decapsBot, decT, encT] + dsimp only [answerBotROHandler, completeROTables, eagerBotHandler, decapsBot, decT, encT, + ROQuery.dispatch] by_cases hc : c = cStar - · simp only [hc, ↓reduceIte, pmf_norm, PMF.map] + · simp only [hc, ↓reduceIte, pure, StateT.pure, pmf_norm, PMF.map] · simp only [hc, ↓reduceIte] cases hdec : S.pke.dec sk c with | none => @@ -466,14 +458,13 @@ decapsulation queries `c ≠ cStar` return `some (Hq c)` when `decT` accepts `c` noncomputable def eagerCoupledBotHandler (S : Scheme PK SK M R CT K) (G : M → R) (Hq : CT → K) (Hraw : Bool × M × CT → K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : PMF (ROResp R K q) := - match q with - | .G m => PMF.pure (G m) - | .H x => PMF.pure (S.coupledH G Hq Hraw pk sk x) - | .decaps c => - PMF.pure (if c = cStar then none else - match decT S.pke G pk sk c with - | some _ => some (Hq c) - | none => none) + ROQuery.dispatch (PMF.pure ∘ G) (PMF.pure ∘ S.coupledH G Hq Hraw pk sk) + (fun c => + PMF.pure + (match decT S.pke G pk sk c with + | some _ => some (Hq c) + | none => none)) + cStar q /-- The eager coupled game for `FO^⊥`: independent tables `G`, `Hq`, `Hraw`, challenge `c* = Enc(pk, m*; G(m*))` with real key `coupledH (false, m*, c*)`, and decapsulation gated by @@ -499,7 +490,7 @@ private theorem eagerBotHandler_coupledH (S : Scheme PK SK M R CT K) (G : M → | G m => rfl | H x => rfl | decaps c => - simp only [eagerBotHandler, eagerCoupledBotHandler, decapsBot] + simp only [eagerBotHandler, eagerCoupledBotHandler, decapsBot, ROQuery.dispatch] by_cases hc : c = cStar · simp [hc] · simp only [hc, ↓reduceIte] @@ -551,15 +542,12 @@ def cachedCoupledBotGame (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT /-- `coupledFilterHandler (cachedCoupledBotCheck S.pke pk sk)` on represented tables `(tG, tH, tQ)`. -/ private noncomputable def answerCachedBotHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) (q : ROQuery M CT) : StateT (Tables M R CT K) PMF (ROResp R K q) := fun t => - match q with - | .G m => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map - fun z => (z.1, (z.2, t.2.1, t.2.2)) - | .H x => S.answerHashH pk sk x t - | .decaps c => - if c = cStar then PMF.pure (none, t) - else + (cStar : CT) (q : ROQuery M CT) : StateT (Tables M R CT K) PMF (ROResp R K q) := + ROQuery.dispatch + (fun m t => (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map + fun z => (z.1, (z.2, t.2.1, t.2.2))) + (S.answerHashH pk sk) + (fun c t => match S.pke.dec sk c with | none => PMF.pure (none, t) | some m' => @@ -570,7 +558,8 @@ private noncomputable def answerCachedBotHandler (S : Scheme PK SK M R CT K) (pk (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) c t.2.2).map fun z => (some z.1, (t.1, t.2.1, z.2)) else - PMF.pure (none, t) + PMF.pure (none, t)) + cStar q private theorem cachedCoupledBotHandler_map_tablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) (st : SimState M R CT K) : @@ -586,11 +575,12 @@ private theorem cachedCoupledBotHandler_map_tablesOf (S : Scheme PK SK M R CT K) | H x => exact S.coupledHashH_map_tablesOf pk sk x st | decaps c => by_cases hc : c = cStar - · simp only [coupledFilterHandler, simFilterDispatch, answerCachedBotHandler, hc, ↓reduceIte] + · simp only [coupledFilterHandler, simFilterDispatch, answerCachedBotHandler, hc, ↓reduceIte, + ROQuery.dispatch] exact PMF.pure_map _ _ - · dsimp only [coupledFilterHandler, simFilterDispatch, answerCachedBotHandler, - cachedCoupledBotCheck, tablesOf, RandomOracle.ofList] + · dsimp only [coupledFilterHandler, simFilterDispatch, answerCachedBotHandler, ROQuery.dispatch] simp only [hc, ↓reduceIte] + dsimp only [filterDecaps, cachedCoupledBotCheck, tablesOf, RandomOracle.ofList] cases hdec : S.pke.dec sk c with | none => exact PMF.pure_map _ _ | some m' => @@ -662,7 +652,7 @@ private theorem answerCachedBotHandler_closeTo_sim (S : Scheme PK SK M R CT K) { (S.completeTables (tG, tH, tQ)).bind (S.eagerCoupledBotStateHandler pk sk cStar (.G m)) by rw [h]; exact (CloseTo.refl _).mono zero_le simp only [answerCachedBotHandler, completeTables, eagerCoupledBotStateHandler, - eagerCoupledBotHandler, pmf_norm] + eagerCoupledBotHandler, pmf_norm, ROQuery.dispatch] rw [answer_bind_complete] · rfl · intros; rfl @@ -673,7 +663,7 @@ private theorem answerCachedBotHandler_closeTo_sim (S : Scheme PK SK M R CT K) { rw [h]; exact (CloseTo.refl _).mono zero_le rcases x with ⟨_ | _, m, c⟩ · simp only [answerCachedBotHandler, answerHashH, completeTables, eagerCoupledBotStateHandler, - eagerCoupledBotHandler, coupledH, pmf_norm] + eagerCoupledBotHandler, coupledH, pmf_norm, ROQuery.dispatch] refine Eq.trans ?_ (answer_bind_complete _ tG m (fun x G => (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tH).bind fun H => (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tQ).bind fun Q => @@ -690,7 +680,7 @@ private theorem answerCachedBotHandler_closeTo_sim (S : Scheme PK SK M R CT K) { · pmf_swap · intros; rfl · simp only [answerCachedBotHandler, answerHashH, completeTables, eagerCoupledBotStateHandler, - eagerCoupledBotHandler, coupledH, pmf_norm, PMF.map] + eagerCoupledBotHandler, coupledH, pmf_norm, PMF.map, ROQuery.dispatch] rw [answer_bind_complete₁] · pmf_swap · intros; rfl @@ -702,7 +692,7 @@ private theorem answerCachedBotHandler_closeTo_sim (S : Scheme PK SK M R CT K) { ((S.completeTables (tG, tH, tQ)).bind fun T => (S.eagerCoupledBotHandler T.1 T.2.2 T.2.1 pk sk cStar (.decaps c)).map (·, T)) (ENNReal.ofReal ((2 : ℝ) ^ (-γ))) - dsimp only [answerCachedBotHandler, eagerCoupledBotHandler, decT] + dsimp only [answerCachedBotHandler, eagerCoupledBotHandler, decT, ROQuery.dispatch] have hnone_sim : CloseTo ((PMF.pure (none, (tG, tH, tQ))).bind fun x : Option K × Tables M R CT K => (S.completeTables x.2).map (x.1, ·)) @@ -908,7 +898,7 @@ theorem closeTo_cachedCoupledBotGame_eagerCoupledBotGame dsimp only [eagerCoupledBotGame] simp only [completeTables, tablesOf, RandomOracle.complete_ofList_singleton, RandomOracle.ofList_nil, RandomOracle.complete_empty, OracleComp.interpret_bind, - OracleComp.interpret_query, eagerCoupledBotHandler, pmf_norm, encT] + OracleComp.interpret_query, eagerCoupledBotHandler, pmf_norm, encT, ROQuery.dispatch] congr 1 with ⟨pk, sk⟩ : 1 dsimp only pmf_hoist (ProbComp.eval S.sampleMsg) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean index 4dc0315ef0..29c01f737f 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean @@ -179,11 +179,10 @@ theorem decapsNoCheck_of_dec_eq_some (S : Scheme PK SK M R CT K) (H : Bool × M `dk` and challenge ciphertext `cStar`: decapsulation queries use `decapsNoCheck` and reject `cStar`. -/ noncomputable def eagerNoCheckOracle (S : Scheme PK SK M R CT K) (G : M → R) - (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (cStar : CT) : - (q : ROQuery M CT) → PMF (ROResp R K q) - | .G m => PMF.pure (G m) - | .H x => PMF.pure (H x) - | .decaps c => PMF.pure (if c = cStar then none else some (S.decapsNoCheck H dk c)) + (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : + PMF (ROResp R K q) := + ROQuery.dispatch (PMF.pure ∘ G) (PMF.pure ∘ H) (fun c => PMF.pure (some (S.decapsNoCheck H dk c))) + cStar q /-- The eager ROM IND-CCA game of `FO-notbot` with decapsulation `decapsNoCheck`; otherwise as `eagerIndCcaGame`. -/ diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean index fca9457123..2d2485f6a9 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean @@ -226,7 +226,8 @@ private theorem simHandler_step (S : Scheme PK SK M R CT K) (d : List (M × R) | H x => rcases x with ⟨isFb, m, c⟩ cases isFb - · dsimp only [simFilterHandler, simFilterDispatch, simHashH] at hmem + · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.dispatch, + filterDecaps] at hmem change (res, st') ∈ (ProbComp.eval (simHashG S.pke.sampleCoins m st >>= fun z => (if S.pke.encDet pk m z.fst = c then simHashHq S.sampleKey c @@ -245,11 +246,12 @@ private theorem simHandler_step (S : Scheme PK SK M R CT K) (d : List (M × R) · simp only [henc, ↓reduceIte] at hmem₂ obtain ⟨hg, hq'⟩ := simHashHRaw_support (z := (res, st')) hmem₂ exact ⟨hg ▸ hsub, fun h => Or.inl (hhq ▸ hq' ▸ h)⟩ - · dsimp only [simFilterHandler, simFilterDispatch, simHashH] at hmem + · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.dispatch, + filterDecaps] at hmem obtain ⟨hg, hq'⟩ := simHashHRaw_support (z := (res, st')) hmem exact ⟨hg ▸ List.Subset.refl _, fun h => Or.inl (hq' ▸ h)⟩ | decaps c => - dsimp only [simFilterHandler, simFilterDispatch] at hmem + dsimp only [simFilterHandler, simFilterDispatch, ROQuery.dispatch, filterDecaps] at hmem by_cases hc : c = cStar · simp only [hc, ↓reduceIte] at hmem change (res, st') ∈ (ProbComp.eval (pure (none, st))).support at hmem @@ -258,12 +260,12 @@ private theorem simHandler_step (S : Scheme PK SK M R CT K) (d : List (M × R) exact ⟨List.Subset.refl _, Or.inl⟩ · simp only [hc, ↓reduceIte] at hmem cases hd : d st.gCache c - · simp only [hd, Bool.cond_false] at hmem + · simp only [hd, filterDecaps, Bool.cond_false] at hmem change (res, st') ∈ (ProbComp.eval (pure (none, st))).support at hmem simp only [ProbComp.eval_pure, PMF.mem_support_pure_iff, Prod.mk.injEq] at hmem rcases hmem with ⟨-, rfl⟩ exact ⟨List.Subset.refl _, Or.inl⟩ - · simp only [hd, Bool.cond_true] at hmem + · simp only [hd, filterDecaps, Bool.cond_true] at hmem change (res, st') ∈ (ProbComp.eval (simHashHq S.sampleKey c st >>= fun a => pure (some a.fst, a.snd))).support at hmem rw [ProbComp.eval_bind, PMF.mem_support_bind_iff] at hmem @@ -334,7 +336,7 @@ private theorem simHandler_reEnc_le (S : Scheme PK SK M R CT K) (d : List (M × simp only [ROQuery.gCost, ROQuery.hCost, Nat.cast_one, Nat.cast_zero, one_mul, zero_mul, zero_add] cases isFb - · dsimp only [simFilterHandler, simFilterDispatch, simHashH] + · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.dispatch, filterDecaps] change (ProbComp.eval (simHashG S.pke.sampleCoins m st >>= fun z => (if S.pke.encDet pk m z.fst = c then simHashHq S.sampleKey c @@ -351,7 +353,7 @@ private theorem simHandler_reEnc_le (S : Scheme PK SK M R CT K) (d : List (M × exact (simHashHq_support (cStar := cStar) hy).1 · simp only [henc, ↓reduceIte] at hy exact (simHashHRaw_support hy).1 - · dsimp only [simFilterHandler, simFilterDispatch, simHashH] + · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.dispatch, filterDecaps] exact (toOuterMeasure_gCache_eq_zero _ st (fun g => ∃ p ∈ g, S.pke.encDet pk p.1 p.2 = cStar) hst fun y hy => (simHashHRaw_support hy).1).trans_le zero_le @@ -359,7 +361,7 @@ private theorem simHandler_reEnc_le (S : Scheme PK SK M R CT K) (d : List (M × simp only [ROQuery.gCost, ROQuery.hCost, Nat.cast_zero, zero_mul, add_zero] refine (toOuterMeasure_gCache_eq_zero _ st (fun g => ∃ p ∈ g, S.pke.encDet pk p.1 p.2 = cStar) hst fun y hy => ?_).le - dsimp only [simFilterHandler, simFilterDispatch] at hy + dsimp only [simFilterHandler, simFilterDispatch, ROQuery.dispatch, filterDecaps] at hy exact gCache_eq_of_mem_support_filterDecaps _ _ _ _ _ _ hy /-- **Bad-event split**: from the empty state, the simulator records `mStar` in @@ -459,21 +461,13 @@ private def genHandler (S : Scheme PK SK M R CT K) (d : List (M × R) → CT → (gs : M → ProbComp R) (hs : CT → ProbComp K) (pk : PK) (cStar : CT) (q : ROQuery M CT) : StateT (SimState M R CT K) ProbComp (ROResp R K q) := - match q with - | .G m => genHashG gs m - | .H (false, m, c) => do - let r ← genHashG gs m - if S.pke.encDet pk m r = c then genHashHq hs c else simHashHRaw S.sampleKey (false, m, c) - | .H (true, m, c) => simHashHRaw S.sampleKey (true, m, c) - | .decaps c => - if c = cStar then - return none - else fun st => - cond (d st.gCache c) - ((do - let k ← genHashHq hs c - return some k) st) - ((return none : StateT (SimState M R CT K) ProbComp (Option K)) st) + ROQuery.dispatch (genHashG gs) + (fun + | (false, m, c) => do + let r ← genHashG gs m + if S.pke.encDet pk m r = c then genHashHq hs c else simHashHRaw S.sampleKey (false, m, c) + | (true, m, c) => simHashHRaw S.sampleKey (true, m, c)) + (filterDecaps (genHashHq hs) d) cStar q private theorem simFilterHandler_eq_genHandler (S : Scheme PK SK M R CT K) (d : List (M × R) → CT → Bool) (pk : PK) (cStar : CT) : @@ -525,10 +519,8 @@ private theorem eval_genHandler_decaps (S : Scheme PK SK M R CT K) ((ProbComp.eval (genHashHq hs c s)).map fun z => (some z.1, z.2)) (PMF.pure (none, s)) := by change ProbComp.eval ((if c = cStar then (return none : StateT (SimState M R CT K) ProbComp - (Option K)) else fun st => cond (d st.gCache c) - ((do let k ← genHashHq hs c; return some k) st) - ((return none : StateT (SimState M R CT K) ProbComp (Option K)) st)) s) = _ - simp only [hc, ↓reduceIte] + (Option K)) else filterDecaps (genHashHq hs) d c) s) = _ + simp only [hc, ↓reduceIte, filterDecaps] cases d s.gCache c · exact ProbComp.eval_pure _ · simp only [Bool.cond_true] @@ -542,9 +534,7 @@ private theorem eval_genHandler_decaps_self (S : Scheme PK SK M R CT K) (hs : CT → ProbComp K) (pk : PK) (cStar : CT) (s : SimState M R CT K) : ProbComp.eval (S.genHandler d gs hs pk cStar (.decaps cStar) s) = PMF.pure (none, s) := by change ProbComp.eval ((if cStar = cStar then (return none : StateT (SimState M R CT K) - ProbComp (Option K)) else fun st => cond (d st.gCache cStar) - ((do let k ← genHashHq hs cStar; return some k) st) - ((return none : StateT (SimState M R CT K) ProbComp (Option K)) st)) s) = _ + ProbComp (Option K)) else filterDecaps (genHashHq hs) d cStar) s) = _ simp only [↓reduceIte] exact ProbComp.eval_pure _ diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean index b08cb2773c..843128c3fc 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean @@ -245,7 +245,7 @@ private theorem testHandler_decaps_gCache_eq (S : Scheme PK SK M R CT K) (x : Option K × SimState M R CT K) (hx : x ∈ (ProbComp.eval (S.testHandler d cStar t (.decaps c) st)).support) : x.2.gCache = st.gCache := by - dsimp only [testHandler, simFilterDispatch] at hx + dsimp only [testHandler, simFilterDispatch, ROQuery.dispatch, filterDecaps] at hx exact gCache_eq_of_mem_support_filterDecaps _ _ _ _ _ _ hx private theorem testHandler_gCache_subset (S : Scheme PK SK M R CT K) @@ -303,7 +303,8 @@ private theorem identicalUntilBad_testHandler (S : Scheme PK SK M R CT K) (pk : · exact eval_testHashH_apply_congr S pk sk m c _ _ (ht m c) s (a, s') (of_not_not hs') · rfl · by_cases hc : c = cStar <;> - simp [testHandler, simFilterDispatch, hc, hd s c (of_not_not hs)] + simp [testHandler, simFilterDispatch, hc, hd s c (of_not_not hs), ROQuery.dispatch, + filterDecaps] absorb_first q s hs x hx := fun hg => hs (hg.of_subset (testHandler_gCache_subset S d₁ cStar t₁ q s x hx)) absorb_second q s hs x hx := fun hg => diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean index b4410e4117..eeef86e336 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean @@ -148,9 +148,9 @@ private theorem eagerHandler_fbTable (S : Scheme PK SK M R CT K) (G : M → R) S.eagerFbHandler G H Hfb ⟨sk, pk, s⟩ cStar q := by cases q with | G m => rfl - | H x => simp [eagerHandler, eagerFbHandler, fbTable, hq x rfl] + | H x => simp [eagerHandler, eagerFbHandler, fbTable, hq x rfl, ROQuery.dispatch] | decaps c => - simp only [eagerHandler, eagerFbHandler, decapsNotBot] + simp only [eagerHandler, eagerFbHandler, decapsNotBot, ROQuery.dispatch] cases decT S.pke G pk sk c <;> simp [fbTable] /-- The logged oracles of the two games are identical until the log contains the seed. -/ diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean index d5010473b7..42112d87c1 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean @@ -76,14 +76,14 @@ variable [DecidableEq M] [DecidableEq CT] noncomputable def eagerFbHandler (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (Hfb : CT → K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : PMF (ROResp R K q) := - match q with - | .G m => PMF.pure (G m) - | .H x => PMF.pure (H x) - | .decaps c => - PMF.pure (if c = cStar then none else - some (match decT S.pke G dk.pk dk.sk c with - | some m' => H (false, m', c) - | none => Hfb c)) + ROQuery.dispatch (PMF.pure ∘ G) (PMF.pure ∘ H) + (fun c => + PMF.pure + (some + (match decT S.pke G dk.pk dk.sk c with + | some m' => H (false, m', c) + | none => Hfb c))) + cStar q /-- The eager IND-CCA game of `FO-notbot` with implicit-rejection keys drawn from an independent table `Hfb : CT → K` instead of `H(true, s, ·)`. -/ @@ -113,10 +113,8 @@ decapsulation query `c ≠ cStar` reads `Hq c`. -/ noncomputable def eagerCoupledHandler (S : Scheme PK SK M R CT K) (G : M → R) (Hq : CT → K) (Hraw : Bool × M × CT → K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : PMF (ROResp R K q) := - match q with - | .G m => PMF.pure (G m) - | .H x => PMF.pure (S.coupledH G Hq Hraw pk sk x) - | .decaps c => PMF.pure (if c = cStar then none else some (Hq c)) + ROQuery.dispatch (PMF.pure ∘ G) (PMF.pure ∘ S.coupledH G Hq Hraw pk sk) + (fun c => PMF.pure (some (Hq c))) cStar q /-- The eager coupled game: independent tables `G`, `Hq`, `Hraw`, challenge `c* = Enc(pk, m*; G(m*))` with real key `coupledH (false, m*, c*)`. -/ @@ -155,12 +153,9 @@ leaves `gCache` unchanged. -/ theorem gCache_eq_of_mem_support_filterDecaps (sampleKey : ProbComp K) (d : List (M × R) → CT → Bool) (cStar c : CT) (st : SimState M R CT K) (x : Option K × SimState M R CT K) - (hx : x ∈ (ProbComp.eval ((if c = cStar then return none else fun st => - cond (d st.gCache c) - ((do let k ← simHashHq sampleKey c; return some k : - StateT (SimState M R CT K) ProbComp (Option K)) st) - ((return none : StateT (SimState M R CT K) ProbComp (Option K)) st) : - StateT (SimState M R CT K) ProbComp (Option K)) st)).support) : + (hx : x ∈ (ProbComp.eval ((if c = cStar then return none else + filterDecaps (simHashHq sampleKey) d c : StateT (SimState M R CT K) ProbComp (Option K)) + st)).support) : x.2.gCache = st.gCache := by by_cases hc : c = cStar · simp only [hc, ↓reduceIte] at hx @@ -169,11 +164,11 @@ theorem gCache_eq_of_mem_support_filterDecaps (sampleKey : ProbComp K) rw [hx] · simp only [hc, ↓reduceIte] at hx cases hd : d st.gCache c - · simp only [hd, Bool.cond_false] at hx + · simp only [filterDecaps, hd, Bool.cond_false] at hx change x ∈ (ProbComp.eval (pure (none, st))).support at hx rw [ProbComp.eval_pure, PMF.mem_support_pure_iff] at hx rw [hx] - · simp only [hd, Bool.cond_true] at hx + · simp only [filterDecaps, hd, Bool.cond_true] at hx change x ∈ (ProbComp.eval (simHashHq sampleKey c st >>= fun a => pure (some a.fst, a.snd))).support at hx rw [ProbComp.eval_bind, PMF.mem_support_bind_iff] at hx diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean index adc0367fb2..824bd69abd 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean @@ -83,17 +83,14 @@ abbrev Tables (M R CT K : Type) : Type := /-- `coupledHandler` on represented tables, answered with `RandomOracle.answer`. -/ private noncomputable def answerHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) (q : ROQuery M CT) : StateT (Tables M R CT K) PMF (ROResp R K q) := fun t => - match q with - | .G m => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map - fun z => (z.1, (z.2, t.2.1, t.2.2)) - | .H x => S.answerHashH pk sk x t - | .decaps c => - if c = cStar then PMF.pure (none, t) - else - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) c t.2.2).map - fun z => (some z.1, (t.1, t.2.1, z.2)) + (cStar : CT) (q : ROQuery M CT) : StateT (Tables M R CT K) PMF (ROResp R K q) := + ROQuery.dispatch + (fun m t => (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map + fun z => (z.1, (z.2, t.2.1, t.2.2))) + (S.answerHashH pk sk) + (fun c t => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) c t.2.2).map + fun z => (some z.1, (t.1, t.2.1, z.2))) + cStar q /-- Mapping `SimState` to its represented tables turns `coupledHashH` into `answerHashH`. -/ theorem coupledHashH_map_tablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) @@ -138,10 +135,10 @@ private theorem coupledHandler_map_tablesOf (S : Scheme PK SK M R CT K) (pk : PK | decaps c => by_cases hc : c = cStar · simp only [coupledHandler, coupledFilterHandler, simFilterDispatch, answerHandler, hc, - ↓reduceIte] + ↓reduceIte, ROQuery.dispatch] exact PMF.pure_map _ _ · simp only [coupledHandler, coupledFilterHandler, simFilterDispatch, answerHandler, hc, - ↓reduceIte, Bool.cond_true] + ↓reduceIte, Bool.cond_true, ROQuery.dispatch, filterDecaps] change (ProbComp.eval (simHashHq S.sampleKey c st >>= fun vs => pure (some vs.1, vs.2))).map _ = _ refine Eq.trans ?_ (eval_zoom_memoize_map SimState.hqCache @@ -182,14 +179,15 @@ private theorem answerHandler_sim (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK rcases t with ⟨tG, tH, tQ⟩ cases q with | G m => - simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, pmf_norm] + simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, pmf_norm, + ROQuery.dispatch] rw [answer_bind_complete] · rfl · intros; rfl | H x => rcases x with ⟨_ | _, m, c⟩ · simp only [answerHandler, answerHashH, completeTables, eagerStateHandler, - eagerCoupledHandler, coupledH, pmf_norm] + eagerCoupledHandler, coupledH, pmf_norm, ROQuery.dispatch] refine Eq.trans ?_ (answer_bind_complete _ tG m (fun x G => (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tH).bind fun H => (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tQ).bind fun Q => @@ -206,16 +204,16 @@ private theorem answerHandler_sim (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK · pmf_swap · intros; rfl · simp only [answerHandler, answerHashH, completeTables, eagerStateHandler, - eagerCoupledHandler, coupledH, pmf_norm, PMF.map] + eagerCoupledHandler, coupledH, pmf_norm, PMF.map, ROQuery.dispatch] rw [answer_bind_complete₁] · pmf_swap · intros; rfl | decaps c => by_cases hc : c = cStar · simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, hc, - ↓reduceIte, pmf_norm, PMF.map] + ↓reduceIte, pure, StateT.pure, pmf_norm, PMF.map, ROQuery.dispatch] · simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, hc, - ↓reduceIte, pmf_norm, PMF.map] + ↓reduceIte, pmf_norm, PMF.map, ROQuery.dispatch] rw [answer_bind_complete₂] · pmf_swap · intros; rfl @@ -295,7 +293,7 @@ theorem eval_coupledGame_eq_eagerCoupledGame [Fintype M] [Finite R] [Fintype CT] simp only [completeTables, tablesOf, RandomOracle.complete_ofList_singleton, RandomOracle.ofList_nil, RandomOracle.complete_empty, OracleComp.interpret_bind, OracleComp.interpret_query, - eagerCoupledHandler, pmf_norm] + eagerCoupledHandler, pmf_norm, ROQuery.dispatch] pmf_swap simp only [hstep] pmf_hoist (ProbComp.eval S.sampleMsg) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean index 75a93f75c6..cbfa614f30 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean @@ -161,7 +161,7 @@ private theorem eagerFbHandler_reindex (S : Scheme PK SK M R CT K) (G : M → R) | G m => rfl | H x => rfl | decaps c => - simp only [eagerFbHandler, eagerCoupledHandler] + simp only [eagerFbHandler, eagerCoupledHandler, ROQuery.dispatch] by_cases hc : c = cStar · simp [hc] · simp only [hc, ↓reduceIte] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean index b0b6e9f030..4a65fdbe63 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean @@ -94,6 +94,9 @@ On top of those definitions, this module formalizes: `Cslib.Crypto.Protocols.FujisakiOkamoto.ROAdversary`: query/response family and deterministic oracle adversary for the ROM IND-CCA experiment with random oracles `G`, `H` and decapsulation oracle. +* `Cslib.Crypto.Protocols.FujisakiOkamoto.ROQuery.dispatch`: the oracle interface in any monad. + Every lazy, eager and simulated handler of the FO games and proof hybrids is this dispatcher + applied to its own `G`, `H` and decapsulation implementations. * `Cslib.Crypto.Protocols.FujisakiOkamoto.ROQueryBounds`: query-count bounds `(qG, qH)` on `G` and `H` expressed via `Cslib.OracleComp.IsCostBounded`. * `Cslib.Crypto.Protocols.FujisakiOkamoto.ROCache`, @@ -161,6 +164,17 @@ abbrev ROResp (R K : Type) : ROQuery M CT → Type | .H _ => K | .decaps _ => Option K +/-- The oracle interface of the FO IND-CCA games, in any monad: `G` and `H` queries go to the +given random-oracle implementations, decapsulation of the challenge `cStar` is refused with +`none`, and any other ciphertext is answered by `decaps`. Every lazy, eager and simulated handler +of the games and proof hybrids is this dispatcher applied to its own `G`, `H` and `decaps`. -/ +@[simp] def ROQuery.dispatch [DecidableEq CT] {m : Type → Type} [Monad m] (G : M → m R) + (H : Bool × M × CT → m K) (decaps : CT → m (Option K)) (cStar : CT) : + (q : ROQuery M CT) → m (ROResp R K q) + | .G msg => G msg + | .H x => H x + | .decaps c => if c = cStar then pure none else decaps c + /-- A deterministic IND-CCA oracle adversary in the Random Oracle Model receives `(pk, c*, k*)` and interacts with `G`, `H`, and the decapsulation oracle before outputting a Boolean guess. Randomized oracle adversaries reduce to these by fixing coins (see `ProbOracleComp.coinLaw`). -/ @@ -254,6 +268,17 @@ def simHashHq (sampleKey : ProbComp K) (c : CT) : StateT (SimState M R CT K) Pro RandomOracle.zoomStateT SimState.hqCache (fun st c' => { st with hqCache := c' }) (memoize (fun _ => sampleKey) c) +/-- Filtered decapsulation branch of the lazy `SimState` handlers: answer `some (Hq c)` when the +filter `d` accepts `c` on the current `G` cache, and `none` otherwise. -/ +def filterDecaps (Hq : CT → StateT (SimState M R CT K) ProbComp K) + (d : List (M × R) → CT → Bool) (c : CT) : StateT (SimState M R CT K) ProbComp (Option K) := + fun st => + cond (d st.gCache c) + ((do + let k ← Hq c + return some k) st) + ((return none : StateT (SimState M R CT K) ProbComp (Option K)) st) + end LazyOracles namespace Scheme @@ -266,13 +291,8 @@ re-encrypt under the lazy random oracle `G` and derive the key via the lazy rand `H`, falling back to `H(true, dk.fallback, c)` on implicit rejection. -/ def roHandler (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : StateT (ROCache M R CT K) ProbComp (ROResp R K q) := - match q with - | .G m => roHashG S.pke.sampleCoins m - | .H x => roHashH S.sampleKey x - | .decaps c => - if c = cStar then - return none - else + ROQuery.dispatch (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) + (fun c => match S.pke.dec dk.sk c with | none => do let k ← roHashH S.sampleKey (true, dk.fallback, c) @@ -284,7 +304,8 @@ def roHandler (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) return some k else do let k ← roHashH S.sampleKey (true, dk.fallback, c) - return some k + return some k) + cStar q /-- The executable ROM IND-CCA security game for `FO-notbot` with bit `b : Bool` (standard KEM IND-CCA experiment, not separately located in the source map above). When `b = false`, the @@ -317,10 +338,8 @@ noncomputable def indCcaRomAdvantage (S : Scheme PK SK M R CT K) decapsulation queries for `c ≠ cStar` evaluate `S.decapsNotBot G H dk c`. -/ noncomputable def eagerHandler (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : PMF (ROResp R K q) := - match q with - | .G m => PMF.pure (G m) - | .H x => PMF.pure (H x) - | .decaps c => PMF.pure (if c = cStar then none else some (S.decapsNotBot G H dk c)) + ROQuery.dispatch (PMF.pure ∘ G) (PMF.pure ∘ H) + (fun c => PMF.pure (some (S.decapsNotBot G H dk c))) cStar q /-- The eager random-oracle IND-CCA game for `FO-notbot` (standard KEM IND-CCA experiment, not separately located in the source map above), defined directly from `S.keyGenNotBot`, @@ -362,18 +381,7 @@ def simFilterDispatch (S : Scheme PK SK M R CT K) (hH : Bool × M × CT → StateT (SimState M R CT K) ProbComp K) (d : List (M × R) → CT → Bool) (cStar : CT) (q : ROQuery M CT) : StateT (SimState M R CT K) ProbComp (ROResp R K q) := - match q with - | .G m => simHashG S.pke.sampleCoins m - | .H x => hH x - | .decaps c => - if c = cStar then - return none - else fun st => - cond (d st.gCache c) - ((do - let k ← simHashHq S.sampleKey c - return some k) st) - ((return none : StateT (SimState M R CT K) ProbComp (Option K)) st) + ROQuery.dispatch (simHashG S.pke.sampleCoins) hH (filterDecaps (simHashHq S.sampleKey) d) cStar q /-- Keyless ROM simulator parameterized by a decapsulation acceptance filter `d : List (M × R) → CT → Bool` on the cached `G`-queries `gCache`. On `c ≠ cStar`, decapsulation @@ -433,39 +441,33 @@ lazily via `RandomOracle.answer`. Decapsulation uses `decT S.pke G dk.pk dk.sk c noncomputable def h1Handler (S : Scheme PK SK M R CT K) (G : M → R) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : - StateT (RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := fun tH => - match q with - | .G m => PMF.pure (G m, tH) - | .H x => RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x tH - | .decaps c => - if c = cStar then - PMF.pure (none, tH) - else + StateT (RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := + ROQuery.dispatch (fun m tH => PMF.pure (G m, tH)) + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey)) + (fun c tH => match decT S.pke G dk.pk dk.sk c with | some m' => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m', c) tH).map (fun (k, tH') => (some k, tH')) | none => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, tH')) + (fun (k, tH') => (some k, tH'))) + cStar q /-- Intermediate handler answering `G` and `H` via `RandomOracle.answer` on a pair of `RandomOracle.Cache` tables `(tG, tH)`. -/ noncomputable def roAnswerHandler (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : StateT (RandomOracle.Cache (fun _ : M => R) × - RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := fun (tG, tH) => - match q with - | .G m => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).map - (fun (r, tG') => (r, (tG', tH))) - | .H x => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x tH).map - (fun (k, tH') => (k, (tG, tH'))) - | .decaps c => - if c = cStar then - PMF.pure (none, (tG, tH)) - else + RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := + ROQuery.dispatch + (fun m (tG, tH) => + (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).map + (fun (r, tG') => (r, (tG', tH)))) + (fun x (tG, tH) => + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x tH).map + (fun (k, tH') => (k, (tG, tH')))) + (fun c (tG, tH) => match S.pke.dec dk.sk c with | none => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map @@ -477,7 +479,8 @@ noncomputable def roAnswerHandler (S : Scheme PK SK M R CT K) (fun (k, tH') => (some k, (tG', tH'))) else (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, (tG', tH'))) + (fun (k, tH') => (some k, (tG', tH')))) + cStar q /-- Evaluating `(do let k ← roHashH sampleKey x; return some k) st` and mapping `ROCache` via `RandomOracle.ofList`. -/ @@ -511,19 +514,19 @@ theorem roHandler_map_ofList (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK let dH : Bool × M × CT → PMF K := fun _ => ProbComp.eval S.sampleKey cases q with | G m => - dsimp only [roHandler, roHashG, roAnswerHandler] + dsimp only [roHandler, roHashG, roAnswerHandler, ROQuery.dispatch] rw [RandomOracle.eval_zoomStateT_memoize] have h := congrArg (PMF.map (fun (r, tG') => (r, (tG', RandomOracle.ofList st.hCache)))) (RandomOracle.memoize_map_ofList dG m st.gCache) simpa only [PMF.map_comp, Function.comp_def, dG] using h | H x => - dsimp only [roHandler, roHashH, roAnswerHandler] + dsimp only [roHandler, roHashH, roAnswerHandler, ROQuery.dispatch] rw [RandomOracle.eval_zoomStateT_memoize] have h := congrArg (PMF.map (fun (k, tH') => (k, (RandomOracle.ofList st.gCache, tH')))) (RandomOracle.memoize_map_ofList dH x st.hCache) simpa only [PMF.map_comp, Function.comp_def, dH] using h | decaps c => - dsimp only [roHandler, roAnswerHandler] + dsimp only [roHandler, roAnswerHandler, ROQuery.dispatch] by_cases hc : c = cStar · simp only [hc, ↓reduceIte] exact PMF.pure_map _ _ @@ -568,19 +571,19 @@ theorem roAnswerHandler_sim_h1 [Fintype M] [Finite R] simp only [Probability.PMF.monad_bind_eq_bind] cases q with | G m => - dsimp only [roAnswerHandler, h1Handler] + dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] have hc := congrArg (PMF.map (fun (r, G) => (r, (G, tH)))) (RandomOracle.answer_complete dG tG m) simpa only [Probability.PMF.monad_bind_eq_bind, PMF.bind_map, PMF.map_bind, PMF.map_comp, Function.comp_def, PMF.bind_bind, PMF.pure_bind, PMF.map, dG] using hc | H x => - dsimp only [roAnswerHandler, h1Handler] + dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] rw [PMF.bind_map, PMF.bind_comm] rfl | decaps c => - dsimp only [roAnswerHandler, h1Handler] + dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] by_cases hc : c = cStar - · simp [hc] + · simp [hc, pure, StateT.pure] · simp only [hc, ↓reduceIte] cases hdec : S.pke.dec dk.sk c with | none => @@ -713,15 +716,15 @@ theorem h1Handler_sim_eager [Fintype M] [Fintype CT] [Finite K] simp only [Probability.PMF.monad_bind_eq_bind] cases q with | G m => - simp [h1Handler, eagerHandler] + simp [h1Handler, eagerHandler, ROQuery.dispatch] | H x => - dsimp only [h1Handler, eagerHandler] + dsimp only [h1Handler, eagerHandler, ROQuery.dispatch] simpa only [Probability.PMF.monad_bind_eq_bind, PMF.pure_bind, PMF.map, Function.comp_def, dH] using RandomOracle.answer_complete dH tH x | decaps c => - dsimp only [h1Handler, eagerHandler, decapsNotBot] + dsimp only [h1Handler, eagerHandler, decapsNotBot, ROQuery.dispatch] by_cases hc : c = cStar - · simp [hc] + · simp [hc, pure, StateT.pure] · simp only [hc, ↓reduceIte] cases hdec : decT S.pke G dk.pk dk.sk c with | none => @@ -841,12 +844,14 @@ theorem simFilterHandler_gCache_length_le (S : Scheme PK SK M R CT K) st'.gCache.length ≤ st.gCache.length + q.ghCost := by cases q with | G m => - simpa only [simFilterHandler, simFilterDispatch, ROQuery.ghCost] using + simpa only [simFilterHandler, simFilterDispatch, ROQuery.ghCost, ROQuery.dispatch, + filterDecaps] using simHashG_gCache_length_le S.pke.sampleCoins m st (res, st') hmem | H x => rcases x with ⟨isFb, m, c⟩ cases isFb - · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.ghCost] at hmem ⊢ + · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.ghCost, ROQuery.dispatch, + filterDecaps] at hmem ⊢ change (res, st') ∈ (ProbComp.eval (simHashG S.pke.sampleCoins m st >>= fun z => (if S.pke.encDet pk m z.fst = c then simHashHq S.sampleKey c @@ -862,11 +867,13 @@ theorem simFilterHandler_gCache_length_le (S : Scheme PK SK M R CT K) · simp only [henc, ↓reduceIte] at hmem₂ rw [simHashHRaw_gCache_eq S.sampleKey (false, m, c) z.snd (res, st') hmem₂] exact hz_le - · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.ghCost] at hmem ⊢ + · dsimp only [simFilterHandler, simFilterDispatch, simHashH, ROQuery.ghCost, ROQuery.dispatch, + filterDecaps] at hmem ⊢ rw [simHashHRaw_gCache_eq S.sampleKey (true, m, c) st (res, st') hmem] exact Nat.le_add_right _ 1 | decaps c => - dsimp only [simFilterHandler, simFilterDispatch, ROQuery.ghCost] at hmem ⊢ + dsimp only [simFilterHandler, simFilterDispatch, ROQuery.ghCost, ROQuery.dispatch, + filterDecaps] at hmem ⊢ by_cases hc : c = cStar · simp only [hc, ↓reduceIte] at hmem change (res, st') ∈ (ProbComp.eval (pure (none, st))).support at hmem @@ -875,12 +882,12 @@ theorem simFilterHandler_gCache_length_le (S : Scheme PK SK M R CT K) simp · simp only [hc, ↓reduceIte] at hmem cases hd : d st.gCache c - · simp only [hd, Bool.cond_false] at hmem + · simp only [hd, filterDecaps, Bool.cond_false] at hmem change (res, st') ∈ (ProbComp.eval (pure (none, st))).support at hmem simp only [ProbComp.eval_pure, PMF.mem_support_pure_iff, Prod.mk.injEq] at hmem rcases hmem with ⟨-, rfl⟩ simp - · simp only [hd, Bool.cond_true] at hmem + · simp only [hd, filterDecaps, Bool.cond_true] at hmem change (res, st') ∈ (ProbComp.eval (simHashHq S.sampleKey c st >>= fun a => pure (some a.fst, a.snd))).support at hmem rw [ProbComp.eval_bind, PMF.mem_support_bind_iff] at hmem diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean index 391c454e8d..3f81381b2a 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean @@ -275,7 +275,7 @@ theorem eval_simBotGame_probeDecapsAdv (P : ExplicitCoinPKE PK SK M R CT) (m₀ (S.pke.encDet pk mStar rStar) z hz rw [← PMF.bind_const (ProbComp.eval S.sampleKey) (PMF.pure false)] congr 1 with kRand : 1 - dsimp only [Scheme.simFilterHandler, Scheme.simFilterDispatch] + dsimp only [Scheme.simFilterHandler, Scheme.simFilterDispatch, ROQuery.dispatch] by_cases hm : mStar = m₀ · have hc : (none : Option CT) = S.pke.encDet pk mStar rStar := by simp [S, unspreadScheme, unspreadPointPKE, hm] @@ -284,7 +284,7 @@ theorem eval_simBotGame_probeDecapsAdv (P : ExplicitCoinPKE PK SK M R CT) (m₀ simp [S, unspreadScheme, unspreadPointPKE, hm] have hcheck : Scheme.simBotCheck S.pke pk z.2.gCache none = false := by simp [Scheme.simBotCheck, hg, S, unspreadScheme, unspreadPointPKE, hm] - simp only [StateT.run, hc, ↓reduceIte, hcheck, Bool.cond_false] + simp only [StateT.run, hc, ↓reduceIte, filterDecaps, hcheck, Bool.cond_false] change (((pure (none, z.2) : ProbComp (Option K × SimState M R (Option CT) K)).eval.bind fun a => PMF.pure (Option.isSome a.1, a.2)).bind fun a => PMF.pure a.1) = PMF.pure false simp @@ -312,7 +312,7 @@ theorem eval_roIndCcaBotGame_probeDecapsAdv (P : ExplicitCoinPKE PK SK M R CT) ( congr 1 with kReal : 1 rw [← PMF.bind_const (ProbComp.eval S.sampleKey) (PMF.pure (decide (mStar ≠ m₀)))] congr 1 with kRand : 1 - dsimp only [Scheme.roBotHandler] + dsimp only [Scheme.roBotHandler, ROQuery.dispatch] by_cases hm : mStar = m₀ · have hc : (none : Option CT) = S.pke.encDet pk mStar rStar := by simp [S, unspreadScheme, unspreadPointPKE, hm] diff --git a/CslibFrontierTests/Crypto/FujisakiOkamoto.lean b/CslibFrontierTests/Crypto/FujisakiOkamoto.lean index 0a6886c1df..8724769f84 100644 --- a/CslibFrontierTests/Crypto/FujisakiOkamoto.lean +++ b/CslibFrontierTests/Crypto/FujisakiOkamoto.lean @@ -243,7 +243,7 @@ theorem identityScheme_simHitProb_diag (m : Fin 4) : Scheme.simHitProb, Scheme.romCpaReduction, Scheme.romFilterCpaReduction, queryCiphertextGAdv, identityScheme, identityPKE, ExplicitCoinPKE.indCpaGame, ExplicitCoinPKE.encrypt, Scheme.simFilterHandler, Scheme.simFilterDispatch, simHashG, OracleComp.interpret_bind, - OracleComp.interpret_query, OracleComp.interpret_pure] + OracleComp.interpret_query, OracleComp.interpret_pure, ROQuery.dispatch] simp [-_root_.PMF.bind_apply, StateT.run, RandomOracle.zoomStateT, memoize_of_none _ m [] rfl] @@ -255,7 +255,7 @@ theorem identityScheme_simHitProb_zero_one : Scheme.simHitProb, Scheme.romCpaReduction, Scheme.romFilterCpaReduction, queryCiphertextGAdv, identityScheme, identityPKE, ExplicitCoinPKE.indCpaGame, ExplicitCoinPKE.encrypt, Scheme.simFilterHandler, Scheme.simFilterDispatch, simHashG, OracleComp.interpret_bind, - OracleComp.interpret_query, OracleComp.interpret_pure] + OracleComp.interpret_query, OracleComp.interpret_pure, ROQuery.dispatch] simp [-_root_.PMF.bind_apply, StateT.run, RandomOracle.zoomStateT, memoize_of_none _ (0 : Fin 4) [] rfl] @@ -296,7 +296,8 @@ theorem identityUnifKeyScheme_indCcaRomAdvantage_eq_half : return decide (kReal = if b then kRand else kReal)) := by simp [-bind_pure_comp, Scheme.roIndCcaGame, Scheme.keyGenNotBot, identityUnifKeyScheme, identityPKE, checkKeyHAdv, Scheme.roHandler, roHashH, - OracleComp.interpret_bind, OracleComp.interpret_query, OracleComp.interpret_pure] + OracleComp.interpret_bind, OracleComp.interpret_query, OracleComp.interpret_pure, + ROQuery.dispatch] simp [StateT.run, RandomOracle.zoomStateT, memoize_of_some _ (false, (0 : Fin 4), (0 : Fin 4)) [((false, 0, 0), _)] _ rfl] rw [Scheme.indCcaRomAdvantage, hgame false, hgame true, Game.coe_advantage, diff --git a/CslibFrontierTests/Crypto/FujisakiOkamotoBot.lean b/CslibFrontierTests/Crypto/FujisakiOkamotoBot.lean index 624ea95be6..d5333115a9 100644 --- a/CslibFrontierTests/Crypto/FujisakiOkamotoBot.lean +++ b/CslibFrontierTests/Crypto/FujisakiOkamotoBot.lean @@ -47,7 +47,8 @@ theorem romBotCpaReduction_probeZeroKeyAdv_eq (m₀ m₁ : Fin 16) : pairCoinScheme.romCpaReduction probeZeroKeyAdv m₀ m₁ := by funext pk cStar simp [Scheme.romBotCpaReduction, Scheme.romFilterCpaReduction, Scheme.romCpaReduction, - Scheme.simFilterHandler, Scheme.simFilterDispatch, probeZeroKeyAdv, OracleComp.interpret_query] + Scheme.simFilterHandler, Scheme.simFilterDispatch, probeZeroKeyAdv, OracleComp.interpret_query, + ROQuery.dispatch] /-- The two-message IND-CPA advantage of `romBotCpaReduction probeZeroKeyAdv` on `pairCoinPKE` is `0` for every pair `(m₀, m₁)`. -/ diff --git a/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean b/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean index 41336fc1ec..5570dff443 100644 --- a/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean +++ b/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean @@ -126,7 +126,7 @@ theorem eval_romCpaReduction_probeZeroKeyAdv (m₀ m₁ : Fin 16) (pk : Unit) Scheme.simFilterHandler, Scheme.simFilterDispatch, Scheme.simHashH, OracleComp.interpret_bind, OracleComp.interpret_query, OracleComp.interpret_pure, StateT.run_bind, StateT.run_pure, ProbComp.eval_bind, - ProbComp.eval_pure] + ProbComp.eval_pure, ROQuery.dispatch] refine (PMF.bind_congr_on_support _ _ (fun _ => PMF.pure (decide (m₁ = 0))) fun kStar _ => ?_).trans (PMF.bind_const _ _) simp only [PMF.bind_bind] diff --git a/manual/programs-crypto-and-probability/security-games-and-primitives.md b/manual/programs-crypto-and-probability/security-games-and-primitives.md index 5b699349b7..8cee33b248 100644 --- a/manual/programs-crypto-and-probability/security-games-and-primitives.md +++ b/manual/programs-crypto-and-probability/security-games-and-primitives.md @@ -608,7 +608,9 @@ untagged `H(s, c)`. For Random Oracle Model (ROM) IND-CCA games and the keyless on `FO-notbot`, import [`FujisakiOkamoto/Security.lean`](../../CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean). It defines the ROM query family `ROQuery M CT` (`G`, `H`, and punctured decapsulation rejecting -`c = cStar` with `none`), pathwise `(qG, qH)` query budgets `ROQueryBounds` via +`c = cStar` with `none`) and its dispatcher `ROQuery.dispatch`, on which every lazy, eager and +simulated FO handler is built from its own `G`, `H` and decapsulation implementations (build new +hybrid handlers the same way), pathwise `(qG, qH)` query budgets `ROQueryBounds` via `OracleComp.IsCostBounded`, the lazy-cached executable game `Scheme.roIndCcaGame` (`Scheme.indCcaRomAdvantage`) and eager random-oracle game `Scheme.eagerIndCcaGame` with exact eager/lazy equivalence (`Scheme.eval_roIndCcaGame_eq_eagerIndCcaGame`, From ce200e63bcfced42141c2bc0852aee6853faa4d7 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Fri, 13 Nov 2026 01:31:10 +0000 Subject: [PATCH 2/7] =?UTF-8?q?refactor(Crypto/KEM):=20one=20Fujisaki?= =?UTF-8?q?=E2=80=93Okamoto=20decapsulation=20with=20oracle=20access?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the T check `decTM` and both decapsulations `Scheme.decapsBotM` / `Scheme.decapsNotBotM` with oracle access to `G` and `H` in any monad. `decT` is `decTM` in `Id` (`decT_eq` gives the old match form), and with pure oracles the decapsulations are `decapsBot` / `decapsNotBot` (`decTM_pure`, `decapsBotM_pure`, `decapsNotBotM_pure`). The lazy handlers `roHandler` / `roBotHandler` and the table handlers `roAnswerHandler`, `answerBotROHandler` and `h1Handler` now run them against their own oracles instead of restating Dec, G(m'), the re-encryption check and the H call. Each table handler's proofs go through one private characterization lemma. Oracle effects are unchanged; a new test checks them against a query-recording `G`. Also drops redundant `filterDecaps` / `ROQuery.dispatch` simp arguments (review nits) and adds a manual sentence. --- .../Crypto/Protocols/KEM/FujisakiOkamoto.lean | 141 +++++++++++++++++- .../Protocols/KEM/FujisakiOkamoto/Bot.lean | 68 +++++---- .../KEM/FujisakiOkamoto/NotBot/Challenge.lean | 2 +- .../FujisakiOkamoto/NotBot/Correctness.lean | 5 +- .../KEM/FujisakiOkamoto/NotBot/Fallback.lean | 2 +- .../FujisakiOkamoto/NotBot/Pushforward.lean | 2 +- .../KEM/FujisakiOkamoto/Security.lean | 130 +++++++++------- .../KEM/FujisakiOkamoto/SpreadNecessity.lean | 2 +- CslibFrontier/Crypto/Protocols/KEM/MLKEM.lean | 4 +- .../Crypto/FujisakiOkamoto.lean | 22 ++- .../security-games-and-primitives.md | 7 +- 11 files changed, 283 insertions(+), 102 deletions(-) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean index 3dbad0a462..7eb3bd61f1 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean @@ -118,6 +118,12 @@ to recompute `Enc(pk, m'; G(m'))` during decapsulation. bundle (`FO^⊥` via `Scheme.encapsBot` / `Scheme.decapsBot` (§3.2.1, Fig. 9) and domain-separated `FO-notbot` via `Scheme.keyGenNotBot` / `Scheme.encapsNotBot` / `Scheme.decapsNotBot` (§3.2.2, Fig. 12)). +* `Cslib.Crypto.Protocols.FujisakiOkamoto.decTM`, + `Cslib.Crypto.Protocols.FujisakiOkamoto.Scheme.decapsBotM`, + `Cslib.Crypto.Protocols.FujisakiOkamoto.Scheme.decapsNotBotM`: the `T` check and both + decapsulation algorithms with oracle access to `G` and `H` in any monad, run by the random-oracle + games; `decT` is `decTM` in the identity monad, and with pure oracles the decapsulations are + `decapsBot` and `decapsNotBot` (`Scheme.decapsBotM_pure`, `Scheme.decapsNotBotM_pure`). ## Main results @@ -313,26 +319,55 @@ def encT (P : ExplicitCoinPKE PK SK M R CT) (G : M → R) (pk : PK) (m : M) : CT /-- Derandomized decryption `Dec_1(pk, sk, c)` of `T[PKE, G]` with mandatory re-encryption check ([HofheinzHoevelmannsKiltz2017], ePrint 2017/604 (2 Nov 2021), §3.1, Fig. 5): runs -`m' ← Dec(sk, c)` and accepts `m'` only when `Enc(pk, m'; G(m')) = c`. -/ +`m' ← Dec(sk, c)` and accepts `m'` only when `Enc(pk, m'; G(m')) = c`, with oracle access to +`G : M → m R` in a monad `m`. `G` is queried only when `Dec` succeeds. The random-oracle games run +it with lazily sampled `G`; `decT` is the case of a fixed function `G`. -/ +def decTM [DecidableEq CT] {m : Type → Type} [Monad m] (P : ExplicitCoinPKE PK SK M R CT) + (G : M → m R) (pk : PK) (sk : SK) (c : CT) : m (Option M) := + match P.dec sk c with + | none => return none + | some m' => do + let r' ← G m' + return if P.encDet pk m' r' = c then some m' else none + +/-- Derandomized decryption `Dec_1(pk, sk, c)` of `T[PKE, G]` with mandatory re-encryption check +([HofheinzHoevelmannsKiltz2017], ePrint 2017/604 (2 Nov 2021), §3.1, Fig. 5) for a fixed +function `G`: `decTM` in the identity monad. -/ def decT [DecidableEq CT] (P : ExplicitCoinPKE PK SK M R CT) (G : M → R) (pk : PK) (sk : SK) (c : CT) : Option M := - match P.dec sk c with - | none => none - | some m' => if encT P G pk m' = c then some m' else none + decTM (m := Id) P G pk sk c + +/-- `decT` runs `m' ← Dec(sk, c)` and accepts `m'` only when `Enc(pk, m'; G(m')) = c`. -/ +theorem decT_eq [DecidableEq CT] (P : ExplicitCoinPKE PK SK M R CT) (G : M → R) + (pk : PK) (sk : SK) (c : CT) : + decT P G pk sk c = + match P.dec sk c with + | none => none + | some m' => if encT P G pk m' = c then some m' else none := by + unfold decT decTM encT + cases P.dec sk c <;> rfl + +/-- With a pure oracle, `decTM` is `decT`. -/ +@[simp] theorem decTM_pure [DecidableEq CT] {m : Type → Type} [Monad m] [LawfulMonad m] + (P : ExplicitCoinPKE PK SK M R CT) (G : M → R) (pk : PK) (sk : SK) (c : CT) : + decTM (m := m) P (pure ∘ G) pk sk c = pure (decT P G pk sk c) := by + rw [decT_eq] + unfold decTM encT + cases P.dec sk c <;> simp /-- Whenever `Dec(sk, Enc_1(pk, m)) = some m`, the re-encryption check succeeds and `decT` recovers `some m`. -/ theorem decT_encT_eq_some [DecidableEq CT] (P : ExplicitCoinPKE PK SK M R CT) (G : M → R) (pk : PK) (sk : SK) (m : M) (hdec : P.dec sk (encT P G pk m) = some m) : decT P G pk sk (encT P G pk m) = some m := by - simp [decT, hdec] + simp [decT_eq, hdec] /-- **Rigidity of `T`**: any plaintext `m'` accepted by `decT` re-encrypts strictly to `c`, and satisfies `P.dec sk c = some m'`. -/ theorem encT_of_decT_eq_some [DecidableEq CT] {P : ExplicitCoinPKE PK SK M R CT} {G : M → R} {pk : PK} {sk : SK} {c : CT} {m' : M} (h : decT P G pk sk c = some m') : encT P G pk m' = c := by - unfold decT at h + rw [decT_eq] at h cases hdec : P.dec sk c with | none => simp [hdec] at h | some m₀ => @@ -344,7 +379,7 @@ theorem encT_of_decT_eq_some [DecidableEq CT] {P : ExplicitCoinPKE PK SK M R CT} theorem dec_of_decT_eq_some [DecidableEq CT] {P : ExplicitCoinPKE PK SK M R CT} {G : M → R} {pk : PK} {sk : SK} {c : CT} {m' : M} (h : decT P G pk sk c = some m') : P.dec sk c = some m' := by - unfold decT at h + rw [decT_eq] at h cases hdec : P.dec sk c with | none => simp [hdec] at h | some m₀ => @@ -358,7 +393,7 @@ theorem decT_eq_none_of_reencrypt_ne [DecidableEq CT] (P : ExplicitCoinPKE PK SK (G : M → R) (pk : PK) (sk : SK) (c : CT) (hbad : ∀ m', P.dec sk c = some m' → encT P G pk m' ≠ c) : decT P G pk sk c = none := by - unfold decT + rw [decT_eq] cases hdec : P.dec sk c with | none => rfl | some m' => exact ite_eq_right (hbad m' hdec) @@ -489,6 +524,96 @@ theorem decapsNotBot_of_reencrypt_ne [DecidableEq CT] (S : Scheme PK SK M R CT K end FixedOracles +/-! ### Decapsulation with oracle access + +The random-oracle games answer `G` and `H` lazily, so their decapsulation oracles run the +decapsulation algorithms with `G : M → m R` and `H : Bool × M × CT → m K` given as oracles in a +monad `m`. Both run the `T` check `decTM` and add only their rejection policy and `H` query. With +pure oracles they are `decapsBot` and `decapsNotBot` (`decapsBotM_pure`, `decapsNotBotM_pure`); +`decapsBotM_eq` and `decapsNotBotM_eq` unfold them into explicit oracle calls. -/ + +section OracleAccess + +variable {PK SK M R CT K : Type} [DecidableEq CT] {m : Type → Type} [Monad m] + +/-- Explicit-rejection decapsulation `Decaps^⊥(pk, sk, c)` of `FO^⊥` with oracle access to `G` and +`H`: returns `some (H(false, m', c))` when `decTM` accepts `m'`, and `none` otherwise. -/ +def decapsBotM (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) (pk : PK) + (sk : SK) (c : CT) : m (Option K) := do + match ← decTM S.pke G pk sk c with + | some m' => some <$> H (false, m', c) + | none => return none + +/-- Implicit-rejection decapsulation `Decaps-notbot(dk, c)` of `FO-notbot` with oracle access to +`G` and `H`: returns `H(false, m', c)` when `decTM` accepts `m'`, and the fallback key +`H(true, dk.fallback, c)` otherwise. -/ +def decapsNotBotM (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) + (dk : ImplicitSK PK SK M) (c : CT) : m K := do + match ← decTM S.pke G dk.pk dk.sk c with + | some m' => H (false, m', c) + | none => H (true, dk.fallback, c) + +variable [LawfulMonad m] + +/-- With pure oracles, `decapsBotM` is the fixed-function decapsulation `decapsBot`. -/ +@[simp] theorem decapsBotM_pure (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) + (pk : PK) (sk : SK) (c : CT) : + S.decapsBotM (m := m) (pure ∘ G) (pure ∘ H) pk sk c = pure (S.decapsBot G H pk sk c) := by + unfold decapsBotM decapsBot + rw [decTM_pure, pure_bind] + cases decT S.pke G pk sk c <;> simp + +/-- With pure oracles, `decapsNotBotM` is the fixed-function decapsulation `decapsNotBot`. -/ +@[simp] theorem decapsNotBotM_pure (S : Scheme PK SK M R CT K) (G : M → R) + (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (c : CT) : + S.decapsNotBotM (m := m) (pure ∘ G) (pure ∘ H) dk c = pure (S.decapsNotBot G H dk c) := by + unfold decapsNotBotM decapsNotBot + rw [decTM_pure, pure_bind] + cases decT S.pke G dk.pk dk.sk c <;> rfl + +/-- `decapsBotM` as explicit oracle calls: no query when `Dec` fails; otherwise `G(m')`, then +`H(false, m', c)` exactly when the re-encryption check passes. -/ +theorem decapsBotM_eq (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) + (pk : PK) (sk : SK) (c : CT) : + S.decapsBotM G H pk sk c = + (match S.pke.dec sk c with + | none => return none + | some m' => do + let r' ← G m' + if S.pke.encDet pk m' r' = c then do + let k ← H (false, m', c) + return some k + else + return none) := by + unfold decapsBotM decTM + cases S.pke.dec sk c with + | none => simp + | some m' => + simp only [bind_assoc, pure_bind] + refine bind_congr fun r' => ?_ + split_ifs <;> simp + +/-- `decapsNotBotM` as explicit oracle calls: `H(true, dk.fallback, c)` when `Dec` fails; +otherwise `G(m')`, then `H(false, m', c)` or `H(true, dk.fallback, c)` by the re-encryption +check. -/ +theorem decapsNotBotM_eq (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) + (dk : ImplicitSK PK SK M) (c : CT) : + S.decapsNotBotM G H dk c = + (match S.pke.dec dk.sk c with + | none => H (true, dk.fallback, c) + | some m' => do + let r' ← G m' + if S.pke.encDet dk.pk m' r' = c then H (false, m', c) else H (true, dk.fallback, c)) := by + unfold decapsNotBotM decTM + cases S.pke.dec dk.sk c with + | none => simp + | some m' => + simp only [bind_assoc, pure_bind] + refine bind_congr fun r' => ?_ + split_ifs <;> rfl + +end OracleAccess + end Scheme end FujisakiOkamoto diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean index bd65815016..beb51e102e 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean @@ -173,23 +173,12 @@ variable [DecidableEq M] [DecidableEq CT] /-- Stateful oracle handler for the executable ROM IND-CCA game of `FO^⊥` ([HofheinzHoevelmannsKiltz2017], §3.2.1, Fig. 9). Queries to the challenge ciphertext `cStar` are -rejected with `none`; other decapsulation queries decrypt `c` via `S.pke.dec sk c`, re-encrypt -under the lazy random oracle `G`, and return `some (H(false, m', c))` on re-encryption match and -`none` on explicit rejection. -/ +rejected with `none`; other decapsulation queries run the scheme's decapsulation `decapsBotM` +against the lazy random oracles `G` and `H`. -/ def roBotHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : StateT (ROCache M R CT K) ProbComp (ROResp R K q) := ROQuery.dispatch (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) - (fun c => - match S.pke.dec sk c with - | none => return none - | some m' => do - let r' ← roHashG S.pke.sampleCoins m' - if S.pke.encDet pk m' r' = c then do - let k ← roHashH S.sampleKey (false, m', c) - return some k - else - return none) - cStar q + (S.decapsBotM (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) pk sk) cStar q /-- The executable ROM IND-CCA security game for `FO^⊥ = U^⊥ ∘ T` with bit `b : Bool`. When `b = false`, the adversary receives the real encapsulated key `kReal = H(false, m*, c*)`; when @@ -244,15 +233,22 @@ private abbrev ROTables (M R CT K : Type) : Type := private def roTablesOf (st : ROCache M R CT K) : ROTables M R CT K := (RandomOracle.ofList st.gCache, RandomOracle.ofList st.hCache) -/-- `roBotHandler` on represented `(tG, tH)` tables via `RandomOracle.answer`. -/ +/-- `roBotHandler` on represented `(tG, tH)` tables via `RandomOracle.answer`: decapsulation +queries `c ≠ cStar` run `decapsBotM` against these table oracles. -/ private noncomputable def answerBotROHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : StateT (ROTables M R CT K) PMF (ROResp R K q) := - ROQuery.dispatch - (fun m t => (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map - fun z => (z.1, (z.2, t.2))) - (fun x t => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x t.2).map - fun z => (z.1, (t.1, z.2))) - (fun c t => + let G : M → StateT (ROTables M R CT K) PMF R := fun m t => + (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map + fun z => (z.1, (z.2, t.2)) + let H : Bool × M × CT → StateT (ROTables M R CT K) PMF K := fun x t => + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x t.2).map + fun z => (z.1, (t.1, z.2)) + ROQuery.dispatch G H (S.decapsBotM G H pk sk) cStar q + +/-- A decapsulation query `c ≠ cStar` to `answerBotROHandler` as explicit table operations. -/ +private theorem answerBotROHandler_decaps (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) + {cStar c : CT} (hc : c ≠ cStar) (t : ROTables M R CT K) : + S.answerBotROHandler pk sk cStar (.decaps c) t = match S.pke.dec sk c with | none => PMF.pure (none, t) | some m' => @@ -261,8 +257,19 @@ private noncomputable def answerBotROHandler (S : Scheme PK SK M R CT K) (pk : P (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m', c) t.2).map fun z => (some z.1, (y.2, z.2)) else - PMF.pure (none, (y.2, t.2))) - cStar q + PMF.pure (none, (y.2, t.2)) := by + simp only [answerBotROHandler, ROQuery.dispatch, hc, ↓reduceIte, decapsBotM_eq] + cases S.pke.dec sk c with + | none => rfl + | some m' => + dsimp only + simp only [bind, StateT.bind, PMF.bind_map] + congr 1 with a : 1 + dsimp only [Function.comp_apply] + split_ifs + · simp only [StateT.bind, Probability.PMF.monad_bind_eq_bind, PMF.bind_map] + rfl + · rfl private theorem roBotHandler_map_roTablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) (st : ROCache M R CT K) : @@ -281,7 +288,8 @@ private theorem roBotHandler_map_roTablesOf (S : Scheme PK SK M R CT K) (pk : PK by_cases hc : c = cStar · simp only [roBotHandler, answerBotROHandler, hc, ↓reduceIte, ROQuery.dispatch] exact PMF.pure_map _ _ - · simp only [roBotHandler, answerBotROHandler, hc, ↓reduceIte, ROQuery.dispatch] + · rw [answerBotROHandler_decaps S pk sk hc] + simp only [roBotHandler, hc, ↓reduceIte, ROQuery.dispatch, decapsBotM_eq] cases hdec : S.pke.dec sk c with | none => exact PMF.pure_map _ _ @@ -348,11 +356,12 @@ private theorem answerBotROHandler_sim [Fintype M] [Finite R] [Fintype CT] [Fini (fun x => (S.completeROTables x.2).map (x.1, ·)) = (S.completeROTables (tG, tH)).bind fun T => (S.eagerBotHandler T.1 T.2 pk sk cStar (.decaps c)).map (·, T) - dsimp only [answerBotROHandler, completeROTables, eagerBotHandler, decapsBot, decT, encT, - ROQuery.dispatch] by_cases hc : c = cStar - · simp only [hc, ↓reduceIte, pure, StateT.pure, pmf_norm, PMF.map] - · simp only [hc, ↓reduceIte] + · dsimp only [answerBotROHandler, completeROTables, eagerBotHandler, ROQuery.dispatch] + simp only [hc, ↓reduceIte, pure, StateT.pure, pmf_norm, PMF.map] + · rw [answerBotROHandler_decaps S pk sk hc] + dsimp only [completeROTables, eagerBotHandler, decapsBot, ROQuery.dispatch] + simp only [hc, ↓reduceIte, decT_eq, encT] cases hdec : S.pke.dec sk c with | none => simp only [Option.map_none, pmf_norm, PMF.map] @@ -692,7 +701,8 @@ private theorem answerCachedBotHandler_closeTo_sim (S : Scheme PK SK M R CT K) { ((S.completeTables (tG, tH, tQ)).bind fun T => (S.eagerCoupledBotHandler T.1 T.2.2 T.2.1 pk sk cStar (.decaps c)).map (·, T)) (ENNReal.ofReal ((2 : ℝ) ^ (-γ))) - dsimp only [answerCachedBotHandler, eagerCoupledBotHandler, decT, ROQuery.dispatch] + dsimp only [answerCachedBotHandler, eagerCoupledBotHandler, ROQuery.dispatch] + simp only [decT_eq] have hnone_sim : CloseTo ((PMF.pure (none, (tG, tH, tQ))).bind fun x : Option K × Tables M R CT K => (S.completeTables x.2).map (x.1, ·)) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean index 2d2485f6a9..bf843f4cca 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean @@ -361,7 +361,7 @@ private theorem simHandler_reEnc_le (S : Scheme PK SK M R CT K) (d : List (M × simp only [ROQuery.gCost, ROQuery.hCost, Nat.cast_zero, zero_mul, add_zero] refine (toOuterMeasure_gCache_eq_zero _ st (fun g => ∃ p ∈ g, S.pke.encDet pk p.1 p.2 = cStar) hst fun y hy => ?_).le - dsimp only [simFilterHandler, simFilterDispatch, ROQuery.dispatch, filterDecaps] at hy + dsimp only [simFilterHandler, simFilterDispatch, ROQuery.dispatch] at hy exact gCache_eq_of_mem_support_filterDecaps _ _ _ _ _ _ hy /-- **Bad-event split**: from the empty state, the simulator records `mStar` in diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean index 843128c3fc..7b8ef649ad 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean @@ -245,7 +245,7 @@ private theorem testHandler_decaps_gCache_eq (S : Scheme PK SK M R CT K) (x : Option K × SimState M R CT K) (hx : x ∈ (ProbComp.eval (S.testHandler d cStar t (.decaps c) st)).support) : x.2.gCache = st.gCache := by - dsimp only [testHandler, simFilterDispatch, ROQuery.dispatch, filterDecaps] at hx + dsimp only [testHandler, simFilterDispatch, ROQuery.dispatch] at hx exact gCache_eq_of_mem_support_filterDecaps _ _ _ _ _ _ hx private theorem testHandler_gCache_subset (S : Scheme PK SK M R CT K) @@ -303,8 +303,7 @@ private theorem identicalUntilBad_testHandler (S : Scheme PK SK M R CT K) (pk : · exact eval_testHashH_apply_congr S pk sk m c _ _ (ht m c) s (a, s') (of_not_not hs') · rfl · by_cases hc : c = cStar <;> - simp [testHandler, simFilterDispatch, hc, hd s c (of_not_not hs), ROQuery.dispatch, - filterDecaps] + simp [testHandler, simFilterDispatch, hc, hd s c (of_not_not hs), filterDecaps] absorb_first q s hs x hx := fun hg => hs (hg.of_subset (testHandler_gCache_subset S d₁ cStar t₁ q s x hx)) absorb_second q s hs x hx := fun hg => diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean index eeef86e336..14939140d5 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean @@ -148,7 +148,7 @@ private theorem eagerHandler_fbTable (S : Scheme PK SK M R CT K) (G : M → R) S.eagerFbHandler G H Hfb ⟨sk, pk, s⟩ cStar q := by cases q with | G m => rfl - | H x => simp [eagerHandler, eagerFbHandler, fbTable, hq x rfl, ROQuery.dispatch] + | H x => simp [eagerHandler, eagerFbHandler, fbTable, hq x rfl] | decaps c => simp only [eagerHandler, eagerFbHandler, decapsNotBot, ROQuery.dispatch] cases decT S.pke G pk sk c <;> simp [fbTable] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean index cbfa614f30..1df4fcdbf2 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean @@ -97,7 +97,7 @@ private theorem readIndex_injective (S : Scheme PK SK M R CT K) (G : M → R) (p Function.Injective (S.readIndex G pk sk) := by rintro (⟨_ | _, m, c⟩ | c) (⟨_ | _, m', c'⟩ | c') h <;> simp only [readIndex] at h <;> (try split_ifs at h) <;> - simp_all [decT, encT] + simp_all [decT_eq, encT] /-- Reading the independent tables `(Hq, Hraw, Hfb')` through `readIndex` gives independent tables `(H, Hfb)`. -/ diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean index 4a65fdbe63..de675e9f44 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean @@ -286,25 +286,12 @@ namespace Scheme variable [DecidableEq M] [DecidableEq CT] /-- Stateful oracle handler for the real ROM IND-CCA game of `FO-notbot`. Queries to the -challenge ciphertext `cStar` are rejected with `none`; other decapsulation queries -re-encrypt under the lazy random oracle `G` and derive the key via the lazy random oracle -`H`, falling back to `H(true, dk.fallback, c)` on implicit rejection. -/ +challenge ciphertext `cStar` are rejected with `none`; other decapsulation queries run the scheme's +decapsulation `decapsNotBotM` against the lazy random oracles `G` and `H`. -/ def roHandler (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : StateT (ROCache M R CT K) ProbComp (ROResp R K q) := ROQuery.dispatch (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) - (fun c => - match S.pke.dec dk.sk c with - | none => do - let k ← roHashH S.sampleKey (true, dk.fallback, c) - return some k - | some m' => do - let r' ← roHashG S.pke.sampleCoins m' - if S.pke.encDet dk.pk m' r' = c then do - let k ← roHashH S.sampleKey (false, m', c) - return some k - else do - let k ← roHashH S.sampleKey (true, dk.fallback, c) - return some k) + (fun c => some <$> S.decapsNotBotM (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) dk c) cStar q /-- The executable ROM IND-CCA security game for `FO-notbot` with bit `b : Bool` (standard KEM @@ -437,37 +424,53 @@ def romCpaReduction (S : Scheme PK SK M R CT K) /-! ### Section 2: Connecting `memoize` to `RandomOracle.answer` and Eager/Lazy Equivalence -/ /-- Hybrid 1 handler: `G : M → R` is a complete table while `H : Bool × M × CT → K` is answered -lazily via `RandomOracle.answer`. Decapsulation uses `decT S.pke G dk.pk dk.sk c`. -/ +lazily via `RandomOracle.answer`. Decapsulation queries `c ≠ cStar` run `decapsNotBotM` against +these oracles. -/ noncomputable def h1Handler (S : Scheme PK SK M R CT K) (G : M → R) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : StateT (RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := - ROQuery.dispatch (fun m tH => PMF.pure (G m, tH)) - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey)) - (fun c tH => + let H := RandomOracle.answer (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey) + ROQuery.dispatch (pure ∘ G) H (fun c => some <$> S.decapsNotBotM (pure ∘ G) H dk c) cStar q + +/-- A decapsulation query `c ≠ cStar` to `h1Handler`: the `T` check runs on the table `G`, and +only the key derivation touches the `H` table. -/ +private theorem h1Handler_decaps (S : Scheme PK SK M R CT K) (G : M → R) + (dk : ImplicitSK PK SK M) {cStar c : CT} (hc : c ≠ cStar) + (tH : RandomOracle.Cache (fun _ : Bool × M × CT => K)) : + S.h1Handler G dk cStar (.decaps c) tH = match decT S.pke G dk.pk dk.sk c with | some m' => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m', c) tH).map (fun (k, tH') => (some k, tH')) | none => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, tH'))) - cStar q + (fun (k, tH') => (some k, tH')) := by + simp only [h1Handler, ROQuery.dispatch, hc, ↓reduceIte, decapsNotBotM, decTM_pure, pure_bind] + cases decT S.pke G dk.pk dk.sk c <;> rfl /-- Intermediate handler answering `G` and `H` via `RandomOracle.answer` on a pair of -`RandomOracle.Cache` tables `(tG, tH)`. -/ +`RandomOracle.Cache` tables `(tG, tH)`. Decapsulation queries `c ≠ cStar` run `decapsNotBotM` +against these table oracles. -/ noncomputable def roAnswerHandler (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : StateT (RandomOracle.Cache (fun _ : M => R) × RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := - ROQuery.dispatch - (fun m (tG, tH) => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).map - (fun (r, tG') => (r, (tG', tH)))) - (fun x (tG, tH) => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x tH).map - (fun (k, tH') => (k, (tG, tH')))) - (fun c (tG, tH) => + let G : M → StateT (RandomOracle.Cache (fun _ : M => R) × + RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF R := fun m (tG, tH) => + (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).map + (fun (r, tG') => (r, (tG', tH))) + let H : Bool × M × CT → StateT (RandomOracle.Cache (fun _ : M => R) × + RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF K := fun x (tG, tH) => + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x tH).map + (fun (k, tH') => (k, (tG, tH'))) + ROQuery.dispatch G H (fun c => some <$> S.decapsNotBotM G H dk c) cStar q + +/-- A decapsulation query `c ≠ cStar` to `roAnswerHandler` as explicit table operations. -/ +private theorem roAnswerHandler_decaps (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) + {cStar c : CT} (hc : c ≠ cStar) (tG : RandomOracle.Cache (fun _ : M => R)) + (tH : RandomOracle.Cache (fun _ : Bool × M × CT => K)) : + S.roAnswerHandler dk cStar (.decaps c) (tG, tH) = match S.pke.dec dk.sk c with | none => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map @@ -479,14 +482,27 @@ noncomputable def roAnswerHandler (S : Scheme PK SK M R CT K) (fun (k, tH') => (some k, (tG', tH'))) else (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, (tG', tH')))) - cStar q - -/-- Evaluating `(do let k ← roHashH sampleKey x; return some k) st` and mapping `ROCache` via + (fun (k, tH') => (some k, (tG', tH'))) := by + simp only [roAnswerHandler, ROQuery.dispatch, hc, ↓reduceIte, decapsNotBotM_eq] + cases S.pke.dec dk.sk c with + | none => + dsimp only + simp only [Functor.map, StateT.map, Probability.PMF.monad_bind_eq_bind, + Probability.PMF.monad_pure_eq_pure, PMF.bind_map] + rfl + | some m' => + dsimp only + simp only [Functor.map, StateT.map, bind, StateT.bind, Probability.PMF.monad_pure_eq_pure, + PMF.bind_map, PMF.bind_bind] + congr 1 with a : 1 + dsimp only [Function.comp_apply] + split_ifs <;> rw [PMF.bind_map] <;> rfl + +/-- Evaluating `(some <$> roHashH sampleKey x) st` and mapping `ROCache` via `RandomOracle.ofList`. -/ private theorem eval_roHashH_some_map_ofList (sampleKey : ProbComp K) (x : Bool × M × CT) (st : ROCache M R CT K) : - (ProbComp.eval ((do let k ← roHashH sampleKey x; return some k) st)).map + (ProbComp.eval ((some <$> roHashH sampleKey x) st)).map (fun (res, st') => (res, (RandomOracle.ofList st'.gCache, RandomOracle.ofList st'.hCache))) = (RandomOracle.answer (fun _ => ProbComp.eval sampleKey) x @@ -526,21 +542,21 @@ theorem roHandler_map_ofList (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK (RandomOracle.memoize_map_ofList dH x st.hCache) simpa only [PMF.map_comp, Function.comp_def, dH] using h | decaps c => - dsimp only [roHandler, roAnswerHandler, ROQuery.dispatch] by_cases hc : c = cStar - · simp only [hc, ↓reduceIte] + · dsimp only [roHandler, roAnswerHandler, ROQuery.dispatch] + simp only [hc, ↓reduceIte] exact PMF.pure_map _ _ - · simp only [hc, ↓reduceIte] + · rw [roAnswerHandler_decaps S dk hc] + dsimp only [roHandler, ROQuery.dispatch] + simp only [hc, ↓reduceIte, decapsNotBotM_eq] cases hdec : S.pke.dec dk.sk c with | none => exact eval_roHashH_some_map_ofList S.sampleKey (true, dk.fallback, c) st | some m' => + dsimp only + rw [map_bind] change (ProbComp.eval (roHashG S.pke.sampleCoins m' st >>= fun a => - (if S.pke.encDet dk.pk m' a.fst = c then do - let k ← roHashH S.sampleKey (false, m', c) - return some k - else do - let k ← roHashH S.sampleKey (true, dk.fallback, c) - return some k) a.snd)).map _ = _ + (some <$> if S.pke.encDet dk.pk m' a.fst = c then roHashH S.sampleKey (false, m', c) + else roHashH S.sampleKey (true, dk.fallback, c)) a.snd)).map _ = _ dsimp only [roHashG] simp only [ProbComp.eval_bind, RandomOracle.eval_zoomStateT_memoize, PMF.map_bind, PMF.bind_map, Function.comp_def] @@ -571,7 +587,8 @@ theorem roAnswerHandler_sim_h1 [Fintype M] [Finite R] simp only [Probability.PMF.monad_bind_eq_bind] cases q with | G m => - dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] + dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch, Function.comp_apply, pure, + StateT.pure] have hc := congrArg (PMF.map (fun (r, G) => (r, (G, tH)))) (RandomOracle.answer_complete dG tG m) simpa only [Probability.PMF.monad_bind_eq_bind, PMF.bind_map, PMF.map_bind, PMF.map_comp, @@ -581,16 +598,17 @@ theorem roAnswerHandler_sim_h1 [Fintype M] [Finite R] rw [PMF.bind_map, PMF.bind_comm] rfl | decaps c => - dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] by_cases hc : c = cStar - · simp [hc, pure, StateT.pure] - · simp only [hc, ↓reduceIte] + · dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] + simp [hc, pure, StateT.pure] + · rw [roAnswerHandler_decaps S dk hc] + simp only [h1Handler_decaps S _ dk hc] cases hdec : S.pke.dec dk.sk c with | none => - simp only [decT, hdec, Function.comp_def, PMF.map, PMF.bind_bind, PMF.pure_bind] + simp only [decT_eq, hdec, Function.comp_def, PMF.map, PMF.bind_bind, PMF.pure_bind] exact PMF.bind_comm _ _ _ | some m' => - simp only [decT, encT, hdec, PMF.bind_bind] + simp only [decT_eq, encT, hdec, PMF.bind_bind] trans (((RandomOracle.answer dG m' tG).bind (fun (r', tG') => (RandomOracle.complete dG tG').map (fun G => (r', G)))).bind (fun (r', G) => if S.pke.encDet dk.pk m' r' = c then @@ -716,16 +734,18 @@ theorem h1Handler_sim_eager [Fintype M] [Fintype CT] [Finite K] simp only [Probability.PMF.monad_bind_eq_bind] cases q with | G m => - simp [h1Handler, eagerHandler, ROQuery.dispatch] + simp [h1Handler, eagerHandler, pure, StateT.pure] | H x => dsimp only [h1Handler, eagerHandler, ROQuery.dispatch] simpa only [Probability.PMF.monad_bind_eq_bind, PMF.pure_bind, PMF.map, Function.comp_def, dH] using RandomOracle.answer_complete dH tH x | decaps c => - dsimp only [h1Handler, eagerHandler, decapsNotBot, ROQuery.dispatch] by_cases hc : c = cStar - · simp [hc, pure, StateT.pure] - · simp only [hc, ↓reduceIte] + · dsimp only [h1Handler, eagerHandler, ROQuery.dispatch] + simp [hc, pure, StateT.pure] + · rw [h1Handler_decaps S G dk hc] + dsimp only [eagerHandler, decapsNotBot, ROQuery.dispatch] + simp only [hc, ↓reduceIte] cases hdec : decT S.pke G dk.pk dk.sk c with | none => have hH := congrArg (PMF.map (fun (k, H) => (some k, H))) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean index 3f81381b2a..563aafd220 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean @@ -322,7 +322,7 @@ theorem eval_roIndCcaBotGame_probeDecapsAdv (P : ExplicitCoinPKE PK SK M R CT) ( have hdec : S.pke.dec sk none = some m₀ := rfl have henc (r' : R) : S.pke.encDet pk m₀ r' = none := by simp [S, unspreadScheme, unspreadPointPKE] - simp [StateT.run_bind, hc, hdec, henc, hm, pmf_norm] + simp [StateT.run_bind, hc, hdec, henc, hm, pmf_norm, Scheme.decapsBotM_eq] /-- **Necessity of the `qD · 2^{-γ}` spreadness term in `FO^⊥`**: for a uniform message sampler on a finite message space `M`, the 1-query adversary `probeDecapsAdv` (`qG = 0, qH = 0, qD = 1`) diff --git a/CslibFrontier/Crypto/Protocols/KEM/MLKEM.lean b/CslibFrontier/Crypto/Protocols/KEM/MLKEM.lean index cfc415e04b..e319a457d9 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/MLKEM.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/MLKEM.lean @@ -110,7 +110,9 @@ theorem decapsWith_eq [DecidableEq CT] (pke : ExplicitCoinPKE PK SK M R CT) (G : decapsWith pke G J dk c = if pke.encDet dk.pk m' (G (m', dk.pkHash)).2 = c then (G (m', dk.pkHash)).1 else J (dk.seed, c) := by - unfold decapsWith decT encT + unfold decapsWith + rw [decT_eq] + unfold encT rw [hdec] by_cases h : pke.encDet dk.pk m' (G (m', dk.pkHash)).2 = c <;> simp [h] diff --git a/CslibFrontierTests/Crypto/FujisakiOkamoto.lean b/CslibFrontierTests/Crypto/FujisakiOkamoto.lean index 8724769f84..3db8187d86 100644 --- a/CslibFrontierTests/Crypto/FujisakiOkamoto.lean +++ b/CslibFrontierTests/Crypto/FujisakiOkamoto.lean @@ -21,7 +21,9 @@ Exercises the HHK17 modular Fujisaki–Okamoto transforms (`T`, `FO^⊥`, `FO-no for `T`, `FO^⊥`, and `FO-notbot`, and rejection of a valid underlying PKE ciphertext encrypted under non-canonical coins (`c = (1, 6)` decrypts to `some 2` under PKE, yet `decapsBot` rejects with - `none` and `decapsNotBot` returns the domain-separated fallback key `H(true, fallback, c)`). + `none` and `decapsNotBot` returns the domain-separated fallback key `H(true, fallback, c)`), + and the same decapsulations with oracle access (`decapsBotM`, `decapsNotBotM`) against a `G` + oracle that records its queries. 2. **Positive and negative witnesses for `DeltaCorrect` (including `δ > 0` tight and non-tight bounds)**: `identityPKE` (`DeltaCorrect 0`), `alwaysZeroDecPKE` (`¬ DeltaCorrect 0`), and `noisyCoinPKE` whose decryptor fails on coin `r = 1` with probability `1 / 2`, satisfying @@ -109,6 +111,24 @@ example : maskedShiftScheme.decapsBot_eq_none_of_reencrypt_ne testG testH 3 3 (1, 6) hbad, maskedShiftScheme.decapsNotBot_of_reencrypt_ne testG testH ⟨3, 3, 7⟩ (1, 6) hbad⟩ +/-- `testG` as an oracle that records each query. -/ +def logG (m : Fin 8) : StateM (List (Fin 8)) (Fin 8) := do + modify (m :: ·) + return testG m + +/-- Decapsulation with oracle access: on the honest ciphertext `(0, 5)` and the non-canonical +`(1, 6)`, `decapsBotM` and `decapsNotBotM` query `G(2)` exactly once, and the rejected `(1, 6)` +keeps that query before returning `none` or the fallback key. With pure oracles, `decapsNotBotM` +is `decapsNotBot`. -/ +example : + (maskedShiftScheme.decapsBotM logG (pure ∘ testH) 3 3 (1, 6)).run [] = (none, [2]) ∧ + (maskedShiftScheme.decapsNotBotM logG (pure ∘ testH) ⟨3, 3, 7⟩ (1, 6)).run [] = (15, [2]) ∧ + (maskedShiftScheme.decapsNotBotM logG (pure ∘ testH) ⟨3, 3, 7⟩ + (encT maskedShiftPKE testG 3 2)).run [] = (2, [2]) ∧ + maskedShiftScheme.decapsNotBotM (m := Id) (pure ∘ testG) (pure ∘ testH) ⟨3, 3, 7⟩ (1, 6) = + maskedShiftScheme.decapsNotBot testG testH ⟨3, 3, 7⟩ (1, 6) := + ⟨rfl, rfl, rfl, maskedShiftScheme.decapsNotBotM_pure testG testH _ _⟩ + /-! ### 2. `DeltaCorrect` Witnesses (Zero-Error, Tight Non-Zero `δ > 0`, Non-Tight, and Negative) -/ /-- Insecure deterministic identity PKE on `Fin 4`: `Enc((), m; ()) = m`, `Dec((), c) = some c`. -/ diff --git a/manual/programs-crypto-and-probability/security-games-and-primitives.md b/manual/programs-crypto-and-probability/security-games-and-primitives.md index 8cee33b248..91e6e4b8df 100644 --- a/manual/programs-crypto-and-probability/security-games-and-primitives.md +++ b/manual/programs-crypto-and-probability/security-games-and-primitives.md @@ -604,7 +604,12 @@ implicit-rejection `FO-notbot` (§3.2.2, Fig. 12; `ImplicitSK`, `Scheme.keyGenNo `Scheme.encapsNotBot`, `Scheme.decapsNotBot`, `decapsNotBot_encT_eq`, `decapsNotBot_of_reencrypt_ne`) that tags `H : Bool × M × CT → K` with `false` for valid key derivations and `true` for fallback rejection keys `H(true, dk.fallback, c)` in place of HHK17's -untagged `H(s, c)`. For Random Oracle Model (ROM) IND-CCA games and the keyless IND-CPA reduction +untagged `H(s, c)`. `FujisakiOkamoto.decTM`, `Scheme.decapsBotM` and `Scheme.decapsNotBotM` are +the same algorithms with oracle access to `G` and `H` in any monad. `decT` is `decTM` in `Id`, and +with pure oracles the decapsulations are `decapsBot` and `decapsNotBot` (`Scheme.decapsBotM_pure`, +`Scheme.decapsNotBotM_pure`). The random-oracle game handlers run them against lazy or table +oracles instead of restating the re-encryption check. For Random Oracle Model (ROM) IND-CCA games +and the keyless IND-CPA reduction on `FO-notbot`, import [`FujisakiOkamoto/Security.lean`](../../CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean). It defines the ROM query family `ROQuery M CT` (`G`, `H`, and punctured decapsulation rejecting From bed174471f21272e4712c025cc28bc624fed5261 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Fri, 13 Nov 2026 01:44:40 +0000 Subject: [PATCH 3/7] =?UTF-8?q?refactor(Crypto/KEM):=20typed=20KEM=20chall?= =?UTF-8?q?enge=20record=20for=20Fujisaki=E2=80=93Okamoto=20adversaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KEM.Challenge PK CT K (pk, ct, key) in a new leaf Crypto/Computational/Encryption/KEM.lean replaces the triple PK × CT × K as the input of ROAdversary, ROQueryBounds and ROFullQueryBounds. Call sites pass ⟨pk, c, k⟩ and the example adversaries read ch.ct and ch.key. The enc lane's E9 KEM record will live in the same file. --- CslibFrontier.lean | 1 + .../Crypto/Computational/Encryption/KEM.lean | 48 ++++++++++++++ .../Protocols/KEM/FujisakiOkamoto/Bot.lean | 34 +++++----- .../KEM/FujisakiOkamoto/NoCheck.lean | 25 ++++---- .../KEM/FujisakiOkamoto/NotBot/Challenge.lean | 14 ++--- .../FujisakiOkamoto/NotBot/Correctness.lean | 6 +- .../KEM/FujisakiOkamoto/NotBot/Fallback.lean | 12 ++-- .../KEM/FujisakiOkamoto/NotBot/Hybrids.lean | 8 +-- .../KEM/FujisakiOkamoto/NotBot/Lazy.lean | 10 +-- .../FujisakiOkamoto/NotBot/Pushforward.lean | 4 +- .../KEM/FujisakiOkamoto/Security.lean | 62 ++++++++++--------- .../KEM/FujisakiOkamoto/SpreadNecessity.lean | 2 +- .../Crypto/FujisakiOkamoto.lean | 20 +++--- .../Crypto/FujisakiOkamotoNotBot.lean | 6 +- 14 files changed, 151 insertions(+), 101 deletions(-) create mode 100644 CslibFrontier/Crypto/Computational/Encryption/KEM.lean diff --git a/CslibFrontier.lean b/CslibFrontier.lean index 1fbf1205a8..c5567bf124 100644 --- a/CslibFrontier.lean +++ b/CslibFrontier.lean @@ -4195,6 +4195,7 @@ public import CslibFrontier.Crypto.Computational.Encryption.Hybrid public import CslibFrontier.Crypto.Computational.Encryption.Hybrid.CCA public import CslibFrontier.Crypto.Computational.Encryption.Hybrid.CompositionCCA public import CslibFrontier.Crypto.Computational.Encryption.Hybrid.DEMCCA +public import CslibFrontier.Crypto.Computational.Encryption.KEM public import CslibFrontier.Crypto.Computational.Encryption.KEMFromPKE public import CslibFrontier.Crypto.Computational.Encryption.KEMFromPKE.CCA public import CslibFrontier.Crypto.Computational.Encryption.MACThenEncrypt diff --git a/CslibFrontier/Crypto/Computational/Encryption/KEM.lean b/CslibFrontier/Crypto/Computational/Encryption/KEM.lean new file mode 100644 index 0000000000..697f2809b4 --- /dev/null +++ b/CslibFrontier/Crypto/Computational/Encryption/KEM.lean @@ -0,0 +1,48 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Cslib.Init + +/-! +# Key encapsulation challenges + +In the indistinguishability games for a key encapsulation mechanism (KEM), the challenger +generates a key pair, encapsulates a key under the public key, and gives the adversary the public +key, the challenge ciphertext and a challenge key: the encapsulated key or an independent random +key, depending on the hidden bit ([HofheinzHoevelmannsKiltz2017], Section 2.2, Definition 2.4). +`KEM.Challenge PK CT K` is that input, with named fields in place of a triple `PK × CT × K`. + +## Main definitions + +* `Cslib.Crypto.KEM.Challenge`: the public key, challenge ciphertext and challenge key given to a + KEM adversary. + +## References + +* [D. Hofheinz, K. Hövelmanns, E. Kiltz, *A Modular Analysis of the Fujisaki–Okamoto + Transformation*][HofheinzHoevelmannsKiltz2017] (IACR ePrint 2017/604, full version dated + 2 November 2021), Section 2.2, Definition 2.4. +-/ + +@[expose] public section + +namespace Cslib.Crypto + +/-- The input of a KEM indistinguishability adversary ([HofheinzHoevelmannsKiltz2017], +Section 2.2, Definition 2.4): the public key, the challenge ciphertext and the challenge key. -/ +structure KEM.Challenge (PK CT K : Type) where + /-- The public key. -/ + pk : PK + /-- The challenge ciphertext, an encapsulation under `pk`. -/ + ct : CT + /-- The challenge key: the key encapsulated in `ct`, or an independent random key, depending on + the challenge bit. -/ + key : K + +end Cslib.Crypto diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean index beb51e102e..be5294f5b3 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean @@ -195,7 +195,7 @@ def roIndCcaBotGame (S : Scheme PK SK M R CT K) let st₀ : ROCache M R CT K := { gCache := [(mStar, rStar)], hCache := [((false, mStar, cStar), kReal)] } let (guess, _) ← - (OracleComp.interpret (S.roBotHandler pk sk cStar) (A (pk, cStar, kStar))).run st₀ + (OracleComp.interpret (S.roBotHandler pk sk cStar) (A ⟨pk, cStar, kStar⟩)).run st₀ return guess /-- The ROM IND-CCA advantage of an adversary `A` against `FO^⊥`. -/ @@ -224,7 +224,7 @@ noncomputable def eagerIndCcaBotGame [Fintype M] [Finite R] [Fintype CT] [Finite let (cStar, kReal) ← ProbComp.eval (S.encapsBot G H pk) let kRand ← ProbComp.eval S.sampleKey let kStar := if b then kRand else kReal - OracleComp.interpret (S.eagerBotHandler G H pk sk cStar) (A (pk, cStar, kStar)) + OracleComp.interpret (S.eagerBotHandler G H pk sk cStar) (A ⟨pk, cStar, kStar⟩) /-- Partial-table pair `(tG, tH)` representing `ROCache`. -/ private abbrev ROTables (M R CT K : Type) : Type := @@ -416,7 +416,7 @@ theorem eval_roIndCcaBotGame_eq_eagerIndCcaBotGame [Fintype M] [Finite R] [Finty congr 1 with ⟨pk, sk⟩ : 1 have hstep (mStar : M) (rStar : R) (kReal kRand : K) : (ProbComp.eval ((OracleComp.interpret (S.roBotHandler pk sk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal))).run + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)).run { gCache := [(mStar, rStar)], hCache := [((false, mStar, S.pke.encDet pk mStar rStar), kReal)] })).bind (fun a => PMF.pure a.1) = @@ -425,7 +425,7 @@ theorem eval_roIndCcaBotGame_eq_eagerIndCcaBotGame [Fintype M] [Finite R] [Finty (pi (Function.update (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey) (false, mStar, S.pke.encDet pk mStar rStar) (PMF.pure kReal))).bind fun H => OracleComp.interpret (S.eagerBotHandler G H pk sk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal)) := by + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩) := by change (ProbComp.eval _).map Prod.fst = _ rw [eval_run_roBotHandler_eq_eager] simp only [completeROTables, roTablesOf, RandomOracle.complete_ofList_singleton, pmf_norm] @@ -438,8 +438,8 @@ theorem eval_roIndCcaBotGame_eq_eagerIndCcaBotGame [Fintype M] [Finite R] [Finty (pi dH).bind fun H => (ProbComp.eval S.sampleKey).bind fun kRand => OracleComp.interpret (S.eagerBotHandler G H pk sk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else - H (false, mStar, S.pke.encDet pk mStar rStar))))) + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else + H (false, mStar, S.pke.encDet pk mStar rStar)⟩))) congr 1 with rStar : 1 pmf_hoist (pi (Function.update dG mStar (PMF.pure rStar))) congr 1 with G : 1 @@ -447,7 +447,7 @@ theorem eval_roIndCcaBotGame_eq_eagerIndCcaBotGame [Fintype M] [Finite R] [Finty refine Eq.trans ?_ (bind_pi_update dH (false, mStar, cStar) (fun kReal H => (ProbComp.eval S.sampleKey).bind fun kRand => OracleComp.interpret (S.eagerBotHandler G H pk sk cStar) - (A (pk, cStar, if b then kRand else kReal)))) + (A ⟨pk, cStar, if b then kRand else kReal⟩))) congr 1 with kReal : 1 exact PMF.bind_comm _ _ _ @@ -489,7 +489,7 @@ noncomputable def eagerCoupledBotGame [Fintype M] [Finite R] [Fintype CT] [Finit let kRand ← ProbComp.eval S.sampleKey let kReal := S.coupledH G Hq Hraw pk sk (false, mStar, cStar) OracleComp.interpret (S.eagerCoupledBotHandler G Hq Hraw pk sk cStar) - (A (pk, cStar, if b then kRand else kReal)) + (A ⟨pk, cStar, if b then kRand else kReal⟩) private theorem eagerBotHandler_coupledH (S : Scheme PK SK M R CT K) (G : M → R) (Hq : CT → K) (Hraw : Bool × M × CT → K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : @@ -868,7 +868,7 @@ theorem closeTo_cachedCoupledBotGame_eagerCoupledBotGame (S.coupledFilterHandler (cachedCoupledBotCheck S.pke keys.1 keys.2) keys.1 keys.2 cStar) (OracleComp.query (spec := ROResp (M := M) (CT := CT) R K) (ROQuery.H (false, mStar, cStar)) >>= fun k => - A (keys.1, cStar, if b then kRand else k))).run + A ⟨keys.1, cStar, if b then kRand else k⟩)).run { gCache := [(mStar, rStar)] })).map Prod.fst := by dsimp only [cachedCoupledBotGame, coupledFilterGame] simp only [pmf_norm] @@ -881,13 +881,13 @@ theorem closeTo_cachedCoupledBotGame_eagerCoupledBotGame (S.coupledFilterHandler (cachedCoupledBotCheck S.pke pk sk) pk sk cStar) (OracleComp.query (spec := ROResp (M := M) (CT := CT) R K) (ROQuery.H (false, mStar, cStar)) >>= fun k => - A (pk, cStar, if b then kRand else k))).run + A ⟨pk, cStar, if b then kRand else k⟩)).run { gCache := [(mStar, rStar)] })).map Prod.fst = (ProbComp.eval ((S.coupledHashH pk sk (false, mStar, cStar)).run { gCache := [(mStar, rStar)] })).bind (fun z => (ProbComp.eval ((OracleComp.interpret (S.coupledFilterHandler (cachedCoupledBotCheck S.pke pk sk) pk sk cStar) - (A (pk, cStar, if b then kRand else z.1))).run z.2)).bind fun a => PMF.pure a.1) := by + (A ⟨pk, cStar, if b then kRand else z.1⟩)).run z.2)).bind fun a => PMF.pure a.1) := by rw [OracleComp.run_interpret_bind, OracleComp.run_interpret_query] simp only [pmf_norm] rfl @@ -904,7 +904,7 @@ theorem closeTo_cachedCoupledBotGame_eagerCoupledBotGame (S.eagerCoupledBotHandler T.1 T.2.2 T.2.1 keys.1 keys.2 cStar) (OracleComp.query (spec := ROResp (M := M) (CT := CT) R K) (ROQuery.H (false, mStar, cStar)) >>= fun k => - A (keys.1, cStar, if b then kRand else k)) := by + A ⟨keys.1, cStar, if b then kRand else k⟩) := by dsimp only [eagerCoupledBotGame] simp only [completeTables, tablesOf, RandomOracle.complete_ofList_singleton, RandomOracle.ofList_nil, RandomOracle.complete_empty, OracleComp.interpret_bind, @@ -922,8 +922,8 @@ theorem closeTo_cachedCoupledBotGame_eagerCoupledBotGame (ProbComp.eval S.sampleKey).bind fun kRand => OracleComp.interpret (S.eagerCoupledBotHandler G Hq Hraw pk sk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else - S.coupledH G Hq Hraw pk sk (false, mStar, S.pke.encDet pk mStar rStar)))))) + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else + S.coupledH G Hq Hraw pk sk (false, mStar, S.pke.encDet pk mStar rStar)⟩)))) congr 1 with rStar : 1 pmf_swap rw [hL, hR] @@ -939,9 +939,9 @@ theorem closeTo_cachedCoupledBotGame_eagerCoupledBotGame have hd : OracleComp.IsCostBounded ROQuery.dCost (OracleComp.query (spec := ROResp (M := M) (CT := CT) R K) (ROQuery.H (false, mStar, cStar)) >>= fun k => - A (keys.1, cStar, if b then kRand else k)) qD := + A ⟨keys.1, cStar, if b then kRand else k⟩) qD := OracleComp.IsCostBounded.liftBind (ROQuery.H (false, mStar, cStar)) _ qD fun k => - hq.2 (keys.1, cStar, if b then kRand else k) + hq.2 ⟨keys.1, cStar, if b then kRand else k⟩ exact S.closeTo_run_cachedCoupledBotHandler_eager hγ hkeys cStar _ hd _ end CompleteBot @@ -971,7 +971,7 @@ def simBotGame (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) (b : Bo S.simFilterGame (simBotCheck S.pke) A b /-- IND-CPA adversary built from a ROM IND-CCA adversary `A` against `FO^⊥`: runs -`A(pk, cStar, kStar)` against `S.simBotHandler pk cStar` from empty caches and outputs whether `m1` +`A ⟨pk, cStar, kStar⟩` against `S.simBotHandler pk cStar` from empty caches and outputs whether `m1` was queried to `G`. -/ def romBotCpaReduction (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) (m0 m1 : M) : PK → CT → ProbComp Bool := diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean index 29c01f737f..f4cc318f61 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NoCheck.lean @@ -194,7 +194,7 @@ noncomputable def eagerNoCheckGame [Fintype M] [Finite R] [Fintype CT] [Finite K let (cStar, kReal) ← ProbComp.eval (S.encapsNotBot G H pk) let kRand ← ProbComp.eval S.sampleKey OracleComp.interpret (S.eagerNoCheckOracle G H dk cStar) - (A (pk, cStar, if b then kRand else kReal)) + (A ⟨pk, cStar, if b then kRand else kReal⟩) end Scheme @@ -219,10 +219,10 @@ def leakBits [DecidableEq K] (c : CT) : /-- The bit-recovery adversary: it reads off a candidate message `m̂` bit by bit with `leakBits`, then outputs whether `H(false, m̂, c*)` equals the challenge key. -/ def leakAdv [DecidableEq K] : ROAdversary PK (Fin n → Bool) R (CT × Option (Fin n)) K := - fun (_, cStar, kStar) => do - let bits ← leakBits (R := R) (K := K) cStar.1 (List.finRange n) - let k ← OracleComp.query (ROQuery.H (false, fun i => (bits.lookup i).getD false, cStar)) - return decide (k = kStar) + fun ch => do + let bits ← leakBits (R := R) (K := K) ch.ct.1 (List.finRange n) + let k ← OracleComp.query (ROQuery.H (false, fun i => (bits.lookup i).getD false, ch.ct)) + return decide (k = ch.key) variable [DecidableEq K] @@ -245,17 +245,16 @@ theorem roQueryBounds_leakAdv : ROQueryBounds (leakAdv (PK := PK) (R := R) (CT := CT) (K := K) (n := n)) 0 (n + 1) := by have h (cost : ROQuery (Fin n → Bool) (CT × Option (Fin n)) → ℕ) (k : ℕ) (hd : ∀ x, cost (.decaps x) = 0) (hH : ∀ x, cost (.H x) = k) - (inp : PK × (CT × Option (Fin n)) × K) : - OracleComp.IsCostBounded cost (leakAdv (R := R) inp) (n * k + k) := by - obtain ⟨_, cStar, kStar⟩ := inp + (ch : KEM.Challenge PK (CT × Option (Fin n)) K) : + OracleComp.IsCostBounded cost (leakAdv (R := R) ch) (n * k + k) := by dsimp only [leakAdv] refine OracleComp.IsCostBounded.bind ?_ fun bits => ?_ · simpa using - isCostBounded_leakBits (R := R) (K := K) cost k hd hH cStar.1 (List.finRange n) + isCostBounded_leakBits (R := R) (K := K) cost k hd hH ch.ct.1 (List.finRange n) · exact ((OracleComp.IsCostBounded.query _).bind fun _ => .pure _ 0).mono (by simp [hH]) - intro inp - exact ⟨by simpa using h ROQuery.gCost 0 (fun _ => rfl) (fun _ => rfl) inp, - by simpa using h ROQuery.hCost 1 (fun _ => rfl) (fun _ => rfl) inp⟩ + intro ch + exact ⟨by simpa using h ROQuery.gCost 0 (fun _ => rfl) (fun _ => rfl) ch, + by simpa using h ROQuery.hCost 1 (fun _ => rfl) (fun _ => rfl) ch⟩ /-- The candidate message that `leakAdv` reads off for the challenge `(c, none)` when `c` decrypts to `m`: bit `i` is read as `true` iff `H(false, m i, (c, some i))` agrees with @@ -284,7 +283,7 @@ theorem interpret_leakAdv (S : Scheme PK SK (Fin n → Bool) R (CT × Option (Fi (G : (Fin n → Bool) → R) (H : Bool × (Fin n → Bool) × (CT × Option (Fin n)) → K) (dk : ImplicitSK PK SK (Fin n → Bool)) (c : CT) (m : Fin n → Bool) (hdec : ∀ i, S.pke.dec dk.sk (c, some i) = some fun _ => m i) (pk : PK) (k : K) : - OracleComp.interpret (S.eagerNoCheckOracle G H dk (c, none)) (leakAdv (pk, (c, none), k)) = + OracleComp.interpret (S.eagerNoCheckOracle G H dk (c, none)) (leakAdv ⟨pk, (c, none), k⟩) = PMF.pure (decide (H (false, leakGuess H m c, (c, none)) = k)) := by have hguess : (fun i => ((List.finRange n).map fun i => (i, leakGuess H m c i)).lookup i |>.getD false) = leakGuess H m c := diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean index bf843f4cca..157998026b 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Challenge.lean @@ -157,12 +157,12 @@ namespace Scheme variable [DecidableEq M] [DecidableEq CT] /-- The keyless simulator `simFilterHandler d pk cStar`, evaluated to `PMF`, run on -`A (pk, cStar, k)` from the state `st`. -/ +`A ⟨pk, cStar, k⟩` from the state `st`. -/ private noncomputable def simRun (S : Scheme PK SK M R CT K) (d : List (M × R) → CT → Bool) (A : ROAdversary PK M R CT K) (pk : PK) (cStar : CT) (k : K) (st : SimState M R CT K) : PMF (Bool × SimState M R CT K) := (OracleComp.interpret (m := StateT (SimState M R CT K) PMF) - (fun q s => ProbComp.eval (S.simFilterHandler d pk cStar q s)) (A (pk, cStar, k))).run st + (fun q s => ProbComp.eval (S.simFilterHandler d pk cStar q s)) (A ⟨pk, cStar, k⟩)).run st omit [DecidableEq CT] in /-- A supported transition of `simHashG` records its answer, keeps the old `gCache` entries and @@ -395,7 +395,7 @@ private theorem simRun_bad_le (S : Scheme PK SK M R CT K) (d : List (M × R) → (bad := fun st : SimState M R CT K => ∃ p ∈ st.gCache, S.pke.encDet pk p.1 p.2 = cStar) ROQuery.gCost ROQuery.hCost (fun _ => ε) 0 0 ε (fun q s hs => S.simHandler_reEnc_le d hγ hpk cStar q s hs) (fun _ _ _ _ _ _ => by simp) - (hq (pk, cStar, k)).1 (hq (pk, cStar, k)).2 (s := {}) (by simp)).trans_eq ?_ + (hq ⟨pk, cStar, k⟩).1 (hq ⟨pk, cStar, k⟩).2 (s := {}) (by simp)).trans_eq ?_ push_cast ring calc P.toOuterMeasure @@ -915,7 +915,7 @@ private theorem simRun_preset_eq (S : Scheme PK SK M R CT K) (d : List (M × R) ((OracleComp.interpret (m := StateT (SimState M R CT K) PMF) (fun q s => ProbComp.eval (S.genHandler d (S.progG mStar rStar) (S.progHq cStar kq) pk cStar q s)) - (A (pk, cStar, k))).run {}).map Prod.fst := by + (A ⟨pk, cStar, k⟩)).run {}).map Prod.fst := by have h := OracleComp.run_interpret_map_state_of_invariant (fun q s => ProbComp.eval (S.genHandler d (S.progG mStar rStar) (S.progHq cStar kq) pk cStar q s)) @@ -923,7 +923,7 @@ private theorem simRun_preset_eq (S : Scheme PK SK M R CT K) (d : List (M × R) (presetState mStar rStar cStar kq) (PresetInv mStar rStar cStar kq) (S.presetInv_genHandler d pk mStar rStar cStar kq) (fun q s hs => S.genHandler_presetState d pk mStar rStar cStar kq hd q s hs) - (A (pk, cStar, k)) {} ⟨Or.inl rfl, Or.inl rfl⟩ + (A ⟨pk, cStar, k⟩) {} ⟨Or.inl rfl, Or.inl rfl⟩ have hfst := congrArg (PMF.map Prod.fst) h rw [PMF.map_comp] at hfst exact hfst.symm @@ -1000,7 +1000,7 @@ private theorem simRun_eq_genHandler (S : Scheme PK SK M R CT K) (OracleComp.interpret (m := StateT (SimState M R CT K) PMF) (fun q s => ProbComp.eval (S.genHandler d (fun _ => S.pke.sampleCoins) (fun _ => S.sampleKey) pk cStar q s)) - (A (pk, cStar, k))).run st := by + (A ⟨pk, cStar, k⟩)).run st := by simp only [simRun, simFilterHandler_eq_genHandler] /-- For one draw of the outer randomness, the simulator run from the preset `simFilterGame` caches @@ -1023,7 +1023,7 @@ private theorem closeTo_simRun_preset (S : Scheme PK SK M R CT K) (gs₂ := fun _ => S.pke.sampleCoins) (hs₁ := S.progHq cStar kq) (hs₂ := fun _ => S.sampleKey) (mStar := mStar) pk cStar (fun m hm => by simp [progG, hm]) (fun c hc => by simp [progHq, hc]) - have h := (hiub.closeTo_run (A (pk, cStar, k)) {}).map Prod.fst + have h := (hiub.closeTo_run (A ⟨pk, cStar, k⟩) {}).map Prod.fst rw [hiub.bad_eq, ← simRun_eq_genHandler] at h refine h.mono ((S.simRun_bad_le d A hq hγ hpk mStar cStar k).trans (le_of_eq ?_)) rw [← PMF.toOuterMeasure_apply_singleton, PMF.toOuterMeasure_map_apply] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean index 7b8ef649ad..9dac2f2d4e 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean @@ -398,9 +398,9 @@ theorem advantage_coupledFilterGame_simFilterGame_le [Fintype M] [Nonempty M] (Game.advantage (ProbComp.eval (S.coupledFilterGame d₁ A b)) (ProbComp.eval (S.simFilterGame d₂ A b)) : ℝ) ≤ ((qG + qH + 1 : ℕ) : ℝ) * δ := by - have hcost (inp : PK × CT × K) : - OracleComp.IsCostBounded ROQuery.ghCost (A inp) (qG + qH) := by - have ⟨hG, hH⟩ := hq inp + have hcost (ch : KEM.Challenge PK CT K) : + OracleComp.IsCostBounded ROQuery.ghCost (A ch) (qG + qH) := by + have ⟨hG, hH⟩ := hq ch convert OracleComp.IsCostBounded.add hG hH using 1 funext q; cases q <;> rfl set E : ℝ≥0∞ := S.pke.keyGen.expect fun x => diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean index 14939140d5..384ba43ff4 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean @@ -181,14 +181,14 @@ private noncomputable def runReal (S : Scheme PK SK M R CT K) (A : ROAdversary P (b : Bool) (pk : PK) (sk : SK) (s : M) (G : M → R) (H : Bool × M × CT → K) (Hfb : CT → K) (m : M) (kRand : K) : PMF Bool := OracleComp.interpret (S.eagerHandler G (fbTable s H Hfb) ⟨sk, pk, s⟩ (encT S.pke G pk m)) - (A (pk, encT S.pke G pk m, if b then kRand else H (false, m, encT S.pke G pk m))) + (A ⟨pk, encT S.pke G pk m, if b then kRand else H (false, m, encT S.pke G pk m)⟩) /-- The adversary's run in `eagerFbGame` once all randomness is fixed. -/ private noncomputable def runFb (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) (b : Bool) (pk : PK) (sk : SK) (s : M) (G : M → R) (H : Bool × M × CT → K) (Hfb : CT → K) (m : M) (kRand : K) : PMF Bool := OracleComp.interpret (S.eagerFbHandler G H Hfb ⟨sk, pk, s⟩ (encT S.pke G pk m)) - (A (pk, encT S.pke G pk m, if b then kRand else H (false, m, encT S.pke G pk m))) + (A ⟨pk, encT S.pke G pk m, if b then kRand else H (false, m, encT S.pke G pk m)⟩) /-- The logged run of `eagerFbGame`; the oracle ignores the fallback seed, set here to `m`. -/ private noncomputable def logRunFb (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) @@ -197,7 +197,7 @@ private noncomputable def logRunFb (S : Scheme PK SK M R CT K) (A : ROAdversary (OracleComp.interpret (m := StateT (List M) PMF) (fun q st => (S.eagerFbHandler G H Hfb ⟨sk, pk, m⟩ (encT S.pke G pk m) q).map (·, logM q st)) - (A (pk, encT S.pke G pk m, if b then kRand else H (false, m, encT S.pke G pk m)))).run [] + (A ⟨pk, encT S.pke G pk m, if b then kRand else H (false, m, encT S.pke G pk m)⟩)).run [] /-- Whether the logged run of `eagerFbGame` queries `H` at the seed `s`. -/ private noncomputable def hitSeed (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) @@ -214,7 +214,7 @@ private theorem runReal_le_and_runFb_le (S : Scheme PK SK M R CT K) S.runReal A b pk sk s G H Hfb m kRand true + S.hitSeed A b pk sk s G H Hfb m kRand true := by set c := encT S.pke G pk m - set prog := A (pk, c, if b then kRand else H (false, m, c)) + set prog := A ⟨pk, c, if b then kRand else H (false, m, c)⟩ have h := identicalUntilBad_log S G H Hfb pk sk s c have e (hd : (q : ROQuery M CT) → PMF (ROResp R K q)) : OracleComp.interpret hd prog = @@ -296,8 +296,8 @@ private theorem expect_hitSeed_le [Fintype M] [Finite R] [Fintype CT] [Finite K] rw [PMF.bind_comm (ProbComp.eval S.sampleMsg)] refine Probability.PMF.bind_apply_le_on_support _ fun x hx => ?_ have hlen : x.2.length ≤ qH := by - have hcost := (hq (ks.1, encT S.pke G ks.1 m, - if b then kRand else H (false, m, encT S.pke G ks.1 m))).2 + have hcost := (hq ⟨ks.1, encT S.pke G ks.1 m, + if b then kRand else H (false, m, encT S.pke G ks.1 m)⟩).2 simpa using hcost.measure_le _ List.length (fun q st res st' hmem => by rw [show st' = logM q st from snd_eq_of_mem_support_map hmem] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean index 42112d87c1..8d01aa429e 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Hybrids.lean @@ -96,7 +96,7 @@ noncomputable def eagerFbGame [Fintype M] [Finite R] [Fintype CT] [Finite K] let (cStar, kReal) ← ProbComp.eval (S.encapsNotBot G H pk) let kRand ← ProbComp.eval S.sampleKey OracleComp.interpret (S.eagerFbHandler G H Hfb dk cStar) - (A (pk, cStar, if b then kRand else kReal)) + (A ⟨pk, cStar, if b then kRand else kReal⟩) /-! ### Eager coupled game -/ @@ -129,7 +129,7 @@ noncomputable def eagerCoupledGame [Fintype M] [Finite R] [Fintype CT] [Finite K let kRand ← ProbComp.eval S.sampleKey let kReal := S.coupledH G Hq Hraw pk sk (false, mStar, cStar) OracleComp.interpret (S.eagerCoupledHandler G Hq Hraw pk sk cStar) - (A (pk, cStar, if b then kRand else kReal)) + (A ⟨pk, cStar, if b then kRand else kReal⟩) /-! ### Lazy coupled and simulator games -/ @@ -213,7 +213,7 @@ def coupledFilterGame (S : Scheme PK SK M R CT K) (d : PK → SK → List (M × { gCache := [(mStar, rStar)] } let kRand ← S.sampleKey let (guess, _) ← (OracleComp.interpret (S.coupledFilterHandler (d pk sk) pk sk cStar) - (A (pk, cStar, if b then kRand else kReal))).run st₀ + (A ⟨pk, cStar, if b then kRand else kReal⟩)).run st₀ return guess /-- The lazy coupled game: `G(m*)` is programmed to fresh challenge coins `r*` and the real key @@ -234,7 +234,7 @@ def simFilterGame (S : Scheme PK SK M R CT K) (d : PK → List (M × R) → CT { gCache := [(mStar, rStar)] } let kRand ← S.sampleKey let (guess, _) ← (OracleComp.interpret (S.simFilterHandler (d pk) pk cStar) - (A (pk, cStar, if b then kRand else kReal))).run st₀ + (A ⟨pk, cStar, if b then kRand else kReal⟩)).run st₀ return guess /-- The lazy keyless simulator game: as `coupledGame` with `simHashH` and `simHandler`, which do diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean index 824bd69abd..44c44d3166 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean @@ -263,7 +263,7 @@ theorem eval_coupledGame_eq_eagerCoupledGame [Fintype M] [Finite R] [Fintype CT] (ProbComp.eval S.sampleKey).bind fun kRand => (ProbComp.eval ((OracleComp.interpret (S.coupledHandler pk sk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else z.1))).run z.2)).bind + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else z.1⟩)).run z.2)).bind fun a => PMF.pure a.1) = (pi (Function.update (fun _ : M => ProbComp.eval S.pke.sampleCoins) mStar (PMF.pure rStar))).bind fun G => @@ -271,19 +271,19 @@ theorem eval_coupledGame_eq_eagerCoupledGame [Fintype M] [Finite R] [Fintype CT] (pi (α := fun _ : Bool × M × CT => K) (fun _ => ProbComp.eval S.sampleKey)).bind fun H => (pi (α := fun _ : CT => K) (fun _ => ProbComp.eval S.sampleKey)).bind fun Q => OracleComp.interpret (S.eagerCoupledHandler G Q H pk sk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else - S.coupledH G Q H pk sk (false, mStar, S.pke.encDet pk mStar rStar))) := by + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else + S.coupledH G Q H pk sk (false, mStar, S.pke.encDet pk mStar rStar)⟩) := by set cStar := S.pke.encDet pk mStar rStar have hP (kRand : K) : (ProbComp.eval ((OracleComp.interpret (S.coupledHandler pk sk cStar) (OracleComp.query (spec := ROResp (M := M) (CT := CT) R K) (ROQuery.H (false, mStar, cStar)) >>= fun k => - A (pk, cStar, if b then kRand else k))).run + A ⟨pk, cStar, if b then kRand else k⟩)).run { gCache := [(mStar, rStar)] })).map Prod.fst = (ProbComp.eval ((S.coupledHashH pk sk (false, mStar, cStar)).run { gCache := [(mStar, rStar)] })).bind (fun z => (ProbComp.eval ((OracleComp.interpret (S.coupledHandler pk sk cStar) - (A (pk, cStar, if b then kRand else z.1))).run z.2)).bind fun a => PMF.pure a.1) := by + (A ⟨pk, cStar, if b then kRand else z.1⟩)).run z.2)).bind fun a => PMF.pure a.1) := by rw [OracleComp.run_interpret_bind, OracleComp.run_interpret_query] simp only [pmf_norm] rfl diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean index 1df4fcdbf2..94c5561f01 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean @@ -193,8 +193,8 @@ theorem eagerCoupledGame_eq_eagerFbGame [Fintype M] [Finite R] [Fintype CT] [Fin (ProbComp.eval S.sampleKey).bind fun kRand => OracleComp.interpret (S.eagerCoupledHandler G Hq Hraw pk sk (encT S.pke G pk mStar)) - (A (pk, encT S.pke G pk mStar, if b = true then kRand else - S.coupledH G Hq Hraw pk sk (false, mStar, encT S.pke G pk mStar))) := by + (A ⟨pk, encT S.pke G pk mStar, if b = true then kRand else + S.coupledH G Hq Hraw pk sk (false, mStar, encT S.pke G pk mStar)⟩) := by congr 1 with s : 1 congr 1 with G : 1 rw [bind_pi_reindex S G pk sk] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean index de675e9f44..285b721843 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean @@ -7,6 +7,7 @@ Authors: Karn Seth module -- upstream: candidate +public import CslibFrontier.Crypto.Computational.Encryption.KEM public import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto public import CslibFrontier.Languages.Probabilistic.RandomOracle import CslibFrontier.Languages.Probabilistic.Simulation @@ -48,8 +49,9 @@ In addition to the `ExplicitCoinPKE` and `Scheme` modeling choices documented in choices: * **Deterministic oracle adversaries (`ROAdversary`)**: adversaries are deterministic oracle - programs `PK × CT × K → OracleComp (ROResp R K) Bool` with no private-coin channel; randomized - oracle adversaries reduce to these by fixing coins (see `ProbOracleComp.coinLaw` / `fixCoins`). + programs `KEM.Challenge PK CT K → OracleComp (ROResp R K) Bool` with no private-coin channel; + randomized oracle adversaries reduce to these by fixing coins (see `ProbOracleComp.coinLaw` / + `fixCoins`). * **Standard KEM IND-CCA experiment (`Scheme.roIndCcaGame`, `Scheme.eagerIndCcaGame`, not separately located in the source map above)**: the real-or-random key experiment draws the random challenge key via `kRand ← S.sampleKey` (rather than a uniform draw on `K`), leaves @@ -175,11 +177,12 @@ of the games and proof hybrids is this dispatcher applied to its own `G`, `H` an | .H x => H x | .decaps c => if c = cStar then pure none else decaps c -/-- A deterministic IND-CCA oracle adversary in the Random Oracle Model receives `(pk, c*, k*)` -and interacts with `G`, `H`, and the decapsulation oracle before outputting a Boolean guess. -Randomized oracle adversaries reduce to these by fixing coins (see `ProbOracleComp.coinLaw`). -/ +/-- A deterministic IND-CCA oracle adversary in the Random Oracle Model receives the challenge +`⟨pk, c*, k*⟩` and interacts with `G`, `H`, and the decapsulation oracle before outputting a +Boolean guess. Randomized oracle adversaries reduce to these by fixing coins (see +`ProbOracleComp.coinLaw`). -/ abbrev ROAdversary (PK M R CT K : Type) : Type := - PK × CT × K → OracleComp (ROResp (M := M) (CT := CT) R K) Bool + KEM.Challenge PK CT K → OracleComp (ROResp (M := M) (CT := CT) R K) Bool /-- Query cost counting calls to `G`. -/ @[simp] def ROQuery.gCost : ROQuery M CT → ℕ @@ -205,19 +208,18 @@ abbrev ROAdversary (PK M R CT K : Type) : Type := | .H _ => 0 | .decaps _ => 1 -/-- An `ROAdversary` satisfies query bounds `(qG, qH)` if for every challenge input `inp`, -its execution makes at most `qG` queries to `G` and at most `qH` queries to `H`. -/ +/-- An `ROAdversary` satisfies query bounds `(qG, qH)` if on every challenge `ch` its execution +makes at most `qG` queries to `G` and at most `qH` queries to `H`. -/ def ROQueryBounds (A : ROAdversary PK M R CT K) (qG qH : ℕ) : Prop := - ∀ inp : PK × CT × K, - OracleComp.IsCostBounded ROQuery.gCost (A inp) qG ∧ - OracleComp.IsCostBounded ROQuery.hCost (A inp) qH + ∀ ch : KEM.Challenge PK CT K, + OracleComp.IsCostBounded ROQuery.gCost (A ch) qG ∧ + OracleComp.IsCostBounded ROQuery.hCost (A ch) qH -/-- An `ROAdversary` satisfies full query bounds `(qG, qH, qD)` if for every challenge input `inp`, -its execution makes at most `qG` queries to `G`, `qH` queries to `H`, and `qD` queries to -`decaps`. -/ +/-- An `ROAdversary` satisfies full query bounds `(qG, qH, qD)` if on every challenge `ch` its +execution makes at most `qG` queries to `G`, `qH` queries to `H`, and `qD` queries to `decaps`. -/ def ROFullQueryBounds (A : ROAdversary PK M R CT K) (qG qH qD : ℕ) : Prop := ROQueryBounds A qG qH ∧ - ∀ inp : PK × CT × K, OracleComp.IsCostBounded ROQuery.dCost (A inp) qD + ∀ ch : KEM.Challenge PK CT K, OracleComp.IsCostBounded ROQuery.dCost (A ch) qD /-- Association-list caches for the lazy random oracles `G : M → R` and `H : Bool × M × CT → K` in the executable ROM IND-CCA game. -/ @@ -311,7 +313,7 @@ def roIndCcaGame (S : Scheme PK SK M R CT K) let st₀ : ROCache M R CT K := { gCache := [(mStar, rStar)], hCache := [((false, mStar, cStar), kReal)] } let (guess, _) ← - (OracleComp.interpret (S.roHandler dk cStar) (A (pk, cStar, kStar))).run st₀ + (OracleComp.interpret (S.roHandler dk cStar) (A ⟨pk, cStar, kStar⟩)).run st₀ return guess /-- The ROM IND-CCA advantage of an adversary `A` against `FO-notbot`. -/ @@ -341,7 +343,7 @@ noncomputable def eagerIndCcaGame [Fintype M] [Finite R] [Fintype CT] [Finite K] let (cStar, kReal) ← ProbComp.eval (S.encapsNotBot G H pk) let kRand ← ProbComp.eval S.sampleKey let kStar := if b then kRand else kReal - OracleComp.interpret (S.eagerHandler G H dk cStar) (A (pk, cStar, kStar)) + OracleComp.interpret (S.eagerHandler G H dk cStar) (A ⟨pk, cStar, kStar⟩) /-- Simulated `H` oracle of `simHandler`: on key-derivation queries `(false, m, c)`, checks whether `S.pke.encDet pk m (G(m)) = c` and if so returns `H_q(c)`. This is designed so that decapsulation @@ -393,19 +395,19 @@ def simHandler (S : Scheme PK SK M R CT K) (pk : PK) (cStar : CT) (q : ROQuery M /-- IND-CPA adversary built from a ROM IND-CCA adversary `A` and decapsulation filter `d`: given an IND-CPA challenge `pk` and `cStar`, samples a challenge key `kStar ← S.sampleKey`, runs -`A(pk, cStar, kStar)` against `S.simFilterHandler (d pk) pk cStar`, and outputs whether `m1` was +`A ⟨pk, cStar, kStar⟩` against `S.simFilterHandler (d pk) pk cStar`, and outputs whether `m1` was queried to `G`. -/ def romFilterCpaReduction (S : Scheme PK SK M R CT K) (d : PK → List (M × R) → CT → Bool) (A : ROAdversary PK M R CT K) (_m0 m1 : M) : PK → CT → ProbComp Bool := fun pk cStar => do let kStar ← S.sampleKey let (_, st) ← - (OracleComp.interpret (S.simFilterHandler (d pk) pk cStar) (A (pk, cStar, kStar))).run {} + (OracleComp.interpret (S.simFilterHandler (d pk) pk cStar) (A ⟨pk, cStar, kStar⟩)).run {} return (st.gCache.lookup m1).isSome /-- IND-CPA adversary built from a ROM IND-CCA adversary `A` against domain-separated `FO-notbot`: given an IND-CPA challenge `pk` and `cStar`, samples a challenge key `kStar ← S.sampleKey`, runs -`A(pk, cStar, kStar)` against `S.simHandler pk cStar`, and outputs whether `m1` was +`A ⟨pk, cStar, kStar⟩` against `S.simHandler pk cStar`, and outputs whether `m1` was queried to `G`. It is modelled on the IND-CPA adversary in the proof of the §3.1 theorem for `T` in @@ -686,7 +688,7 @@ theorem eval_roIndCcaGame_eq_h1 [Fintype M] [Finite R] (ProbComp.eval S.sampleKey).bind (fun kRand => let kStar := if b then kRand else kReal let tH₀ := RandomOracle.ofList [((false, mStar, cStar), kReal)] - ((OracleComp.interpret (S.h1Handler G dk cStar) (A (pk, cStar, kStar))).run tH₀).map + ((OracleComp.interpret (S.h1Handler G dk cStar) (A ⟨pk, cStar, kStar⟩)).run tH₀).map Prod.fst))))) := by dsimp only [roIndCcaGame] simp only [ProbComp.eval_bind, ProbComp.eval_pure] @@ -695,13 +697,13 @@ theorem eval_roIndCcaGame_eq_h1 [Fintype M] [Finite R] let dG : M → PMF R := fun _ => ProbComp.eval S.pke.sampleCoins have hstep (rStar : R) (kReal kRand : K) : (ProbComp.eval ((OracleComp.interpret (S.roHandler dk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal))).run + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)).run { gCache := [(mStar, rStar)], hCache := [((false, mStar, S.pke.encDet pk mStar rStar), kReal)] })).bind (fun x => PMF.pure x.fst) = (pi (Function.update dG mStar (PMF.pure rStar))).bind (fun G => ((OracleComp.interpret (S.h1Handler G dk (S.pke.encDet pk mStar rStar)) - (A (pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal))).run + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)).run (RandomOracle.ofList [((false, mStar, S.pke.encDet pk mStar rStar), kReal)])).map Prod.fst) := by change (ProbComp.eval _).map Prod.fst = _ @@ -712,7 +714,7 @@ theorem eval_roIndCcaGame_eq_h1 [Fintype M] [Finite R] (ProbComp.eval S.sampleKey).bind fun kReal => (ProbComp.eval S.sampleKey).bind fun kRand => ((OracleComp.interpret (S.h1Handler G dk cStar) - (A (pk, cStar, if b then kRand else kReal))).run + (A ⟨pk, cStar, if b then kRand else kReal⟩)).run (RandomOracle.ofList [((false, mStar, cStar), kReal)])).map Prod.fst)] congr 1 with rStar : 1 pmf_swap @@ -801,17 +803,17 @@ theorem eval_roIndCcaGame_eq_eagerIndCcaGame [Fintype M] [Finite R] [Fintype CT] set cStar := S.pke.encDet pk mStar (G mStar) have hstep (kReal kRand : K) : ((OracleComp.interpret (S.h1Handler G dk cStar) - (A (pk, cStar, if b then kRand else kReal))).run + (A ⟨pk, cStar, if b then kRand else kReal⟩)).run (RandomOracle.ofList [((false, mStar, cStar), kReal)])).map Prod.fst = (pi (Function.update dH (false, mStar, cStar) (PMF.pure kReal))).bind (fun H => OracleComp.interpret (S.eagerHandler G H dk cStar) - (A (pk, cStar, if b then kRand else kReal))) := by + (A ⟨pk, cStar, if b then kRand else kReal⟩)) := by rw [eval_run_h1Handler_eq_eager, RandomOracle.complete_ofList_singleton] simp only [hstep] refine Eq.trans ?_ (bind_pi_update dH (false, mStar, cStar) (fun kReal H => (ProbComp.eval S.sampleKey).bind fun kRand => OracleComp.interpret (S.eagerHandler G H dk cStar) - (A (pk, cStar, if b then kRand else kReal)))) + (A ⟨pk, cStar, if b then kRand else kReal⟩))) congr 1 with kReal : 1 exact PMF.bind_comm _ _ _ @@ -1018,7 +1020,7 @@ theorem sum_simFilterHitProb_le [Fintype M] (S : Scheme PK SK M R CT K) let cStar := S.pke.encDet keys.fst m₀ rStar (OracleComp.interpret (m := StateT (SimState M R CT K) PMF) (fun q st => ProbComp.eval (S.simFilterHandler (d keys.fst) keys.fst cStar q st)) - (A (keys.fst, cStar, kStar))).run {} + (A ⟨keys.fst, cStar, kStar⟩)).run {} have heq (m₁ : M) : ProbComp.eval (S.pke.indCpaGame m₀ m₁ false (S.romFilterCpaReduction d A m₀ m₁)) = runDist.map fun p => (p.snd.gCache.lookup m₁).isSome := by @@ -1033,8 +1035,8 @@ theorem sum_simFilterHitProb_le [Fintype M] (S : Scheme PK SK M R CT K) obtain ⟨keys, -, rStar, -, kStar, -, hrun⟩ := hx set pk := keys.fst set cStar := S.pke.encDet pk m₀ rStar - have hcost : OracleComp.IsCostBounded ROQuery.ghCost (A (pk, cStar, kStar)) (qG + qH) := by - have ⟨hG, hH⟩ := hq (pk, cStar, kStar) + have hcost : OracleComp.IsCostBounded ROQuery.ghCost (A ⟨pk, cStar, kStar⟩) (qG + qH) := by + have ⟨hG, hH⟩ := hq ⟨pk, cStar, kStar⟩ convert OracleComp.IsCostBounded.add hG hH using 1 funext q; cases q <;> rfl have hlen := hcost.measure_le diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean index 563aafd220..242af68025 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/SpreadNecessity.lean @@ -208,7 +208,7 @@ end UnspreadPKE /-! ### The 1-Query Decapsulation Probe Adversary -/ -/-- The 1-query decapsulation probe adversary: ignores `(pk, c*, k*)`, queries the decapsulation +/-- The 1-query decapsulation probe adversary: ignores the challenge, queries the decapsulation oracle once on `none : Option CT`, and outputs `true` iff the oracle returns `some k`. -/ @[expose] def probeDecapsAdv : ROAdversary PK M R (Option CT) K := fun _ => do diff --git a/CslibFrontierTests/Crypto/FujisakiOkamoto.lean b/CslibFrontierTests/Crypto/FujisakiOkamoto.lean index 3db8187d86..9182bc5c0a 100644 --- a/CslibFrontierTests/Crypto/FujisakiOkamoto.lean +++ b/CslibFrontierTests/Crypto/FujisakiOkamoto.lean @@ -235,13 +235,13 @@ def identityScheme : Scheme Unit Unit (Fin 4) Unit (Fin 4) (Fin 2) where sampleMsg := ProbComp.unifFin 3 sampleKey := pure 0 -/-- ROM adversary against `identityScheme` that queries `G` on the challenge ciphertext `cStar` -(which equals the plaintext message in `identityPKE`), queries punctured decapsulation on `cStar`, +/-- ROM adversary against `identityScheme` that queries `G` on the challenge ciphertext `ch.ct` +(which equals the plaintext message in `identityPKE`), queries punctured decapsulation on `ch.ct`, and outputs `true`. -/ def queryCiphertextGAdv : ROAdversary Unit (Fin 4) Unit (Fin 4) (Fin 2) := - fun (_, cStar, _) => do - let _ ← OracleComp.query (ROQuery.G cStar) - let _ ← OracleComp.query (ROQuery.decaps cStar) + fun ch => do + let _ ← OracleComp.query (ROQuery.G ch.ct) + let _ ← OracleComp.query (ROQuery.decaps ch.ct) return true /-- `queryCiphertextGAdv` makes at most `1` query to `G` and `0` queries to `H`. -/ @@ -297,12 +297,12 @@ def identityUnifKeyScheme : Scheme Unit Unit (Fin 4) Unit (Fin 4) (Fin 2) where sampleMsg := pure 0 sampleKey := ProbComp.unifFin 1 -/-- ROM adversary that queries `H(false, cStar, cStar)` and checks whether the returned key equals -the challenge key `kStar`. -/ +/-- ROM adversary that queries `H(false, c*, c*)` on the challenge ciphertext `c* = ch.ct` and +checks whether the returned key equals the challenge key `ch.key`. -/ def checkKeyHAdv : ROAdversary Unit (Fin 4) Unit (Fin 4) (Fin 2) := - fun (_, cStar, kStar) => do - let k ← OracleComp.query (ROQuery.H (false, cStar, cStar)) - return decide (k = kStar) + fun ch => do + let k ← OracleComp.query (ROQuery.H (false, ch.ct, ch.ct)) + return decide (k = ch.key) /-- Positive-advantage witness for `indCcaRomAdvantage`: `checkKeyHAdv` distinguishes real from random keys in `identityUnifKeyScheme` with advantage `1 / 2`. -/ diff --git a/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean b/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean index 5570dff443..fb666c5487 100644 --- a/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean +++ b/CslibFrontierTests/Crypto/FujisakiOkamotoNotBot.lean @@ -102,9 +102,9 @@ theorem pairCoinScheme_sampleMsg_uniform : /-- ROM adversary that queries `H(false, 0, c*)` once (`qG = 0`, `qH = 1`) and tests whether the reply equals the challenge key `k*`. -/ def probeZeroKeyAdv : ROAdversary Unit (Fin 16) (Fin 16) (Fin 16 × Fin 16) (Fin 2) := - fun (_, cStar, kStar) => do - let k ← OracleComp.query (ROQuery.H (false, 0, cStar)) - return decide (k = kStar) + fun ch => do + let k ← OracleComp.query (ROQuery.H (false, 0, ch.ct)) + return decide (k = ch.key) /-- `probeZeroKeyAdv` makes `0` queries to `G` and `1` query to `H`. -/ theorem probeZeroKeyAdv_bounds : ROQueryBounds probeZeroKeyAdv 0 1 := by From f25aee38deb59f10fea448e849c4a9644b350ad3 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Fri, 13 Nov 2026 01:49:53 +0000 Subject: [PATCH 4/7] refactor(Crypto/KEM): fixed-function FO decapsulations are the oracle-access ones in Id decapsBot and decapsNotBot are now decapsBotM / decapsNotBotM at m := Id, as decT already is decTM at Id, so each decapsulation has one implementation. decapsBot_eq and decapsNotBot_eq (rfl) give the previous direct forms and replace unfolding at the eight proof sites; the _pure laws move after the fixed-oracle section. Manual updated. --- .../Crypto/Protocols/KEM/FujisakiOkamoto.lean | 204 ++++++++++-------- .../Protocols/KEM/FujisakiOkamoto/Bot.lean | 4 +- .../KEM/FujisakiOkamoto/NotBot/Fallback.lean | 2 +- .../KEM/FujisakiOkamoto/Security.lean | 2 +- .../security-games-and-primitives.md | 14 +- 5 files changed, 131 insertions(+), 95 deletions(-) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean index 7eb3bd61f1..aa83f170da 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto.lean @@ -122,8 +122,9 @@ to recompute `Enc(pk, m'; G(m'))` during decapsulation. `Cslib.Crypto.Protocols.FujisakiOkamoto.Scheme.decapsBotM`, `Cslib.Crypto.Protocols.FujisakiOkamoto.Scheme.decapsNotBotM`: the `T` check and both decapsulation algorithms with oracle access to `G` and `H` in any monad, run by the random-oracle - games; `decT` is `decTM` in the identity monad, and with pure oracles the decapsulations are - `decapsBot` and `decapsNotBot` (`Scheme.decapsBotM_pure`, `Scheme.decapsNotBotM_pure`). + games. `decT`, `decapsBot` and `decapsNotBot` are these in the identity monad; with pure oracles + in any lawful monad the decapsulations return `decapsBot` and `decapsNotBot` + (`Scheme.decapsBotM_pure`, `Scheme.decapsNotBotM_pure`). ## Main results @@ -428,6 +429,82 @@ structure Scheme (PK SK M R CT K : Type) where namespace Scheme +/-! ### Decapsulation with oracle access + +The random-oracle games answer `G` and `H` lazily, so their decapsulation oracles run the +decapsulation algorithms with `G : M → m R` and `H : Bool × M × CT → m K` given as oracles in a +monad `m`. Both run the `T` check `decTM` and add only their rejection policy and `H` query. +`decapsBotM_eq` and `decapsNotBotM_eq` unfold them into explicit oracle calls. The fixed-function +decapsulations `decapsBot` and `decapsNotBot` below are these algorithms in the identity monad; with +pure oracles in any lawful monad, the oracle-access versions return them (`decapsBotM_pure`, +`decapsNotBotM_pure`). -/ + +section OracleAccess + +variable {PK SK M R CT K : Type} [DecidableEq CT] {m : Type → Type} [Monad m] + +/-- Explicit-rejection decapsulation `Decaps^⊥(pk, sk, c)` of `FO^⊥` with oracle access to `G` and +`H`: returns `some (H(false, m', c))` when `decTM` accepts `m'`, and `none` otherwise. -/ +def decapsBotM (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) (pk : PK) + (sk : SK) (c : CT) : m (Option K) := do + match ← decTM S.pke G pk sk c with + | some m' => some <$> H (false, m', c) + | none => return none + +/-- Implicit-rejection decapsulation `Decaps-notbot(dk, c)` of `FO-notbot` with oracle access to +`G` and `H`: returns `H(false, m', c)` when `decTM` accepts `m'`, and the fallback key +`H(true, dk.fallback, c)` otherwise. -/ +def decapsNotBotM (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) + (dk : ImplicitSK PK SK M) (c : CT) : m K := do + match ← decTM S.pke G dk.pk dk.sk c with + | some m' => H (false, m', c) + | none => H (true, dk.fallback, c) + +variable [LawfulMonad m] + +/-- `decapsBotM` as explicit oracle calls: no query when `Dec` fails; otherwise `G(m')`, then +`H(false, m', c)` exactly when the re-encryption check passes. -/ +theorem decapsBotM_eq (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) + (pk : PK) (sk : SK) (c : CT) : + S.decapsBotM G H pk sk c = + (match S.pke.dec sk c with + | none => return none + | some m' => do + let r' ← G m' + if S.pke.encDet pk m' r' = c then do + let k ← H (false, m', c) + return some k + else + return none) := by + unfold decapsBotM decTM + cases S.pke.dec sk c with + | none => simp + | some m' => + simp only [bind_assoc, pure_bind] + refine bind_congr fun r' => ?_ + split_ifs <;> simp + +/-- `decapsNotBotM` as explicit oracle calls: `H(true, dk.fallback, c)` when `Dec` fails; +otherwise `G(m')`, then `H(false, m', c)` or `H(true, dk.fallback, c)` by the re-encryption +check. -/ +theorem decapsNotBotM_eq (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) + (dk : ImplicitSK PK SK M) (c : CT) : + S.decapsNotBotM G H dk c = + (match S.pke.dec dk.sk c with + | none => H (true, dk.fallback, c) + | some m' => do + let r' ← G m' + if S.pke.encDet dk.pk m' r' = c then H (false, m', c) else H (true, dk.fallback, c)) := by + unfold decapsNotBotM decTM + cases S.pke.dec dk.sk c with + | none => simp + | some m' => + simp only [bind_assoc, pure_bind] + refine bind_congr fun r' => ?_ + split_ifs <;> rfl + +end OracleAccess + section FixedOracles variable {PK SK M R CT K : Type} @@ -443,11 +520,18 @@ def encapsBot (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → return (c, H (false, m, c)) /-- Explicit-rejection decapsulation `Decaps^⊥(pk, sk, c)` for `FO^⊥ = U^⊥ ∘ T` -([HofheinzHoevelmannsKiltz2017], ePrint 2017/604 (2 Nov 2021), §3.2.1, Fig. 9): returns -`some (H(false, m', c))` when `decT` accepts `m'`, and `none` otherwise. -/ +([HofheinzHoevelmannsKiltz2017], ePrint 2017/604 (2 Nov 2021), §3.2.1, Fig. 9) with fixed +functions `G` and `H`: `decapsBotM` in the identity monad. It returns `some (H(false, m', c))` +when `decT` accepts `m'`, and `none` otherwise (`decapsBot_eq`). -/ def decapsBot [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (pk : PK) (sk : SK) (c : CT) : Option K := - (decT S.pke G pk sk c).map fun m' => H (false, m', c) + S.decapsBotM (m := Id) G H pk sk c + +/-- `decapsBot` returns `some (H(false, m', c))` when `decT` accepts `m'`, and `none` otherwise. -/ +theorem decapsBot_eq [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M → R) + (H : Bool × M × CT → K) (pk : PK) (sk : SK) (c : CT) : + S.decapsBot G H pk sk c = (decT S.pke G pk sk c).map fun m' => H (false, m', c) := + rfl /-- Honest recovery for `FO^⊥`: whenever `Dec(sk, Enc_1(pk, m)) = some m`, `decapsBot` returns `some (H(false, m, Enc_1(pk, m)))`. -/ @@ -455,13 +539,13 @@ theorem decapsBot_encT_eq_some [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : (H : Bool × M × CT → K) (pk : PK) (sk : SK) (m : M) (hdec : S.pke.dec sk (encT S.pke G pk m) = some m) : S.decapsBot G H pk sk (encT S.pke G pk m) = some (H (false, m, encT S.pke G pk m)) := by - simp [decapsBot, decT_encT_eq_some S.pke G pk sk m hdec] + simp [decapsBot_eq, decT_encT_eq_some S.pke G pk sk m hdec] theorem decapsBot_eq_none_of_decT_eq_none [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (pk : PK) (sk : SK) (c : CT) (hnone : decT S.pke G pk sk c = none) : S.decapsBot G H pk sk c = none := by - simp [decapsBot, hnone] + simp [decapsBot_eq, hnone] /-- Explicit rejection for `FO^⊥`: any tampered ciphertext failing the re-encryption check decapsulates to `none`. -/ @@ -490,14 +574,23 @@ def encapsNotBot (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT /-- Implicit-rejection decapsulation `Decaps-notbot(dk, c)` for the domain-separated variant of `FO-notbot = U-notbot ∘ T` ([HofheinzHoevelmannsKiltz2017], ePrint 2017/604 (2 Nov 2021), §3.2.2, -Fig. 12): returns `H(false, m', c)` when `decT` accepts `m'`, and returns the domain-separated -pseudorandom fallback key `H(true, dk.fallback, c)` otherwise (where the `Bool` tag separates -valid key derivations from rejection evaluations, in place of HHK17's untagged `H(s, c)`). -/ +Fig. 12) with fixed functions `G` and `H`: `decapsNotBotM` in the identity monad. It returns +`H(false, m', c)` when `decT` accepts `m'`, and the domain-separated pseudorandom fallback key +`H(true, dk.fallback, c)` otherwise (`decapsNotBot_eq`), where the `Bool` tag separates valid key +derivations from rejection evaluations, in place of HHK17's untagged `H(s, c)`. -/ def decapsNotBot [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (c : CT) : K := - match decT S.pke G dk.pk dk.sk c with - | some m' => H (false, m', c) - | none => H (true, dk.fallback, c) + S.decapsNotBotM (m := Id) G H dk c + +/-- `decapsNotBot` returns `H(false, m', c)` when `decT` accepts `m'`, and the fallback key +`H(true, dk.fallback, c)` otherwise. -/ +theorem decapsNotBot_eq [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M → R) + (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (c : CT) : + S.decapsNotBot G H dk c = + (match decT S.pke G dk.pk dk.sk c with + | some m' => H (false, m', c) + | none => H (true, dk.fallback, c)) := + rfl /-- Honest recovery for `FO-notbot`: whenever `Dec(dk.sk, Enc_1(dk.pk, m)) = some m`, the re-encryption check passes and `decapsNotBot` outputs `H(false, m, Enc_1(dk.pk, m))`. -/ @@ -505,13 +598,13 @@ theorem decapsNotBot_encT_eq [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (m : M) (hdec : S.pke.dec dk.sk (encT S.pke G dk.pk m) = some m) : S.decapsNotBot G H dk (encT S.pke G dk.pk m) = H (false, m, encT S.pke G dk.pk m) := by - simp [decapsNotBot, decT_encT_eq_some S.pke G dk.pk dk.sk m hdec] + simp [decapsNotBot_eq, decT_encT_eq_some S.pke G dk.pk dk.sk m hdec] theorem decapsNotBot_of_decT_eq_none [DecidableEq CT] (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (c : CT) (hnone : decT S.pke G dk.pk dk.sk c = none) : S.decapsNotBot G H dk c = H (true, dk.fallback, c) := by - simp [decapsNotBot, hnone] + simp [decapsNotBot_eq, hnone] /-- Implicit pseudorandom rejection for `FO-notbot`: any tampered ciphertext failing the re-encryption check decapsulates to `H(true, dk.fallback, c)`. -/ @@ -524,42 +617,21 @@ theorem decapsNotBot_of_reencrypt_ne [DecidableEq CT] (S : Scheme PK SK M R CT K end FixedOracles -/-! ### Decapsulation with oracle access - -The random-oracle games answer `G` and `H` lazily, so their decapsulation oracles run the -decapsulation algorithms with `G : M → m R` and `H : Bool × M × CT → m K` given as oracles in a -monad `m`. Both run the `T` check `decTM` and add only their rejection policy and `H` query. With -pure oracles they are `decapsBot` and `decapsNotBot` (`decapsBotM_pure`, `decapsNotBotM_pure`); -`decapsBotM_eq` and `decapsNotBotM_eq` unfold them into explicit oracle calls. -/ - -section OracleAccess +/-! ### Pure oracles -variable {PK SK M R CT K : Type} [DecidableEq CT] {m : Type → Type} [Monad m] - -/-- Explicit-rejection decapsulation `Decaps^⊥(pk, sk, c)` of `FO^⊥` with oracle access to `G` and -`H`: returns `some (H(false, m', c))` when `decTM` accepts `m'`, and `none` otherwise. -/ -def decapsBotM (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) (pk : PK) - (sk : SK) (c : CT) : m (Option K) := do - match ← decTM S.pke G pk sk c with - | some m' => some <$> H (false, m', c) - | none => return none +With pure oracles `pure ∘ G` and `pure ∘ H` in any lawful monad, the oracle-access decapsulations +return the fixed-function ones. -/ -/-- Implicit-rejection decapsulation `Decaps-notbot(dk, c)` of `FO-notbot` with oracle access to -`G` and `H`: returns `H(false, m', c)` when `decTM` accepts `m'`, and the fallback key -`H(true, dk.fallback, c)` otherwise. -/ -def decapsNotBotM (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) - (dk : ImplicitSK PK SK M) (c : CT) : m K := do - match ← decTM S.pke G dk.pk dk.sk c with - | some m' => H (false, m', c) - | none => H (true, dk.fallback, c) +section PureOracles -variable [LawfulMonad m] +variable {PK SK M R CT K : Type} [DecidableEq CT] {m : Type → Type} [Monad m] [LawfulMonad m] /-- With pure oracles, `decapsBotM` is the fixed-function decapsulation `decapsBot`. -/ @[simp] theorem decapsBotM_pure (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (pk : PK) (sk : SK) (c : CT) : S.decapsBotM (m := m) (pure ∘ G) (pure ∘ H) pk sk c = pure (S.decapsBot G H pk sk c) := by - unfold decapsBotM decapsBot + rw [decapsBot_eq] + unfold decapsBotM rw [decTM_pure, pure_bind] cases decT S.pke G pk sk c <;> simp @@ -567,52 +639,12 @@ variable [LawfulMonad m] @[simp] theorem decapsNotBotM_pure (S : Scheme PK SK M R CT K) (G : M → R) (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (c : CT) : S.decapsNotBotM (m := m) (pure ∘ G) (pure ∘ H) dk c = pure (S.decapsNotBot G H dk c) := by - unfold decapsNotBotM decapsNotBot + rw [decapsNotBot_eq] + unfold decapsNotBotM rw [decTM_pure, pure_bind] cases decT S.pke G dk.pk dk.sk c <;> rfl -/-- `decapsBotM` as explicit oracle calls: no query when `Dec` fails; otherwise `G(m')`, then -`H(false, m', c)` exactly when the re-encryption check passes. -/ -theorem decapsBotM_eq (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) - (pk : PK) (sk : SK) (c : CT) : - S.decapsBotM G H pk sk c = - (match S.pke.dec sk c with - | none => return none - | some m' => do - let r' ← G m' - if S.pke.encDet pk m' r' = c then do - let k ← H (false, m', c) - return some k - else - return none) := by - unfold decapsBotM decTM - cases S.pke.dec sk c with - | none => simp - | some m' => - simp only [bind_assoc, pure_bind] - refine bind_congr fun r' => ?_ - split_ifs <;> simp - -/-- `decapsNotBotM` as explicit oracle calls: `H(true, dk.fallback, c)` when `Dec` fails; -otherwise `G(m')`, then `H(false, m', c)` or `H(true, dk.fallback, c)` by the re-encryption -check. -/ -theorem decapsNotBotM_eq (S : Scheme PK SK M R CT K) (G : M → m R) (H : Bool × M × CT → m K) - (dk : ImplicitSK PK SK M) (c : CT) : - S.decapsNotBotM G H dk c = - (match S.pke.dec dk.sk c with - | none => H (true, dk.fallback, c) - | some m' => do - let r' ← G m' - if S.pke.encDet dk.pk m' r' = c then H (false, m', c) else H (true, dk.fallback, c)) := by - unfold decapsNotBotM decTM - cases S.pke.dec dk.sk c with - | none => simp - | some m' => - simp only [bind_assoc, pure_bind] - refine bind_congr fun r' => ?_ - split_ifs <;> rfl - -end OracleAccess +end PureOracles end Scheme diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean index be5294f5b3..69e8775bee 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean @@ -360,7 +360,7 @@ private theorem answerBotROHandler_sim [Fintype M] [Finite R] [Fintype CT] [Fini · dsimp only [answerBotROHandler, completeROTables, eagerBotHandler, ROQuery.dispatch] simp only [hc, ↓reduceIte, pure, StateT.pure, pmf_norm, PMF.map] · rw [answerBotROHandler_decaps S pk sk hc] - dsimp only [completeROTables, eagerBotHandler, decapsBot, ROQuery.dispatch] + dsimp only [completeROTables, eagerBotHandler, decapsBot_eq, ROQuery.dispatch] simp only [hc, ↓reduceIte, decT_eq, encT] cases hdec : S.pke.dec sk c with | none => @@ -499,7 +499,7 @@ private theorem eagerBotHandler_coupledH (S : Scheme PK SK M R CT K) (G : M → | G m => rfl | H x => rfl | decaps c => - simp only [eagerBotHandler, eagerCoupledBotHandler, decapsBot, ROQuery.dispatch] + simp only [eagerBotHandler, eagerCoupledBotHandler, decapsBot_eq, ROQuery.dispatch] by_cases hc : c = cStar · simp [hc] · simp only [hc, ↓reduceIte] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean index 384ba43ff4..c79642d1f1 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Fallback.lean @@ -150,7 +150,7 @@ private theorem eagerHandler_fbTable (S : Scheme PK SK M R CT K) (G : M → R) | G m => rfl | H x => simp [eagerHandler, eagerFbHandler, fbTable, hq x rfl] | decaps c => - simp only [eagerHandler, eagerFbHandler, decapsNotBot, ROQuery.dispatch] + simp only [eagerHandler, eagerFbHandler, decapsNotBot_eq, ROQuery.dispatch] cases decT S.pke G pk sk c <;> simp [fbTable] /-- The logged oracles of the two games are identical until the log contains the seed. -/ diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean index 285b721843..7291eb7212 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean @@ -746,7 +746,7 @@ theorem h1Handler_sim_eager [Fintype M] [Fintype CT] [Finite K] · dsimp only [h1Handler, eagerHandler, ROQuery.dispatch] simp [hc, pure, StateT.pure] · rw [h1Handler_decaps S G dk hc] - dsimp only [eagerHandler, decapsNotBot, ROQuery.dispatch] + dsimp only [eagerHandler, decapsNotBot_eq, ROQuery.dispatch] simp only [hc, ↓reduceIte] cases hdec : decT S.pke G dk.pk dk.sk c with | none => diff --git a/manual/programs-crypto-and-probability/security-games-and-primitives.md b/manual/programs-crypto-and-probability/security-games-and-primitives.md index 91e6e4b8df..2a37414520 100644 --- a/manual/programs-crypto-and-probability/security-games-and-primitives.md +++ b/manual/programs-crypto-and-probability/security-games-and-primitives.md @@ -605,9 +605,11 @@ implicit-rejection `FO-notbot` (§3.2.2, Fig. 12; `ImplicitSK`, `Scheme.keyGenNo `decapsNotBot_of_reencrypt_ne`) that tags `H : Bool × M × CT → K` with `false` for valid key derivations and `true` for fallback rejection keys `H(true, dk.fallback, c)` in place of HHK17's untagged `H(s, c)`. `FujisakiOkamoto.decTM`, `Scheme.decapsBotM` and `Scheme.decapsNotBotM` are -the same algorithms with oracle access to `G` and `H` in any monad. `decT` is `decTM` in `Id`, and -with pure oracles the decapsulations are `decapsBot` and `decapsNotBot` (`Scheme.decapsBotM_pure`, -`Scheme.decapsNotBotM_pure`). The random-oracle game handlers run them against lazy or table +the same algorithms with oracle access to `G` and `H` in any monad. `decT`, `decapsBot` and +`decapsNotBot` are these in `Id` (`decT_eq`, `Scheme.decapsBot_eq` and `Scheme.decapsNotBot_eq` +give their direct forms), and with pure oracles in any lawful monad the oracle-access +decapsulations return them (`Scheme.decapsBotM_pure`, `Scheme.decapsNotBotM_pure`). The +random-oracle game handlers run them against lazy or table oracles instead of restating the re-encryption check. For Random Oracle Model (ROM) IND-CCA games and the keyless IND-CPA reduction on `FO-notbot`, import @@ -615,8 +617,10 @@ on `FO-notbot`, import It defines the ROM query family `ROQuery M CT` (`G`, `H`, and punctured decapsulation rejecting `c = cStar` with `none`) and its dispatcher `ROQuery.dispatch`, on which every lazy, eager and simulated FO handler is built from its own `G`, `H` and decapsulation implementations (build new -hybrid handlers the same way), pathwise `(qG, qH)` query budgets `ROQueryBounds` via -`OracleComp.IsCostBounded`, the lazy-cached executable game `Scheme.roIndCcaGame` +hybrid handlers the same way), deterministic oracle adversaries `ROAdversary` on the challenge +record `KEM.Challenge PK CT K` (fields `pk`, `ct`, `key`; defined in +[`Encryption/KEM.lean`](../../CslibFrontier/Crypto/Computational/Encryption/KEM.lean)), pathwise +`(qG, qH)` query budgets `ROQueryBounds` via `OracleComp.IsCostBounded`, the lazy-cached executable game `Scheme.roIndCcaGame` (`Scheme.indCcaRomAdvantage`) and eager random-oracle game `Scheme.eagerIndCcaGame` with exact eager/lazy equivalence (`Scheme.eval_roIndCcaGame_eq_eagerIndCcaGame`, `Scheme.indCcaRomAdvantage_eq_eager`), and the keyless simulator `Scheme.simHandler` and IND-CPA From 81560e4096b122b150f336e04f97ab0d0f106483 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Fri, 13 Nov 2026 02:25:10 +0000 Subject: [PATCH 5/7] chore(Crypto/KEM): KEM leaf is a plain public section (no definition to expose) --- CslibFrontier/Crypto/Computational/Encryption/KEM.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CslibFrontier/Crypto/Computational/Encryption/KEM.lean b/CslibFrontier/Crypto/Computational/Encryption/KEM.lean index 697f2809b4..7698528052 100644 --- a/CslibFrontier/Crypto/Computational/Encryption/KEM.lean +++ b/CslibFrontier/Crypto/Computational/Encryption/KEM.lean @@ -30,7 +30,7 @@ key, depending on the hidden bit ([HofheinzHoevelmannsKiltz2017], Section 2.2, D 2 November 2021), Section 2.2, Definition 2.4. -/ -@[expose] public section +public section namespace Cslib.Crypto From 3553340dd788f46c029e6ee59701bc9b4f63b3a7 Mon Sep 17 00:00:00 2001 From: Sam Schlesinger Date: Fri, 13 Nov 2026 04:02:18 +0000 Subject: [PATCH 6/7] refactor(Crypto/KEM): one lazy=eager random-oracle lemma for the FO-notbot chains (K4a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `Languages/Probabilistic/RandomOracle/Represented.lean`: a stateful handler whose calls, read through `rep : State → Cache`, are lazy `RandomOracle.answer` calls has the law of completing `rep s` once and answering every call from the table (`eval_run_interpret_eq_complete_bind`). `Cache.sum` joins the caches of `spec₁ ⊕ₒ spec₂`, with `answer_sum_inl/inr` and `bind_complete_sum` (independent completion per side). `PMF.bind_pi_sum` moves from a private copy in NotBot/Pushforward to `Probability/Product/Split`. FO Security §2 and NotBot/Lazy now state each game query once, as an oracle program over the joined random oracles, and derive their eager/lazy theorems from the generic lemma. The per-game table handlers, their per-query `ofList` bridges and `answer_complete` simulations are deleted: `h1Handler`, `roAnswerHandler`, `roHandler_map_ofList`, `roAnswerHandler_sim_h1`, `eval_run_roHandler_eq_h1`, `eval_roIndCcaGame_eq_h1`, `h1Handler_sim_eager`, `eval_run_h1Handler_eq_eager` and Lazy's `answerHandler` chain. The table API that only Bot's CloseTo chain still uses (`Tables`, `tablesOf`, `answerHashH`, `coupledHashH_map_tablesOf`, `FullTables`, `completeTables`) moves into Bot as private declarations; K4b migrates it. Also: `ROQueryBounds.ghCost` replaces two inline copies, and the KEM leaf imports `CslibFrontier.Init`. Statements of `eval_roIndCcaGame_eq_eagerIndCcaGame`, `indCcaRomAdvantage_eq_eager` and `eval_coupledGame_eq_eagerCoupledGame` are unchanged. Part of #4931 (#4817). --- CslibFrontier.lean | 1 + .../Crypto/Computational/Encryption/KEM.lean | 2 +- .../Protocols/KEM/FujisakiOkamoto/Bot.lean | 66 ++- .../FujisakiOkamoto/NotBot/Correctness.lean | 7 +- .../KEM/FujisakiOkamoto/NotBot/Lazy.lean | 331 +++++------ .../FujisakiOkamoto/NotBot/Pushforward.lean | 24 +- .../KEM/FujisakiOkamoto/Security.lean | 531 +++++------------- .../RandomOracle/Represented.lean | 187 ++++++ CslibFrontier/Probability/Product/Split.lean | 29 + manual/workflows/probabilistic-workflows.md | 34 ++ 10 files changed, 619 insertions(+), 593 deletions(-) create mode 100644 CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean diff --git a/CslibFrontier.lean b/CslibFrontier.lean index 9e5e0e8e63..0a574bde2c 100644 --- a/CslibFrontier.lean +++ b/CslibFrontier.lean @@ -6067,6 +6067,7 @@ public import CslibFrontier.Languages.Probabilistic.QueryCount.Support public import CslibFrontier.Languages.Probabilistic.RandomOracle public import CslibFrontier.Languages.Probabilistic.RandomOracle.Probabilistic public import CslibFrontier.Languages.Probabilistic.RandomOracle.Programming +public import CslibFrontier.Languages.Probabilistic.RandomOracle.Represented public import CslibFrontier.Languages.Probabilistic.RandomPermutation public import CslibFrontier.Languages.Probabilistic.RandomPermutation.ExtPermsCard public import CslibFrontier.Languages.Probabilistic.Rational diff --git a/CslibFrontier/Crypto/Computational/Encryption/KEM.lean b/CslibFrontier/Crypto/Computational/Encryption/KEM.lean index 7698528052..a4c927c524 100644 --- a/CslibFrontier/Crypto/Computational/Encryption/KEM.lean +++ b/CslibFrontier/Crypto/Computational/Encryption/KEM.lean @@ -7,7 +7,7 @@ Authors: Samuel Schlesinger module -- upstream: candidate -public import Cslib.Init +public import CslibFrontier.Init /-! # Key encapsulation challenges diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean index 69e8775bee..dba14d0cf1 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean @@ -13,7 +13,6 @@ import CslibFrontier.Crypto.Game.CloseTo.UntilBad import CslibFrontier.Crypto.Game.Hybrid import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Challenge import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Correctness -import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Lazy import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Pushforward import CslibFrontier.Languages.Probabilistic.Norm import CslibFrontier.Languages.Probabilistic.RandomOracle.Probabilistic @@ -548,6 +547,59 @@ def cachedCoupledBotGame (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT (b : Bool) : ProbComp Bool := S.coupledFilterGame (cachedCoupledBotCheck S.pke) A b +/-- The three partial tables `(G, Hraw, Hq)` of the coupled game. -/ +private abbrev Tables (M R CT K : Type) : Type := + RandomOracle.Cache (fun _ : M => R) × RandomOracle.Cache (fun _ : Bool × M × CT => K) × + RandomOracle.Cache (fun _ : CT => K) + +/-- The represented tables of a `SimState`. -/ +private def tablesOf (st : SimState M R CT K) : Tables M R CT K := + (RandomOracle.ofList st.gCache, RandomOracle.ofList st.hCache, RandomOracle.ofList st.hqCache) + +/-- `coupledHashH` on represented tables, answered with `RandomOracle.answer`. -/ +private noncomputable def answerHashH (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) + (x : Bool × M × CT) : StateT (Tables M R CT K) PMF K := fun (tG, tH, tQ) => + match x with + | (false, m, c) => + (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).bind fun y => + if S.pke.encDet pk m y.1 = c ∧ S.pke.dec sk c = some m then + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) c tQ).map + fun z => (z.1, (y.2, tH, z.2)) + else + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m, c) tH).map + fun z => (z.1, (y.2, z.2, tQ)) + | (true, m, c) => + (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, m, c) tH).map + fun z => (z.1, (tG, z.2, tQ)) + +/-- Mapping `SimState` to its represented tables turns `coupledHashH` into `answerHashH`. -/ +private theorem coupledHashH_map_tablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) + (x : Bool × M × CT) (st : SimState M R CT K) : + (ProbComp.eval (S.coupledHashH pk sk x st)).map (fun z => (z.1, tablesOf z.2)) = + S.answerHashH pk sk x (tablesOf st) := by + rcases x with ⟨_ | _, m, c⟩ + · change (ProbComp.eval (simHashG S.pke.sampleCoins m st >>= fun vs => + (if S.pke.encDet pk m vs.1 = c ∧ S.pke.dec sk c = some m then + simHashHq S.sampleKey c else simHashHRaw S.sampleKey (false, m, c)) vs.2)).map _ = _ + dsimp only [answerHashH, simHashG] + refine eval_zoom_memoize_bind (fun st : SimState M R CT K => st.gCache) + (fun st g => { st with gCache := g }) S.pke.sampleCoins m st + (fun r st' => (if S.pke.encDet pk m r = c ∧ S.pke.dec sk c = some m then + simHashHq S.sampleKey c else simHashHRaw S.sampleKey (false, m, c)) st') + tablesOf _ (fun r gC => ?_) + by_cases h : S.pke.encDet pk m r = c ∧ S.pke.dec sk c = some m + · simp only [h, and_self, ↓reduceIte] + exact eval_zoom_memoize_map SimState.hqCache (fun st q => { st with hqCache := q }) + S.sampleKey c _ (fun k => k) tablesOf + (fun q => (RandomOracle.ofList gC, RandomOracle.ofList st.hCache, q)) (fun _ => rfl) + · simp only [h, ↓reduceIte] + exact eval_zoom_memoize_map (fun st : SimState M R CT K => st.hCache) + (fun st h => { st with hCache := h }) S.sampleKey (false, m, c) _ (fun k => k) tablesOf + (fun h => (RandomOracle.ofList gC, h, RandomOracle.ofList st.hqCache)) (fun _ => rfl) + · exact eval_zoom_memoize_map (fun st : SimState M R CT K => st.hCache) + (fun st h => { st with hCache := h }) S.sampleKey (true, m, c) st (fun k => k) tablesOf + (fun h => (RandomOracle.ofList st.gCache, h, RandomOracle.ofList st.hqCache)) (fun _ => rfl) + /-- `coupledFilterHandler (cachedCoupledBotCheck S.pke pk sk)` on represented tables `(tG, tH, tQ)`. -/ private noncomputable def answerCachedBotHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) @@ -616,6 +668,18 @@ section CompleteBot variable [Fintype M] [Finite R] [Fintype CT] [Finite K] +/-- Full tables `(G, Hraw, Hq)` of the eager coupled game. -/ +private abbrev FullTables (M R CT K : Type) : Type := + (M → R) × (Bool × M × CT → K) × (CT → K) + +/-- Complete the three represented tables independently. -/ +private noncomputable def completeTables (S : Scheme PK SK M R CT K) : + Tables M R CT K → PMF (FullTables M R CT K) + | (tG, tH, tQ) => + (RandomOracle.complete (fun _ => ProbComp.eval S.pke.sampleCoins) tG).bind fun G => + (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tH).bind fun H => + (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tQ).map fun Q => (G, H, Q) + private noncomputable def eagerCoupledBotStateHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) (q : ROQuery M CT) : StateT (FullTables M R CT K) PMF (ROResp R K q) := diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean index 9dac2f2d4e..f13a934800 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Correctness.lean @@ -398,11 +398,6 @@ theorem advantage_coupledFilterGame_simFilterGame_le [Fintype M] [Nonempty M] (Game.advantage (ProbComp.eval (S.coupledFilterGame d₁ A b)) (ProbComp.eval (S.simFilterGame d₂ A b)) : ℝ) ≤ ((qG + qH + 1 : ℕ) : ℝ) * δ := by - have hcost (ch : KEM.Challenge PK CT K) : - OracleComp.IsCostBounded ROQuery.ghCost (A ch) (qG + qH) := by - have ⟨hG, hH⟩ := hq ch - convert OracleComp.IsCostBounded.add hG hH using 1 - funext q; cases q <;> rfl set E : ℝ≥0∞ := S.pke.keyGen.expect fun x => ENNReal.ofReal (S.pke.keyPairMaxFailProb x.1 x.2) with hE have hclose : CloseTo (ProbComp.eval (S.coupledFilterGame d₁ A b)) @@ -456,7 +451,7 @@ theorem advantage_coupledFilterGame_simFilterGame_le [Fintype M] [Nonempty M] exact hr refine (CloseTo.bind_right_tsum _ (err := fun _ => ((qG + qH : ℕ) : ℝ≥0∞) * p) fun kRand _ => ?_).mono (le_of_eq (hconst _ _)) - exact closeTo_run_coupled_sim S pk sk _ (d₁ pk sk) (d₂ pk) (hd pk sk) _ (hcost _) hgood _ + exact closeTo_run_coupled_sim S pk sk _ (d₁ pk sk) (d₂ pk) (hd pk sk) _ (hq.ghCost _) hgood _ · rw [ite_eq_right_of_eq_false _ _ (eq_false hr)] exact fun e => (Probability.PMF.toOuterMeasure_le_one _ e).trans (le_self_add.trans le_add_self) diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean index 44c44d3166..3cae809be7 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean @@ -10,7 +10,7 @@ module public import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Hybrids import CslibFrontier.Languages.Probabilistic.Norm import CslibFrontier.Languages.Probabilistic.RandomOracle.Probabilistic -import CslibFrontier.Languages.Probabilistic.Simulation +import CslibFrontier.Languages.Probabilistic.RandomOracle.Represented import CslibFrontier.Tactic.PMFSwap /-! @@ -23,11 +23,11 @@ that it has the same output distribution as the eager coupled game `Scheme.eager step in the IND-CCA proof for `FO-notbot` of [HofheinzHoevelmannsKiltz2017] (IACR ePrint 2017/604, full version dated 2 November 2021, §3.1 and §3.2.2). -The proof follows the eager/lazy argument of -`CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.Security`: the list caches are mapped to -`RandomOracle.Cache` tables with `RandomOracle.ofList`, and the three tables are then completed -at once with `RandomOracle.complete`; each oracle call commutes with completion by -`RandomOracle.answer_complete`. +The three lazy tables are one lazy random oracle on the joined interface `CoupledSpec`, and every +query of the coupled game is the oracle program `coupledProgram` over it. So +`RandomOracle.eval_run_interpret_eq_complete_bind` replaces the three caches by complete tables in +one step, and `RandomOracle.bind_complete_sum` splits the completed table into `G`, `Hraw` and +`Hq`. ## Main results @@ -47,8 +47,7 @@ namespace Cslib.Crypto.Protocols.FujisakiOkamoto open Probability open Probability.PMF -open RandomOracle (eval_zoom_memoize_map eval_zoom_memoize_bind - answer_bind_complete answer_bind_complete₁ answer_bind_complete₂) +open scoped Cslib variable {PK SK M R CT K : Type} @@ -56,197 +55,156 @@ namespace Scheme variable [DecidableEq M] [DecidableEq CT] -/-- The three partial tables `(G, Hraw, Hq)` of the coupled game. -/ -abbrev Tables (M R CT K : Type) : Type := - RandomOracle.Cache (fun _ : M => R) × RandomOracle.Cache (fun _ : Bool × M × CT => K) × - RandomOracle.Cache (fun _ : CT => K) - -/-- The represented tables of a `SimState`. -/ -@[expose] def tablesOf (st : SimState M R CT K) : Tables M R CT K := - (RandomOracle.ofList st.gCache, RandomOracle.ofList st.hCache, RandomOracle.ofList st.hqCache) - -/-- `coupledHashH` on represented tables, answered with `RandomOracle.answer`. -/ -@[expose] noncomputable def answerHashH (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (x : Bool × M × CT) : StateT (Tables M R CT K) PMF K := fun (tG, tH, tQ) => - match x with - | (false, m, c) => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).bind fun y => - if S.pke.encDet pk m y.1 = c ∧ S.pke.dec sk c = some m then - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) c tQ).map - fun z => (z.1, (y.2, tH, z.2)) - else - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m, c) tH).map - fun z => (z.1, (y.2, z.2, tQ)) - | (true, m, c) => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, m, c) tH).map - fun z => (z.1, (tG, z.2, tQ)) - -/-- `coupledHandler` on represented tables, answered with `RandomOracle.answer`. -/ -private noncomputable def answerHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) (q : ROQuery M CT) : StateT (Tables M R CT K) PMF (ROResp R K q) := - ROQuery.dispatch - (fun m t => (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m t.1).map - fun z => (z.1, (z.2, t.2.1, t.2.2))) - (S.answerHashH pk sk) - (fun c t => (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) c t.2.2).map - fun z => (some z.1, (t.1, t.2.1, z.2))) - cStar q - -/-- Mapping `SimState` to its represented tables turns `coupledHashH` into `answerHashH`. -/ -theorem coupledHashH_map_tablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (x : Bool × M × CT) (st : SimState M R CT K) : - (ProbComp.eval (S.coupledHashH pk sk x st)).map (fun z => (z.1, tablesOf z.2)) = - S.answerHashH pk sk x (tablesOf st) := by - rcases x with ⟨_ | _, m, c⟩ - · change (ProbComp.eval (simHashG S.pke.sampleCoins m st >>= fun vs => - (if S.pke.encDet pk m vs.1 = c ∧ S.pke.dec sk c = some m then - simHashHq S.sampleKey c else simHashHRaw S.sampleKey (false, m, c)) vs.2)).map _ = _ - dsimp only [answerHashH, simHashG] - refine eval_zoom_memoize_bind (fun st : SimState M R CT K => st.gCache) - (fun st g => { st with gCache := g }) S.pke.sampleCoins m st - (fun r st' => (if S.pke.encDet pk m r = c ∧ S.pke.dec sk c = some m then - simHashHq S.sampleKey c else simHashHRaw S.sampleKey (false, m, c)) st') - tablesOf _ (fun r gC => ?_) - by_cases h : S.pke.encDet pk m r = c ∧ S.pke.dec sk c = some m - · simp only [h, and_self, ↓reduceIte] - exact eval_zoom_memoize_map SimState.hqCache (fun st q => { st with hqCache := q }) - S.sampleKey c _ (fun k => k) tablesOf - (fun q => (RandomOracle.ofList gC, RandomOracle.ofList st.hCache, q)) (fun _ => rfl) - · simp only [h, ↓reduceIte] - exact eval_zoom_memoize_map (fun st : SimState M R CT K => st.hCache) - (fun st h => { st with hCache := h }) S.sampleKey (false, m, c) _ (fun k => k) tablesOf - (fun h => (RandomOracle.ofList gC, h, RandomOracle.ofList st.hqCache)) (fun _ => rfl) - · exact eval_zoom_memoize_map (fun st : SimState M R CT K => st.hCache) - (fun st h => { st with hCache := h }) S.sampleKey (true, m, c) st (fun k => k) tablesOf - (fun h => (RandomOracle.ofList st.gCache, h, RandomOracle.ofList st.hqCache)) (fun _ => rfl) - -/-- Mapping `SimState` to its represented tables turns `coupledHandler` into `answerHandler`. -/ -private theorem coupledHandler_map_tablesOf (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) (q : ROQuery M CT) (st : SimState M R CT K) : - (ProbComp.eval (S.coupledHandler pk sk cStar q st)).map (fun z => (z.1, tablesOf z.2)) = - S.answerHandler pk sk cStar q (tablesOf st) := by +/-- The random oracles `G : M → R`, `Hraw : Bool × M × CT → K` and `Hq : CT → K` of the coupled +game as one interface. -/ +private abbrev CoupledSpec (M R CT K : Type) := + ((fun _ : M => R) ⊕ₒ (fun _ : Bool × M × CT => K)) ⊕ₒ (fun _ : CT => K) + +/-- Each query of the coupled game as a program over its random oracles: `G` is forwarded, `H` +runs the coupled key derivation `coupledH`, and decapsulation reads `Hq`. -/ +private def coupledProgram (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) : + (q : ROQuery M CT) → OracleComp (CoupledSpec M R CT K) (ROResp R K q) := + ROQuery.dispatch (fun m => OracleComp.query (spec := CoupledSpec M R CT K) (.inl (.inl m))) + (fun + | (false, m, c) => do + let r : R ← OracleComp.query (spec := CoupledSpec M R CT K) (.inl (.inl m)) + if S.pke.encDet pk m r = c ∧ S.pke.dec sk c = some m then + OracleComp.query (spec := CoupledSpec M R CT K) (.inr c) + else + OracleComp.query (spec := CoupledSpec M R CT K) (.inl (.inr (false, m, c))) + | (true, m, c) => OracleComp.query (spec := CoupledSpec M R CT K) (.inl (.inr (true, m, c)))) + (fun c => do + let k ← OracleComp.query (spec := CoupledSpec M R CT K) (.inr c) + return some k) + cStar + +/-- The lazy oracles of the coupled game on the caches of `SimState`. -/ +private abbrev lazyOracle (S : Scheme PK SK M R CT K) : + (q : (M ⊕ (Bool × M × CT)) ⊕ CT) → + StateT (SimState M R CT K) ProbComp (CoupledSpec M R CT K q) := + OracleComp.sumHandler + (OracleComp.sumHandler (simHashG S.pke.sampleCoins) (simHashHRaw S.sampleKey)) + (simHashHq S.sampleKey) + +/-- The sampling distributions of the coupled game's random oracles. -/ +private noncomputable abbrev coupledDistribution (S : Scheme PK SK M R CT K) : + (q : (M ⊕ (Bool × M × CT)) ⊕ CT) → PMF (CoupledSpec M R CT K q) := + OracleComp.sumHandler + (OracleComp.sumHandler (fun _ => ProbComp.eval S.pke.sampleCoins) + (fun _ => ProbComp.eval S.sampleKey)) + (fun _ => ProbComp.eval S.sampleKey) + +/-- The coupled game's oracles answered from complete tables `G`, `Hraw` and `Hq`. -/ +private noncomputable abbrev tableOracle (G : M → R) (Hraw : Bool × M × CT → K) (Hq : CT → K) : + (q : (M ⊕ (Bool × M × CT)) ⊕ CT) → PMF (CoupledSpec M R CT K q) := + OracleComp.sumHandler + (OracleComp.sumHandler (fun m => PMF.pure (G m)) (fun x => PMF.pure (Hraw x))) + (fun c => PMF.pure (Hq c)) + +/-- The joined cache represented by the three list caches of a `SimState`. -/ +private abbrev coupledCache (st : SimState M R CT K) : RandomOracle.Cache (CoupledSpec M R CT K) := + RandomOracle.Cache.sum + (RandomOracle.Cache.sum (RandomOracle.ofList st.gCache) (RandomOracle.ofList st.hCache)) + (RandomOracle.ofList st.hqCache) + +/-- Each lazy oracle call, read on the joined cache, is a lazy call of the joined oracle. -/ +private theorem eval_lazyOracle_map_coupledCache (S : Scheme PK SK M R CT K) + (q : (M ⊕ (Bool × M × CT)) ⊕ CT) (st : SimState M R CT K) : + (ProbComp.eval (S.lazyOracle q st)).map (fun z => (z.1, coupledCache z.2)) = + RandomOracle.answer S.coupledDistribution q (coupledCache st) := by + rcases q with (m | x) | c + · rw [coupledCache, RandomOracle.answer_sum_inl, RandomOracle.answer_sum_inl, PMF.map_comp] + exact RandomOracle.eval_zoom_memoize_map (fun st : SimState M R CT K => st.gCache) + (fun st g => { st with gCache := g }) S.pke.sampleCoins m st (fun r => r) coupledCache + (fun g => RandomOracle.Cache.sum + (RandomOracle.Cache.sum g (RandomOracle.ofList st.hCache)) + (RandomOracle.ofList st.hqCache)) (fun _ => rfl) + · rw [coupledCache, RandomOracle.answer_sum_inl, RandomOracle.answer_sum_inr, PMF.map_comp] + exact RandomOracle.eval_zoom_memoize_map (fun st : SimState M R CT K => st.hCache) + (fun st h => { st with hCache := h }) S.sampleKey x st (fun k => k) coupledCache + (fun h => RandomOracle.Cache.sum + (RandomOracle.Cache.sum (RandomOracle.ofList st.gCache) h) + (RandomOracle.ofList st.hqCache)) (fun _ => rfl) + · rw [coupledCache, RandomOracle.answer_sum_inr] + exact RandomOracle.eval_zoom_memoize_map SimState.hqCache + (fun st q => { st with hqCache := q }) S.sampleKey c st (fun k => k) coupledCache + (fun q => RandomOracle.Cache.sum + (RandomOracle.Cache.sum (RandomOracle.ofList st.gCache) (RandomOracle.ofList st.hCache)) + q) (fun _ => rfl) + +/-- `coupledHandler` interprets `coupledProgram` with the lazy oracles on `SimState`. -/ +private theorem coupledHandler_eq_interpret (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) + (cStar : CT) (q : ROQuery M CT) : + S.coupledHandler pk sk cStar q = + OracleComp.interpret S.lazyOracle (S.coupledProgram pk sk cStar q) := by cases q with - | G m => - exact eval_zoom_memoize_map (fun st : SimState M R CT K => st.gCache) - (fun st g => { st with gCache := g }) S.pke.sampleCoins m st (fun r => r) tablesOf - (fun g => (g, RandomOracle.ofList st.hCache, RandomOracle.ofList st.hqCache)) - (fun _ => rfl) - | H x => exact S.coupledHashH_map_tablesOf pk sk x st + | G m => exact (OracleComp.interpret_query S.lazyOracle (.inl (.inl m))).symm + | H x => + rcases x with ⟨_ | _, m, c⟩ + · simp only [coupledHandler, coupledFilterHandler, simFilterDispatch, coupledHashH, + coupledProgram, ROQuery.dispatch, OracleComp.interpret_bind, OracleComp.interpret_query] + refine congrArg (simHashG S.pke.sampleCoins m >>= ·) (funext fun r => ?_) + split_ifs <;> exact (OracleComp.interpret_query _ _).symm + · exact (OracleComp.interpret_query S.lazyOracle (.inl (.inr (true, m, c)))).symm | decaps c => by_cases hc : c = cStar - · simp only [coupledHandler, coupledFilterHandler, simFilterDispatch, answerHandler, hc, - ↓reduceIte, ROQuery.dispatch] - exact PMF.pure_map _ _ - · simp only [coupledHandler, coupledFilterHandler, simFilterDispatch, answerHandler, hc, - ↓reduceIte, Bool.cond_true, ROQuery.dispatch, filterDecaps] - change (ProbComp.eval (simHashHq S.sampleKey c st >>= fun vs => - pure (some vs.1, vs.2))).map _ = _ - refine Eq.trans ?_ (eval_zoom_memoize_map SimState.hqCache - (fun st q => { st with hqCache := q }) S.sampleKey c st some tablesOf - (fun q => (RandomOracle.ofList st.gCache, RandomOracle.ofList st.hCache, q)) - (fun _ => rfl)) - rw [ProbComp.eval_bind, PMF.map_bind] - simp only [ProbComp.eval_pure, PMF.pure_map] + · simp [coupledHandler, coupledFilterHandler, simFilterDispatch, coupledProgram, hc] + · simp only [coupledHandler, coupledFilterHandler, simFilterDispatch, coupledProgram, + ROQuery.dispatch, hc, ↓reduceIte, OracleComp.interpret_bind, OracleComp.interpret_query, + OracleComp.interpret_pure] rfl -section Complete - -variable [Fintype M] [Finite R] [Fintype CT] [Finite K] - -/-- Full tables `(G, Hraw, Hq)` of the eager coupled game. -/ -abbrev FullTables (M R CT K : Type) : Type := - (M → R) × (Bool × M × CT → K) × (CT → K) - -/-- Complete the three represented tables independently. -/ -@[expose] noncomputable def completeTables (S : Scheme PK SK M R CT K) : - Tables M R CT K → PMF (FullTables M R CT K) - | (tG, tH, tQ) => - (RandomOracle.complete (fun _ => ProbComp.eval S.pke.sampleCoins) tG).bind fun G => - (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tH).bind fun H => - (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tQ).map fun Q => (G, H, Q) - -/-- `eagerCoupledHandler` reading its tables from the state. -/ -private noncomputable def eagerStateHandler (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) (q : ROQuery M CT) : StateT (FullTables M R CT K) PMF (ROResp R K q) := - fun T => (S.eagerCoupledHandler T.1 T.2.2 T.2.1 pk sk cStar q).map (·, T) - -/-- Single-step simulation from `answerHandler` to `eagerStateHandler` via -`RandomOracle.answer_complete`. -/ -private theorem answerHandler_sim (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) - (q : ROQuery M CT) (t : Tables M R CT K) : - (S.answerHandler pk sk cStar q t).bind (fun x => (S.completeTables x.2).map (x.1, ·)) = - (S.completeTables t).bind (S.eagerStateHandler pk sk cStar q) := by - rcases t with ⟨tG, tH, tQ⟩ +/-- `eagerCoupledHandler` interprets `coupledProgram` with the complete tables. -/ +private theorem eagerCoupledHandler_eq_interpret (S : Scheme PK SK M R CT K) (G : M → R) + (Hq : CT → K) (Hraw : Bool × M × CT → K) (pk : PK) (sk : SK) (cStar : CT) + (q : ROQuery M CT) : + S.eagerCoupledHandler G Hq Hraw pk sk cStar q = + OracleComp.interpret (tableOracle G Hraw Hq) (S.coupledProgram pk sk cStar q) := by cases q with - | G m => - simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, pmf_norm, - ROQuery.dispatch] - rw [answer_bind_complete] - · rfl - · intros; rfl + | G m => exact (OracleComp.interpret_query (tableOracle G Hraw Hq) (.inl (.inl m))).symm | H x => rcases x with ⟨_ | _, m, c⟩ - · simp only [answerHandler, answerHashH, completeTables, eagerStateHandler, - eagerCoupledHandler, coupledH, pmf_norm, ROQuery.dispatch] - refine Eq.trans ?_ (answer_bind_complete _ tG m - (fun x G => (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tH).bind fun H => - (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) tQ).bind fun Q => - PMF.pure (if S.pke.encDet pk m x.1 = c ∧ S.pke.dec sk c = some m then Q c - else H (false, m, c), G, H, Q)) (fun _ _ _ => rfl)) - congr 1 with y : 1 - by_cases h : S.pke.encDet pk m y.1 = c ∧ S.pke.dec sk c = some m - · simp only [h, and_self, ↓reduceIte, pmf_norm, PMF.map] - rw [answer_bind_complete₂] - · pmf_swap - · intros; rfl - · simp only [h, ↓reduceIte, pmf_norm, PMF.map] - rw [answer_bind_complete₁] - · pmf_swap - · intros; rfl - · simp only [answerHandler, answerHashH, completeTables, eagerStateHandler, - eagerCoupledHandler, coupledH, pmf_norm, PMF.map, ROQuery.dispatch] - rw [answer_bind_complete₁] - · pmf_swap - · intros; rfl + · simp only [eagerCoupledHandler, coupledH, coupledProgram, ROQuery.dispatch, + OracleComp.interpret_bind, OracleComp.interpret_query, Function.comp_apply, + PMF.monad_bind_eq_bind, PMF.pure_bind] + split_ifs <;> exact (OracleComp.interpret_query _ _).symm + · exact (OracleComp.interpret_query (tableOracle G Hraw Hq) (.inl (.inr (true, m, c)))).symm | decaps c => by_cases hc : c = cStar - · simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, hc, - ↓reduceIte, pure, StateT.pure, pmf_norm, PMF.map, ROQuery.dispatch] - · simp only [answerHandler, completeTables, eagerStateHandler, eagerCoupledHandler, hc, - ↓reduceIte, pmf_norm, PMF.map, ROQuery.dispatch] - rw [answer_bind_complete₂] - · pmf_swap - · intros; rfl + · simp [eagerCoupledHandler, coupledProgram, hc] + · simp only [eagerCoupledHandler, coupledProgram, ROQuery.dispatch, hc, ↓reduceIte, + OracleComp.interpret_bind, OracleComp.interpret_query, OracleComp.interpret_pure, + PMF.monad_bind_eq_bind, PMF.pure_bind] + rfl -/-- Running `prog` under `coupledHandler` from `st` has the output distribution of completing the -represented tables of `st` and interpreting `prog` under `eagerCoupledHandler`. -/ -private theorem eval_run_coupledHandler_eq_eager (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) - (cStar : CT) {α : Type} (prog : OracleComp (ROResp (M := M) (CT := CT) R K) α) - (st : SimState M R CT K) : +/-- Running a program under `coupledHandler` has the law of completing the three caches into +tables `G`, `Hraw` and `Hq` and running it under `eagerCoupledHandler`. -/ +private theorem eval_run_coupledHandler_eq_eager [Fintype M] [Finite R] [Fintype CT] [Finite K] + (S : Scheme PK SK M R CT K) (pk : PK) (sk : SK) (cStar : CT) {α : Type} + (prog : OracleComp (ROResp (M := M) (CT := CT) R K) α) (st : SimState M R CT K) : (ProbComp.eval ((OracleComp.interpret (S.coupledHandler pk sk cStar) prog).run st)).map Prod.fst = - (S.completeTables (tablesOf st)).bind fun T => - OracleComp.interpret (S.eagerCoupledHandler T.1 T.2.2 T.2.1 pk sk cStar) prog := by - rw [OracleComp.map_run_interpret ProbComp.isMonadHom_eval] - have hmap := congrArg (PMF.map Prod.fst) - (OracleComp.run_interpret_map_state - (fun q s => ProbComp.eval (S.coupledHandler pk sk cStar q s)) - (S.answerHandler pk sk cStar) tablesOf - (fun q s => S.coupledHandler_map_tablesOf pk sk cStar q s) prog st) - simp only [PMF.map_comp, Function.comp_def] at hmap - rw [hmap, OracleComp.run_interpret_simulation_fst _ (S.eagerStateHandler pk sk cStar) _ - (fun q t => S.answerHandler_sim pk sk cStar q t)] + (RandomOracle.complete (fun _ => ProbComp.eval S.pke.sampleCoins) + (RandomOracle.ofList st.gCache)).bind fun G => + (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) + (RandomOracle.ofList st.hCache)).bind fun Hraw => + (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) + (RandomOracle.ofList st.hqCache)).bind fun Hq => + OracleComp.interpret (S.eagerCoupledHandler G Hq Hraw pk sk cStar) prog := by + have himpl : S.coupledHandler pk sk cStar = fun q => + OracleComp.interpret S.lazyOracle (S.coupledProgram pk sk cStar q) := + funext (S.coupledHandler_eq_interpret pk sk cStar) + have heager (G : M → R) (Hq : CT → K) (Hraw : Bool × M × CT → K) : + S.eagerCoupledHandler G Hq Hraw pk sk cStar = fun q => + OracleComp.interpret (tableOracle G Hraw Hq) (S.coupledProgram pk sk cStar q) := + funext (S.eagerCoupledHandler_eq_interpret G Hq Hraw pk sk cStar) + simp only [heager, himpl, ← OracleComp.interpret_interpret] + rw [RandomOracle.eval_run_interpret_eq_complete_bind S.coupledDistribution S.lazyOracle + coupledCache S.eval_lazyOracle_map_coupledCache, + ← RandomOracle.bind_complete_sum, ← RandomOracle.bind_complete_sum] congr 1 with T : 1 - have hro := congrArg (PMF.map Prod.fst) - (OracleComp.run_interpret_readOnly - (fun q T' => S.eagerCoupledHandler T'.1 T'.2.2 T'.2.1 pk sk cStar q) prog T) - change ((OracleComp.interpret (S.eagerStateHandler pk sk cStar) prog).run T).map Prod.fst = - ((OracleComp.interpret (fun q => S.eagerCoupledHandler T.1 T.2.2 T.2.1 pk sk cStar q) - prog).map (fun a => (a, T))).map Prod.fst at hro - rw [PMF.map_comp] at hro - exact hro.trans (PMF.map_id _) - -end Complete + congr 1 with q : 1 + rcases q with (m | x) | c <;> rfl /-- **Lazy/eager equivalence of the coupled game**: the lazy coupled game `S.coupledGame A b` has the output distribution of the eager coupled game `S.eagerCoupledGame A b`. -/ @@ -290,8 +248,7 @@ theorem eval_coupledGame_eq_eagerCoupledGame [Fintype M] [Finite R] [Fintype CT] rw [PMF.bind_comm] simp only [← hP] simp only [eval_run_coupledHandler_eq_eager] - simp only [completeTables, tablesOf, - RandomOracle.complete_ofList_singleton, RandomOracle.ofList_nil, + simp only [RandomOracle.complete_ofList_singleton, RandomOracle.ofList_nil, RandomOracle.complete_empty, OracleComp.interpret_bind, OracleComp.interpret_query, eagerCoupledHandler, pmf_norm, ROQuery.dispatch] pmf_swap diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean index 94c5561f01..215356cf2c 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Pushforward.lean @@ -8,7 +8,6 @@ module -- upstream: candidate public import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Hybrids -import CslibFrontier.Probability.PMF.Map import CslibFrontier.Probability.Product.Split import CslibFrontier.Tactic.PMFSwap @@ -54,23 +53,6 @@ open scoped ENNReal NNReal variable {PK SK M R CT K : Type} -/-- An independent product over a sum of index types is the product of the two independent -products over the summands. -/ -private theorem bind_pi_sum {I J γ β : Type} [Fintype I] [Fintype J] [Finite γ] (μ : PMF γ) - (F : (I → γ) → (J → γ) → PMF β) : - (pi fun _ : I ⊕ J => μ).bind (fun f => F (fun i => f (Sum.inl i)) fun j => f (Sum.inr j)) = - (pi fun _ : I => μ).bind fun a => (pi fun _ : J => μ).bind fun b => F a b := by - have hsplit : - (pi fun _ : I ⊕ J => μ).map (Equiv.sumArrowEquivProdArrow I J γ) = - (pi fun _ : I => μ).bind fun a => (pi fun _ : J => μ).map (a, ·) := by - ext ⟨a, b⟩ - rw [map_equiv_apply, bind_map_prodMk_apply, pi_apply, pi_apply, pi_apply, - Fintype.prod_sum_type] - rfl - have h := congrArg (fun p => p.bind fun ab => F ab.1 ab.2) hsplit - simp only [PMF.bind_bind, PMF.map, PMF.pure_bind, Function.comp_def] at h - exact h - namespace Scheme variable [DecidableEq M] [DecidableEq CT] @@ -118,13 +100,13 @@ private theorem bind_pi_reindex [Fintype M] [Fintype CT] [Finite K] (pi fun _ : CT ⊕ ((Bool × M × CT) ⊕ CT) => μ).bind fun f => F (S.coupledH G (fun c => f (.inl c)) (fun x => f (.inr (.inl x))) pk sk) (S.coupledFb G (fun c => f (.inl c)) (fun c => f (.inr (.inr c))) pk sk) := by - refine Eq.symm ((bind_pi_sum μ fun (Hq : CT → K) (g : (Bool × M × CT) ⊕ CT → K) => + refine Eq.symm ((bind_pi_sum (fun _ => μ) fun (Hq : CT → K) (g : (Bool × M × CT) ⊕ CT → K) => F (S.coupledH G Hq (fun x => g (Sum.inl x)) pk sk) (S.coupledFb G Hq (fun c => g (Sum.inr c)) pk sk)).trans ?_) congr 1 with Hq : 1 - exact bind_pi_sum μ fun Hraw Hfb' => + exact bind_pi_sum (fun _ => μ) fun Hraw Hfb' => F (S.coupledH G Hq Hraw pk sk) (S.coupledFb G Hq Hfb' pk sk) - rw [hR, ← bind_pi_sum μ F, + rw [hR, ← bind_pi_sum (fun _ => μ) F, ← pi_map_comp_injective (fun _ : CT ⊕ ((Bool × M × CT) ⊕ CT) => μ) (S.readIndex_injective G pk sk), PMF.bind_map] diff --git a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean index 7291eb7212..cb29a66004 100644 --- a/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean +++ b/CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean @@ -10,6 +10,8 @@ module public import CslibFrontier.Crypto.Computational.Encryption.KEM public import CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto public import CslibFrontier.Languages.Probabilistic.RandomOracle +import CslibFrontier.Languages.Probabilistic.RandomOracle.Probabilistic +import CslibFrontier.Languages.Probabilistic.RandomOracle.Represented import CslibFrontier.Languages.Probabilistic.Simulation import CslibFrontier.Probability.PMF.Union import CslibFrontier.Tactic.PMFSwap @@ -64,8 +66,9 @@ On top of those definitions, this module formalizes: 1. **Executable lazy and eager ROM IND-CCA games for `FO-notbot`** (`Scheme.roIndCcaGame`, `Scheme.indCcaRomAdvantage`, `Scheme.eagerIndCcaGame`) over the oracle interface `ROQuery M CT` (`G`, `H`, and punctured decapsulation rejecting `c = cStar` with `none`), - together with exact eager/lazy equivalence (`Scheme.eval_roIndCcaGame_eq_h1`, - `Scheme.eval_roIndCcaGame_eq_eagerIndCcaGame`, `Scheme.indCcaRomAdvantage_eq_eager`). + together with exact eager/lazy equivalence (`Scheme.eval_roIndCcaGame_eq_eagerIndCcaGame`, + `Scheme.indCcaRomAdvantage_eq_eager`), obtained from the generic represented-cache law + `RandomOracle.eval_run_interpret_eq_complete_bind`. 2. **Keyless ROM simulator and IND-CPA reduction** (`Scheme.simHandler`, `Scheme.romCpaReduction`, `Scheme.simHitProb`). Neither is a verbatim construction from [HofheinzHoevelmannsKiltz2017]; both are adapted to domain-separated `FO-notbot`. `simHandler` is @@ -215,6 +218,12 @@ def ROQueryBounds (A : ROAdversary PK M R CT K) (qG qH : ℕ) : Prop := OracleComp.IsCostBounded ROQuery.gCost (A ch) qG ∧ OracleComp.IsCostBounded ROQuery.hCost (A ch) qH +/-- Bounds `(qG, qH)` on `G` and `H` queries bound their combined count by `qG + qH`. -/ +theorem ROQueryBounds.ghCost {A : ROAdversary PK M R CT K} {qG qH : ℕ} (hq : ROQueryBounds A qG qH) + (ch : KEM.Challenge PK CT K) : OracleComp.IsCostBounded ROQuery.ghCost (A ch) (qG + qH) := by + convert OracleComp.IsCostBounded.add (hq ch).1 (hq ch).2 using 1 + funext q; cases q <;> rfl + /-- An `ROAdversary` satisfies full query bounds `(qG, qH, qD)` if on every challenge `ch` its execution makes at most `qG` queries to `G`, `qH` queries to `H`, and `qD` queries to `decaps`. -/ def ROFullQueryBounds (A : ROAdversary PK M R CT K) (qG qH qD : ℕ) : Prop := @@ -423,370 +432,127 @@ def romCpaReduction (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) (m0 m1 : M) : PK → CT → ProbComp Bool := S.romFilterCpaReduction (fun _ _ _ => true) A m0 m1 -/-! ### Section 2: Connecting `memoize` to `RandomOracle.answer` and Eager/Lazy Equivalence -/ - -/-- Hybrid 1 handler: `G : M → R` is a complete table while `H : Bool × M × CT → K` is answered -lazily via `RandomOracle.answer`. Decapsulation queries `c ≠ cStar` run `decapsNotBotM` against -these oracles. -/ -noncomputable def h1Handler (S : Scheme PK SK M R CT K) - (G : M → R) (dk : ImplicitSK PK SK M) (cStar : CT) - (q : ROQuery M CT) : - StateT (RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := - let H := RandomOracle.answer (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey) - ROQuery.dispatch (pure ∘ G) H (fun c => some <$> S.decapsNotBotM (pure ∘ G) H dk c) cStar q - -/-- A decapsulation query `c ≠ cStar` to `h1Handler`: the `T` check runs on the table `G`, and -only the key derivation touches the `H` table. -/ -private theorem h1Handler_decaps (S : Scheme PK SK M R CT K) (G : M → R) - (dk : ImplicitSK PK SK M) {cStar c : CT} (hc : c ≠ cStar) - (tH : RandomOracle.Cache (fun _ : Bool × M × CT => K)) : - S.h1Handler G dk cStar (.decaps c) tH = - match decT S.pke G dk.pk dk.sk c with - | some m' => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m', c) tH).map - (fun (k, tH') => (some k, tH')) - | none => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, tH')) := by - simp only [h1Handler, ROQuery.dispatch, hc, ↓reduceIte, decapsNotBotM, decTM_pure, pure_bind] - cases decT S.pke G dk.pk dk.sk c <;> rfl - -/-- Intermediate handler answering `G` and `H` via `RandomOracle.answer` on a pair of -`RandomOracle.Cache` tables `(tG, tH)`. Decapsulation queries `c ≠ cStar` run `decapsNotBotM` -against these table oracles. -/ -noncomputable def roAnswerHandler (S : Scheme PK SK M R CT K) - (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : - StateT (RandomOracle.Cache (fun _ : M => R) × - RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := - let G : M → StateT (RandomOracle.Cache (fun _ : M => R) × - RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF R := fun m (tG, tH) => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m tG).map - (fun (r, tG') => (r, (tG', tH))) - let H : Bool × M × CT → StateT (RandomOracle.Cache (fun _ : M => R) × - RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF K := fun x (tG, tH) => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) x tH).map - (fun (k, tH') => (k, (tG, tH'))) - ROQuery.dispatch G H (fun c => some <$> S.decapsNotBotM G H dk c) cStar q - -/-- A decapsulation query `c ≠ cStar` to `roAnswerHandler` as explicit table operations. -/ -private theorem roAnswerHandler_decaps (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) - {cStar c : CT} (hc : c ≠ cStar) (tG : RandomOracle.Cache (fun _ : M => R)) - (tH : RandomOracle.Cache (fun _ : Bool × M × CT => K)) : - S.roAnswerHandler dk cStar (.decaps c) (tG, tH) = - match S.pke.dec dk.sk c with - | none => - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, (tG, tH'))) - | some m' => - (RandomOracle.answer (fun _ => ProbComp.eval S.pke.sampleCoins) m' tG).bind fun (r', tG') => - if S.pke.encDet dk.pk m' r' = c then - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (false, m', c) tH).map - (fun (k, tH') => (some k, (tG', tH'))) - else - (RandomOracle.answer (fun _ => ProbComp.eval S.sampleKey) (true, dk.fallback, c) tH).map - (fun (k, tH') => (some k, (tG', tH'))) := by - simp only [roAnswerHandler, ROQuery.dispatch, hc, ↓reduceIte, decapsNotBotM_eq] +/-! ### Section 2: Eager/Lazy Equivalence + +The lazy oracles `G` and `H` of `roHandler` are one lazy random oracle on the joined interface +`ROSpec`, kept in `ROCache` and read through `RandomOracle.ofList`. Every query of the game is the +oracle program `roProgram` over that interface, so +`RandomOracle.eval_run_interpret_eq_complete_bind` replaces both caches by complete tables in one +step, and `RandomOracle.bind_complete_sum` splits the completed table into `G` and `H`. -/ + +/-- The random oracles `G : M → R` and `H : Bool × M × CT → K` as one interface. -/ +private abbrev ROSpec (M R CT K : Type) := (fun _ : M => R) ⊕ₒ (fun _ : Bool × M × CT => K) + +/-- Each query of the ROM IND-CCA game as a program over the random oracles: `G` and `H` are +forwarded, and decapsulation runs `decapsNotBotM` against them. -/ +private def roProgram (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) : + (q : ROQuery M CT) → OracleComp (ROSpec M R CT K) (ROResp R K q) := + ROQuery.dispatch (fun m => OracleComp.query (spec := ROSpec M R CT K) (Sum.inl m)) + (fun x => OracleComp.query (spec := ROSpec M R CT K) (Sum.inr x)) + (fun c => some <$> S.decapsNotBotM + (fun m => OracleComp.query (spec := ROSpec M R CT K) (Sum.inl m)) + (fun x => OracleComp.query (spec := ROSpec M R CT K) (Sum.inr x)) dk c) cStar + +omit [DecidableEq M] in +/-- Interpreting `decapsNotBotM` against oracle calls runs it against the interpreted calls. -/ +private theorem interpret_decapsNotBotM {ι : Type} {spec : OracleSpec ι} {n : Type → Type} + [Monad n] [LawfulMonad n] (oracle : (q : ι) → n (spec q)) (S : Scheme PK SK M R CT K) + (G : M → OracleComp spec R) (H : Bool × M × CT → OracleComp spec K) + (dk : ImplicitSK PK SK M) (c : CT) : + OracleComp.interpret oracle (S.decapsNotBotM G H dk c) = + S.decapsNotBotM (fun m => OracleComp.interpret oracle (G m)) + (fun x => OracleComp.interpret oracle (H x)) dk c := by + rw [decapsNotBotM_eq, decapsNotBotM_eq] cases S.pke.dec dk.sk c with - | none => - dsimp only - simp only [Functor.map, StateT.map, Probability.PMF.monad_bind_eq_bind, - Probability.PMF.monad_pure_eq_pure, PMF.bind_map] - rfl + | none => rfl | some m' => - dsimp only - simp only [Functor.map, StateT.map, bind, StateT.bind, Probability.PMF.monad_pure_eq_pure, - PMF.bind_map, PMF.bind_bind] - congr 1 with a : 1 - dsimp only [Function.comp_apply] - split_ifs <;> rw [PMF.bind_map] <;> rfl - -/-- Evaluating `(some <$> roHashH sampleKey x) st` and mapping `ROCache` via -`RandomOracle.ofList`. -/ -private theorem eval_roHashH_some_map_ofList (sampleKey : ProbComp K) (x : Bool × M × CT) - (st : ROCache M R CT K) : - (ProbComp.eval ((some <$> roHashH sampleKey x) st)).map - (fun (res, st') => - (res, (RandomOracle.ofList st'.gCache, RandomOracle.ofList st'.hCache))) = - (RandomOracle.answer (fun _ => ProbComp.eval sampleKey) x - (RandomOracle.ofList st.hCache)).map - (fun (k, tH') => (some k, (RandomOracle.ofList st.gCache, tH'))) := by - dsimp only [roHashH] - change (ProbComp.eval - (RandomOracle.zoomStateT ROCache.hCache (fun st c => { st with hCache := c }) - (memoize (fun _ => sampleKey) x) st >>= fun a => pure (some a.fst, a.snd))).map _ = _ - simp only [ProbComp.eval_bind, RandomOracle.eval_zoomStateT_memoize, ProbComp.eval_pure, - PMF.map_bind, PMF.bind_map, PMF.pure_map, Function.comp_def] - have h := congrArg (PMF.map (fun (k, tH') => (some k, (RandomOracle.ofList st.gCache, tH')))) - (RandomOracle.memoize_map_ofList (fun _ => ProbComp.eval sampleKey) x st.hCache) - simpa only [PMF.map_comp, Function.comp_def, PMF.map, PMF.bind_bind, PMF.pure_bind] using h - -/-- Mapping `ROCache` via `RandomOracle.ofList` turns `S.roHandler` into `S.roAnswerHandler`. -/ -theorem roHandler_map_ofList (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) - (q : ROQuery M CT) (st : ROCache M R CT K) : - (ProbComp.eval (S.roHandler dk cStar q st)).map - (fun (res, st') => - (res, (RandomOracle.ofList st'.gCache, RandomOracle.ofList st'.hCache))) = - S.roAnswerHandler dk cStar q - (RandomOracle.ofList st.gCache, RandomOracle.ofList st.hCache) := by - let dG : M → PMF R := fun _ => ProbComp.eval S.pke.sampleCoins - let dH : Bool × M × CT → PMF K := fun _ => ProbComp.eval S.sampleKey + simp only [OracleComp.interpret_bind] + congr 1 with r' + split_ifs <;> rfl + +/-- `roHandler` interprets `roProgram` with the lazy oracles `roHashG` and `roHashH`. -/ +private theorem roHandler_eq_interpret (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) + (cStar : CT) (q : ROQuery M CT) : + S.roHandler dk cStar q = OracleComp.interpret + (OracleComp.sumHandler (roHashG S.pke.sampleCoins) (roHashH S.sampleKey)) + (S.roProgram dk cStar q) := by cases q with - | G m => - dsimp only [roHandler, roHashG, roAnswerHandler, ROQuery.dispatch] - rw [RandomOracle.eval_zoomStateT_memoize] - have h := congrArg (PMF.map (fun (r, tG') => (r, (tG', RandomOracle.ofList st.hCache)))) - (RandomOracle.memoize_map_ofList dG m st.gCache) - simpa only [PMF.map_comp, Function.comp_def, dG] using h - | H x => - dsimp only [roHandler, roHashH, roAnswerHandler, ROQuery.dispatch] - rw [RandomOracle.eval_zoomStateT_memoize] - have h := congrArg (PMF.map (fun (k, tH') => (k, (RandomOracle.ofList st.gCache, tH')))) - (RandomOracle.memoize_map_ofList dH x st.hCache) - simpa only [PMF.map_comp, Function.comp_def, dH] using h + | G m => exact (OracleComp.interpret_query _ _).symm + | H x => exact (OracleComp.interpret_query _ _).symm | decaps c => by_cases hc : c = cStar - · dsimp only [roHandler, roAnswerHandler, ROQuery.dispatch] - simp only [hc, ↓reduceIte] - exact PMF.pure_map _ _ - · rw [roAnswerHandler_decaps S dk hc] - dsimp only [roHandler, ROQuery.dispatch] - simp only [hc, ↓reduceIte, decapsNotBotM_eq] - cases hdec : S.pke.dec dk.sk c with - | none => exact eval_roHashH_some_map_ofList S.sampleKey (true, dk.fallback, c) st - | some m' => - dsimp only - rw [map_bind] - change (ProbComp.eval (roHashG S.pke.sampleCoins m' st >>= fun a => - (some <$> if S.pke.encDet dk.pk m' a.fst = c then roHashH S.sampleKey (false, m', c) - else roHashH S.sampleKey (true, dk.fallback, c)) a.snd)).map _ = _ - dsimp only [roHashG] - simp only [ProbComp.eval_bind, RandomOracle.eval_zoomStateT_memoize, PMF.map_bind, - PMF.bind_map, Function.comp_def] - rw [← RandomOracle.memoize_map_ofList dG m' st.gCache, PMF.bind_map] - congr 1; funext ⟨r', gC'⟩ - dsimp only [Function.comp_def] - by_cases henc : S.pke.encDet dk.pk m' r' = c <;> - simp only [henc, ↓reduceIte] <;> - exact eval_roHashH_some_map_ofList S.sampleKey _ { st with gCache := gC' } - -/-- Single-step simulation from `S.roAnswerHandler` to `S.h1Handler` via -`RandomOracle.answer_complete`. -/ -theorem roAnswerHandler_sim_h1 [Fintype M] [Finite R] - (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) - (q : ROQuery M CT) - (st : RandomOracle.Cache (fun _ : M => R) × RandomOracle.Cache (fun _ : Bool × M × CT => K)) : - (do - let (res, st') ← S.roAnswerHandler dk cStar q st - let G ← RandomOracle.complete (fun _ : M => ProbComp.eval S.pke.sampleCoins) st'.fst - PMF.pure (res, (G, st'.snd))) = - (do - let G ← RandomOracle.complete (fun _ : M => ProbComp.eval S.pke.sampleCoins) st.fst - let (res, tH') ← S.h1Handler G dk cStar q st.snd - PMF.pure (res, (G, tH'))) := by - rcases st with ⟨tG, tH⟩ - let dG : M → PMF R := fun _ => ProbComp.eval S.pke.sampleCoins - let dH : Bool × M × CT → PMF K := fun _ => ProbComp.eval S.sampleKey - simp only [Probability.PMF.monad_bind_eq_bind] + · simp [roHandler, roProgram, hc] + · simp only [roHandler, roProgram, ROQuery.dispatch, hc, ↓reduceIte, + OracleComp.interpret_map, interpret_decapsNotBotM, OracleComp.interpret_query] + +omit [DecidableEq M] in +/-- `eagerHandler` interprets `roProgram` with the complete tables `G` and `H`. -/ +private theorem eagerHandler_eq_interpret (S : Scheme PK SK M R CT K) (G : M → R) + (H : Bool × M × CT → K) (dk : ImplicitSK PK SK M) (cStar : CT) (q : ROQuery M CT) : + S.eagerHandler G H dk cStar q = OracleComp.interpret + (OracleComp.sumHandler (fun m => PMF.pure (G m)) (fun x => PMF.pure (H x))) + (S.roProgram dk cStar q) := by cases q with - | G m => - dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch, Function.comp_apply, pure, - StateT.pure] - have hc := congrArg (PMF.map (fun (r, G) => (r, (G, tH)))) - (RandomOracle.answer_complete dG tG m) - simpa only [Probability.PMF.monad_bind_eq_bind, PMF.bind_map, PMF.map_bind, PMF.map_comp, - Function.comp_def, PMF.bind_bind, PMF.pure_bind, PMF.map, dG] using hc - | H x => - dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] - rw [PMF.bind_map, PMF.bind_comm] - rfl + | G m => exact (OracleComp.interpret_query _ _).symm + | H x => exact (OracleComp.interpret_query _ _).symm | decaps c => by_cases hc : c = cStar - · dsimp only [roAnswerHandler, h1Handler, ROQuery.dispatch] - simp [hc, pure, StateT.pure] - · rw [roAnswerHandler_decaps S dk hc] - simp only [h1Handler_decaps S _ dk hc] - cases hdec : S.pke.dec dk.sk c with - | none => - simp only [decT_eq, hdec, Function.comp_def, PMF.map, PMF.bind_bind, PMF.pure_bind] - exact PMF.bind_comm _ _ _ - | some m' => - simp only [decT_eq, encT, hdec, PMF.bind_bind] - trans (((RandomOracle.answer dG m' tG).bind (fun (r', tG') => - (RandomOracle.complete dG tG').map (fun G => (r', G)))).bind (fun (r', G) => - if S.pke.encDet dk.pk m' r' = c then - (RandomOracle.answer dH (false, m', c) tH).bind - fun (k, tH') => PMF.pure (some k, (G, tH')) - else - (RandomOracle.answer dH (true, dk.fallback, c) tH).bind - fun (k, tH') => PMF.pure (some k, (G, tH')))) - · simp only [PMF.bind_bind] - congr 1; funext ⟨r', tG'⟩ - by_cases henc : S.pke.encDet dk.pk m' r' = c <;> - simp only [henc, ↓reduceIte, PMF.bind_map, Function.comp_def] <;> - exact PMF.bind_comm _ _ _ - · have hG := RandomOracle.answer_complete dG tG m' - simp only [Probability.PMF.monad_bind_eq_bind] at hG - rw [hG, PMF.bind_map] - congr 1 with G : 1 - dsimp only [Function.comp_def] - by_cases henc : S.pke.encDet dk.pk m' (G m') = c <;> - simp [henc, dH, PMF.map] - -/-- Running `A` under `S.roHandler` from `st` has the same output distribution as completing -`st.gCache` via `RandomOracle.complete` and running `A` under `S.h1Handler`. -/ -theorem eval_run_roHandler_eq_h1 [Fintype M] [Finite R] - (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) - (prog : OracleComp (ROResp (M := M) (CT := CT) R K) Bool) (st : ROCache M R CT K) : + · simp [eagerHandler, roProgram, hc] + · simp only [eagerHandler, roProgram, ROQuery.dispatch, hc, ↓reduceIte, + OracleComp.interpret_map, interpret_decapsNotBotM, OracleComp.interpret_query] + change PMF.pure _ = some <$> S.decapsNotBotM (m := PMF) (pure ∘ G) (pure ∘ H) dk c + rw [decapsNotBotM_pure, map_pure] + rfl + +/-- The joined cache represented by the two list caches of an `ROCache`. -/ +private abbrev roTables (st : ROCache M R CT K) : RandomOracle.Cache (ROSpec M R CT K) := + RandomOracle.Cache.sum (RandomOracle.ofList st.gCache) (RandomOracle.ofList st.hCache) + +/-- Each lazy oracle call, read on the joined cache, is a lazy call of the joined oracle. -/ +private theorem eval_roHash_map_roTables (S : Scheme PK SK M R CT K) + (q : M ⊕ (Bool × M × CT)) (st : ROCache M R CT K) : + (ProbComp.eval (OracleComp.sumHandler (roHashG S.pke.sampleCoins) (roHashH S.sampleKey) q + st)).map (fun z => (z.1, roTables z.2)) = + RandomOracle.answer (OracleComp.sumHandler (fun _ => ProbComp.eval S.pke.sampleCoins) + (fun _ => ProbComp.eval S.sampleKey)) q (roTables st) := by + rcases q with m | x + · rw [roTables, RandomOracle.answer_sum_inl] + exact RandomOracle.eval_zoom_memoize_map ROCache.gCache (fun st c => { st with gCache := c }) + S.pke.sampleCoins m st (fun r => r) roTables + (fun c => RandomOracle.Cache.sum c (RandomOracle.ofList st.hCache)) (fun _ => rfl) + · rw [roTables, RandomOracle.answer_sum_inr] + exact RandomOracle.eval_zoom_memoize_map ROCache.hCache (fun st c => { st with hCache := c }) + S.sampleKey x st (fun k => k) roTables + (fun c => RandomOracle.Cache.sum (RandomOracle.ofList st.gCache) c) (fun _ => rfl) + +/-- Running a program under `roHandler` has the law of completing both caches into tables `G` and +`H` and running it under `eagerHandler`. -/ +private theorem eval_run_roHandler_eq_eager [Fintype M] [Finite R] [Fintype CT] [Finite K] + (S : Scheme PK SK M R CT K) (dk : ImplicitSK PK SK M) (cStar : CT) {α : Type} + (prog : OracleComp (ROResp (M := M) (CT := CT) R K) α) (st : ROCache M R CT K) : (ProbComp.eval ((OracleComp.interpret (S.roHandler dk cStar) prog).run st)).map Prod.fst = - (RandomOracle.complete (fun _ : M => ProbComp.eval S.pke.sampleCoins) - (RandomOracle.ofList st.gCache)).bind (fun G => - ((OracleComp.interpret (S.h1Handler G dk cStar) prog).run - (RandomOracle.ofList st.hCache)).map Prod.fst) := by - rw [OracleComp.map_run_interpret ProbComp.isMonadHom_eval] - have hmap := congrArg (PMF.map Prod.fst) - (OracleComp.run_interpret_map_state - (fun q s => ProbComp.eval (S.roHandler dk cStar q s)) - (S.roAnswerHandler dk cStar) - (fun s => (RandomOracle.ofList s.gCache, RandomOracle.ofList s.hCache)) - (fun q s => S.roHandler_map_ofList dk cStar q s) prog st) - simp only [PMF.map_comp, Function.comp_def] at hmap - rw [hmap] - let sim : (RandomOracle.Cache (fun _ : M => R) × - RandomOracle.Cache (fun _ : Bool × M × CT => K)) → - PMF ((M → R) × RandomOracle.Cache (fun _ : Bool × M × CT => K)) := - fun (tG, tH) => (RandomOracle.complete (fun _ : M => ProbComp.eval S.pke.sampleCoins) tG).map - (fun G => (G, tH)) - let h₂ : (q : ROQuery M CT) → - StateT ((M → R) × RandomOracle.Cache (fun _ : Bool × M × CT => K)) PMF (ROResp R K q) := - fun q (G, tH) => (S.h1Handler G dk cStar q tH).map (fun (res, tH') => (res, (G, tH'))) - have hsim := OracleComp.run_interpret_simulation_fst - (S.roAnswerHandler dk cStar) h₂ sim - (fun q s => by - simpa only [sim, h₂, Probability.PMF.monad_bind_eq_bind, PMF.bind_map, PMF.map_bind, - Function.comp_def, PMF.map, PMF.bind_bind, PMF.pure_bind] using - S.roAnswerHandler_sim_h1 dk cStar q s) - prog (RandomOracle.ofList st.gCache, RandomOracle.ofList st.hCache) - simp only [sim, PMF.bind_map, Function.comp_def] at hsim - rw [hsim] - congr 1 with G : 1 - have hproj := congrArg (PMF.map Prod.fst) - (OracleComp.run_interpret_map_state - (S.h1Handler G dk cStar) h₂ (fun tH => (G, tH)) - (fun q tH => rfl) prog (RandomOracle.ofList st.hCache)) - simpa only [PMF.map_comp, Function.comp_def] using hproj.symm - -/-- Evaluating `S.roIndCcaGame A b` equals sampling `G` eagerly from `pi` and running `A` -against `S.h1Handler`. -/ -theorem eval_roIndCcaGame_eq_h1 [Fintype M] [Finite R] - (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) (b : Bool) : - ProbComp.eval (S.roIndCcaGame A b) = - (ProbComp.eval S.keyGenNotBot).bind (fun (pk, dk) => - (ProbComp.eval S.sampleMsg).bind (fun mStar => - (pi (α := fun _ : M => R) (fun _ => ProbComp.eval S.pke.sampleCoins)).bind (fun G => - let cStar := S.pke.encDet pk mStar (G mStar) - (ProbComp.eval S.sampleKey).bind (fun kReal => - (ProbComp.eval S.sampleKey).bind (fun kRand => - let kStar := if b then kRand else kReal - let tH₀ := RandomOracle.ofList [((false, mStar, cStar), kReal)] - ((OracleComp.interpret (S.h1Handler G dk cStar) (A ⟨pk, cStar, kStar⟩)).run tH₀).map - Prod.fst))))) := by - dsimp only [roIndCcaGame] - simp only [ProbComp.eval_bind, ProbComp.eval_pure] - congr 1; funext ⟨pk, dk⟩ - congr 1 with mStar : 1 - let dG : M → PMF R := fun _ => ProbComp.eval S.pke.sampleCoins - have hstep (rStar : R) (kReal kRand : K) : - (ProbComp.eval ((OracleComp.interpret (S.roHandler dk (S.pke.encDet pk mStar rStar)) - (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)).run - { gCache := [(mStar, rStar)], - hCache := [((false, mStar, S.pke.encDet pk mStar rStar), kReal)] })).bind - (fun x => PMF.pure x.fst) = - (pi (Function.update dG mStar (PMF.pure rStar))).bind (fun G => - ((OracleComp.interpret (S.h1Handler G dk (S.pke.encDet pk mStar rStar)) - (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)).run - (RandomOracle.ofList [((false, mStar, S.pke.encDet pk mStar rStar), kReal)])).map - Prod.fst) := by - change (ProbComp.eval _).map Prod.fst = _ - rw [eval_run_roHandler_eq_h1, RandomOracle.complete_ofList_singleton] - simp only [hstep] - rw [← bind_pi_update dG mStar (fun rStar G => - let cStar := S.pke.encDet pk mStar rStar - (ProbComp.eval S.sampleKey).bind fun kReal => - (ProbComp.eval S.sampleKey).bind fun kRand => - ((OracleComp.interpret (S.h1Handler G dk cStar) - (A ⟨pk, cStar, if b then kRand else kReal⟩)).run - (RandomOracle.ofList [((false, mStar, cStar), kReal)])).map Prod.fst)] - congr 1 with rStar : 1 - pmf_swap - -/-- Single-step simulation from `S.h1Handler` to `S.eagerHandler` via -`RandomOracle.answer_complete`. -/ -theorem h1Handler_sim_eager [Fintype M] [Fintype CT] [Finite K] - (S : Scheme PK SK M R CT K) (G : M → R) (dk : ImplicitSK PK SK M) (cStar : CT) - (q : ROQuery M CT) (tH : RandomOracle.Cache (fun _ : Bool × M × CT => K)) : - (do - let (res, tH') ← S.h1Handler G dk cStar q tH - let H ← RandomOracle.complete (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey) tH' - PMF.pure (res, H)) = - (do - let H ← RandomOracle.complete (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey) tH - let res ← S.eagerHandler G H dk cStar q - PMF.pure (res, H)) := by - let dH : Bool × M × CT → PMF K := fun _ => ProbComp.eval S.sampleKey - simp only [Probability.PMF.monad_bind_eq_bind] - cases q with - | G m => - simp [h1Handler, eagerHandler, pure, StateT.pure] - | H x => - dsimp only [h1Handler, eagerHandler, ROQuery.dispatch] - simpa only [Probability.PMF.monad_bind_eq_bind, PMF.pure_bind, PMF.map, Function.comp_def, dH] - using RandomOracle.answer_complete dH tH x - | decaps c => - by_cases hc : c = cStar - · dsimp only [h1Handler, eagerHandler, ROQuery.dispatch] - simp [hc, pure, StateT.pure] - · rw [h1Handler_decaps S G dk hc] - dsimp only [eagerHandler, decapsNotBot_eq, ROQuery.dispatch] - simp only [hc, ↓reduceIte] - cases hdec : decT S.pke G dk.pk dk.sk c with - | none => - have hH := congrArg (PMF.map (fun (k, H) => (some k, H))) - (RandomOracle.answer_complete dH tH (true, dk.fallback, c)) - simpa only [Probability.PMF.monad_bind_eq_bind, PMF.bind_map, PMF.map_bind, PMF.map_comp, - Function.comp_def, PMF.bind_bind, PMF.pure_bind, PMF.map, dH] using hH - | some m' => - have hH := congrArg (PMF.map (fun (k, H) => (some k, H))) - (RandomOracle.answer_complete dH tH (false, m', c)) - simpa only [Probability.PMF.monad_bind_eq_bind, PMF.bind_map, PMF.map_bind, PMF.map_comp, - Function.comp_def, PMF.bind_bind, PMF.pure_bind, PMF.map, dH] using hH - -/-- Running `prog` under `S.h1Handler G dk cStar` from `tH` has the same output distribution as -completing `tH` via `RandomOracle.complete` and interpreting `prog` under `S.eagerHandler`. -/ -theorem eval_run_h1Handler_eq_eager [Fintype M] [Fintype CT] [Finite K] - (S : Scheme PK SK M R CT K) (G : M → R) (dk : ImplicitSK PK SK M) (cStar : CT) - (prog : OracleComp (ROResp (M := M) (CT := CT) R K) Bool) - (tH : RandomOracle.Cache (fun _ : Bool × M × CT => K)) : - ((OracleComp.interpret (S.h1Handler G dk cStar) prog).run tH).map Prod.fst = - (RandomOracle.complete (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey) tH).bind (fun H => - OracleComp.interpret (S.eagerHandler G H dk cStar) prog) := by - let h₂ : (q : ROQuery M CT) → StateT (Bool × M × CT → K) PMF (ROResp R K q) := - fun q H => (S.eagerHandler G H dk cStar q).map (fun res => (res, H)) - have hsim := OracleComp.run_interpret_simulation_fst - (S.h1Handler G dk cStar) h₂ - (RandomOracle.complete (fun _ : Bool × M × CT => ProbComp.eval S.sampleKey)) - (fun q s => by - simpa only [h₂, Probability.PMF.monad_bind_eq_bind, PMF.map, Function.comp_def] using - S.h1Handler_sim_eager G dk cStar q s) - prog tH - rw [hsim] - congr 1 with H : 1 - have hro := congrArg (PMF.map Prod.fst) - (OracleComp.run_interpret_readOnly (fun q H' => S.eagerHandler G H' dk cStar q) prog H) - change ((OracleComp.interpret h₂ prog).run H).map Prod.fst = - ((OracleComp.interpret (fun q => S.eagerHandler G H dk cStar q) prog).map - (fun a => (a, H))).map Prod.fst at hro - rw [PMF.map_comp] at hro - exact hro.trans (PMF.map_id _) + (RandomOracle.complete (fun _ => ProbComp.eval S.pke.sampleCoins) + (RandomOracle.ofList st.gCache)).bind fun G => + (RandomOracle.complete (fun _ => ProbComp.eval S.sampleKey) + (RandomOracle.ofList st.hCache)).bind fun H => + OracleComp.interpret (S.eagerHandler G H dk cStar) prog := by + have himpl : S.roHandler dk cStar = fun q => OracleComp.interpret + (OracleComp.sumHandler (roHashG S.pke.sampleCoins) (roHashH S.sampleKey)) + (S.roProgram dk cStar q) := funext (S.roHandler_eq_interpret dk cStar) + have heager (G : M → R) (H : Bool × M × CT → K) : S.eagerHandler G H dk cStar = fun q => + OracleComp.interpret + (OracleComp.sumHandler (fun m => PMF.pure (G m)) (fun x => PMF.pure (H x))) + (S.roProgram dk cStar q) := funext (S.eagerHandler_eq_interpret G H dk cStar) + simp only [heager, himpl, ← OracleComp.interpret_interpret] + rw [RandomOracle.eval_run_interpret_eq_complete_bind + (OracleComp.sumHandler (fun _ => ProbComp.eval S.pke.sampleCoins) + (fun _ => ProbComp.eval S.sampleKey)) + (OracleComp.sumHandler (roHashG S.pke.sampleCoins) (roHashH S.sampleKey)) roTables + S.eval_roHash_map_roTables, + ← RandomOracle.bind_complete_sum] + congr 1 with T : 1 + congr 1 with q : 1 + rcases q with m | x <;> rfl /-- **Eager/lazy equivalence for the `FO-notbot` ROM IND-CCA game**: evaluating the executable lazy-cached game `S.roIndCcaGame A b` yields the exact same output distribution as the eager @@ -794,26 +560,41 @@ random-oracle game `S.eagerIndCcaGame A b`. -/ theorem eval_roIndCcaGame_eq_eagerIndCcaGame [Fintype M] [Finite R] [Fintype CT] [Finite K] (S : Scheme PK SK M R CT K) (A : ROAdversary PK M R CT K) (b : Bool) : ProbComp.eval (S.roIndCcaGame A b) = S.eagerIndCcaGame A b := by - rw [eval_roIndCcaGame_eq_h1] + let dG : M → PMF R := fun _ => ProbComp.eval S.pke.sampleCoins + let dH : Bool × M × CT → PMF K := fun _ => ProbComp.eval S.sampleKey + dsimp only [roIndCcaGame] simp only [eagerIndCcaGame, encapsNotBot, encapsBot, encT, Probability.PMF.monad_bind_eq_bind, ProbComp.eval_bind, ProbComp.eval_pure, PMF.bind_bind, PMF.pure_bind] congr 1; funext ⟨pk, dk⟩ - let dH : Bool × M × CT → PMF K := fun _ => ProbComp.eval S.sampleKey - pmf_swap with G mStar - set cStar := S.pke.encDet pk mStar (G mStar) - have hstep (kReal kRand : K) : - ((OracleComp.interpret (S.h1Handler G dk cStar) - (A ⟨pk, cStar, if b then kRand else kReal⟩)).run - (RandomOracle.ofList [((false, mStar, cStar), kReal)])).map Prod.fst = - (pi (Function.update dH (false, mStar, cStar) (PMF.pure kReal))).bind (fun H => - OracleComp.interpret (S.eagerHandler G H dk cStar) - (A ⟨pk, cStar, if b then kRand else kReal⟩)) := by - rw [eval_run_h1Handler_eq_eager, RandomOracle.complete_ofList_singleton] + have hstep (mStar : M) (rStar : R) (kReal kRand : K) : + (ProbComp.eval ((OracleComp.interpret (S.roHandler dk (S.pke.encDet pk mStar rStar)) + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)).run + { gCache := [(mStar, rStar)], + hCache := [((false, mStar, S.pke.encDet pk mStar rStar), kReal)] })).bind + (fun x => PMF.pure x.fst) = + (pi (Function.update dG mStar (PMF.pure rStar))).bind fun G => + (pi (Function.update dH (false, mStar, S.pke.encDet pk mStar rStar) + (PMF.pure kReal))).bind fun H => + OracleComp.interpret (S.eagerHandler G H dk (S.pke.encDet pk mStar rStar)) + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩) := by + change (ProbComp.eval _).map Prod.fst = _ + rw [eval_run_roHandler_eq_eager, RandomOracle.complete_ofList_singleton, + RandomOracle.complete_ofList_singleton] simp only [hstep] - refine Eq.trans ?_ (bind_pi_update dH (false, mStar, cStar) (fun kReal H => + pmf_hoist (ProbComp.eval S.sampleMsg) + congr 1 with mStar : 1 + rw [← bind_pi_update dG mStar fun rStar G => + (pi dH).bind fun H => (ProbComp.eval S.sampleKey).bind fun kRand => + OracleComp.interpret (S.eagerHandler G H dk (S.pke.encDet pk mStar rStar)) + (A ⟨pk, S.pke.encDet pk mStar rStar, + if b then kRand else H (false, mStar, S.pke.encDet pk mStar rStar)⟩)] + congr 1 with rStar : 1 + pmf_hoist (pi (Function.update dG mStar (PMF.pure rStar))) + congr 1 with G : 1 + rw [← bind_pi_update dH (false, mStar, S.pke.encDet pk mStar rStar) fun kReal H => (ProbComp.eval S.sampleKey).bind fun kRand => - OracleComp.interpret (S.eagerHandler G H dk cStar) - (A ⟨pk, cStar, if b then kRand else kReal⟩))) + OracleComp.interpret (S.eagerHandler G H dk (S.pke.encDet pk mStar rStar)) + (A ⟨pk, S.pke.encDet pk mStar rStar, if b then kRand else kReal⟩)] congr 1 with kReal : 1 exact PMF.bind_comm _ _ _ @@ -1035,11 +816,7 @@ theorem sum_simFilterHitProb_le [Fintype M] (S : Scheme PK SK M R CT K) obtain ⟨keys, -, rStar, -, kStar, -, hrun⟩ := hx set pk := keys.fst set cStar := S.pke.encDet pk m₀ rStar - have hcost : OracleComp.IsCostBounded ROQuery.ghCost (A ⟨pk, cStar, kStar⟩) (qG + qH) := by - have ⟨hG, hH⟩ := hq ⟨pk, cStar, kStar⟩ - convert OracleComp.IsCostBounded.add hG hH using 1 - funext q; cases q <;> rfl - have hlen := hcost.measure_le + have hlen := (hq.ghCost ⟨pk, cStar, kStar⟩).measure_le (fun q st => ProbComp.eval (S.simFilterHandler (d pk) pk cStar q st)) (fun st : SimState M R CT K => st.gCache.length) (S.simFilterHandler_gCache_length_le (d pk) pk cStar) diff --git a/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean b/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean new file mode 100644 index 0000000000..f17fcf0293 --- /dev/null +++ b/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean @@ -0,0 +1,187 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import CslibFrontier.Languages.Probabilistic.RandomOracle +public import CslibFrontier.Languages.Probabilistic.Sum +import CslibFrontier.Languages.Probabilistic.Semantics +import CslibFrontier.Languages.Probabilistic.Simulation +import CslibFrontier.Probability.Product.Split + +/-! +# Lazy random oracles behind a represented state + +A game usually keeps its random-oracle tables in its own state: association lists, several caches +in a record, or caches for several oracles at once. When each call of such a handler, read through +a map `rep` from the game state to a `RandomOracle.Cache`, is a lazy `RandomOracle.answer`, the +game has the law of completing the represented cache once and answering every call from the +completed table (`eval_run_interpret_eq_complete_bind`). The per-call hypothesis is the only +game-specific step of an eager/lazy argument. + +Several oracles are one oracle on the sum of their interfaces. `Cache.sum` joins their caches, so +that one represented state serves them all, and `bind_complete_sum` splits the completed table back +into one table per oracle. + +## Main definitions + +* `Cslib.RandomOracle.Cache.sum`: the cache of `spec₁ ⊕ₒ spec₂` built from one cache for each side. + +## Main results + +* `Cslib.RandomOracle.run_interpret_eq_complete_bind`, + `Cslib.RandomOracle.eval_run_interpret_eq_complete_bind`: a handler that is a lazy random oracle + on a represented cache has the law of the completed table. +* `Cslib.RandomOracle.answer_sum_inl`, `Cslib.RandomOracle.answer_sum_inr`: a lazy call on one side + of a joined cache is a lazy call on that side's cache. +* `Cslib.RandomOracle.bind_complete_sum`: completing a joined cache completes each side + independently. +-/ + +public section + +namespace Cslib.RandomOracle + +open Probability +open scoped Cslib + +section Represented + +variable {Query State α : Type} {Response : Query → Type} [DecidableEq Query] [Fintype Query] + [∀ q, Finite (Response q)] + +/-- A stateful handler that, read through `rep`, answers every call as the lazy random oracle +`answer distribution` has the result law of completing the represented initial cache and answering +every call from the completed table. -/ +theorem run_interpret_eq_complete_bind (distribution : (q : Query) → PMF (Response q)) + (impl : (q : Query) → StateT State PMF (Response q)) (rep : State → Cache Response) + (step : ∀ q s, (impl q s).map (fun z => (z.1, rep z.2)) = answer distribution q (rep s)) + (program : OracleComp Response α) (s : State) : + ((OracleComp.interpret impl program).run s).map Prod.fst = + (complete distribution (rep s)).bind fun table => + OracleComp.interpret (fun q => PMF.pure (table q)) program := by + have hrep := congrArg (PMF.map Prod.fst) + (OracleComp.run_interpret_map_state impl (answer distribution) rep step program s) + have hcomplete := congrArg (PMF.map Prod.fst) + (run_interpret_answer_complete distribution program (rep s)) + simp only [PMF.monad_bind_eq_bind, PMF.map_bind, PMF.map_comp, Function.comp_def] at hcomplete + change ((OracleComp.interpret (answer distribution) program).run (rep s)).bind + (fun a => (complete distribution a.2).map (Function.const _ a.1)) = + (complete distribution (rep s)).bind (fun table => + (OracleComp.interpret (fun q => PMF.pure (table q)) program).map id) at hcomplete + simp only [PMF.map_const, PMF.map_id] at hcomplete + simp only [PMF.map_comp, Function.comp_def] at hrep + rw [hrep, ← hcomplete] + rfl + +/-- A closed stateful handler that, read through `rep`, answers every call as the lazy random +oracle `answer distribution` has the result law of completing the represented initial cache and +answering every call from the completed table. -/ +theorem eval_run_interpret_eq_complete_bind (distribution : (q : Query) → PMF (Response q)) + (impl : (q : Query) → StateT State ProbComp (Response q)) (rep : State → Cache Response) + (step : ∀ q s, + (ProbComp.eval (impl q s)).map (fun z => (z.1, rep z.2)) = answer distribution q (rep s)) + (program : OracleComp Response α) (s : State) : + (ProbComp.eval ((OracleComp.interpret impl program).run s)).map Prod.fst = + (complete distribution (rep s)).bind fun table => + OracleComp.interpret (fun q => PMF.pure (table q)) program := by + rw [OracleComp.map_run_interpret ProbComp.isMonadHom_eval] + exact run_interpret_eq_complete_bind distribution _ rep step program s + +end Represented + +section Sum + +universe u + +variable {ι₁ ι₂ : Type u} {spec₁ : OracleSpec.{u, u} ι₁} {spec₂ : OracleSpec.{u, u} ι₂} + +/-- The cache of the joined interface `spec₁ ⊕ₒ spec₂`: left queries read `c₁`, right queries read +`c₂`. -/ +@[expose] def Cache.sum (c₁ : Cache spec₁) (c₂ : Cache spec₂) : Cache (spec₁ ⊕ₒ spec₂) + | .inl q => c₁ q + | .inr q => c₂ q + +@[simp] theorem Cache.sum_inl (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) : + Cache.sum c₁ c₂ (.inl q) = c₁ q := rfl + +@[simp] theorem Cache.sum_inr (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) : + Cache.sum c₁ c₂ (.inr q) = c₂ q := rfl + +/-- The responses of a joined interface are finite when both sides' responses are. -/ +instance [∀ q, Finite (spec₁ q)] [∀ q, Finite (spec₂ q)] : ∀ q, Finite ((spec₁ ⊕ₒ spec₂) q) + | .inl q => inferInstanceAs (Finite (spec₁ q)) + | .inr q => inferInstanceAs (Finite (spec₂ q)) + +/-- Completing a joined cache completes its two sides independently. -/ +theorem bind_complete_sum [Fintype ι₁] [Fintype ι₂] [∀ q, Finite (spec₁ q)] + [∀ q, Finite (spec₂ q)] {γ : Type u} (d₁ : (q : ι₁) → PMF (spec₁ q)) + (d₂ : (q : ι₂) → PMF (spec₂ q)) (c₁ : Cache spec₁) (c₂ : Cache spec₂) + (F : ((q : ι₁) → spec₁ q) → ((q : ι₂) → spec₂ q) → PMF γ) : + (complete (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂)).bind + (fun table => F (fun q => table (.inl q)) fun q => table (.inr q)) = + (complete d₁ c₁).bind fun t₁ => (complete d₂ c₂).bind fun t₂ => F t₁ t₂ := + PMF.bind_pi_sum (β := spec₁ ⊕ₒ spec₂) + (fun q => (Cache.sum c₁ c₂ q).elim (OracleComp.sumHandler d₁ d₂ q) PMF.pure) F + +variable [DecidableEq ι₁] [DecidableEq ι₂] + +/-- Recording a left answer in a joined cache records it in the left cache. -/ +theorem Cache.update_sum_inl (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) (v : spec₁ q) : + Function.update (Cache.sum c₁ c₂) (.inl q) (some v) = + Cache.sum (Function.update c₁ q (some v)) c₂ := by + funext q' + rcases q' with q' | q' + · by_cases h : q' = q + · subst h; simp + · simp [Function.update_of_ne h, Function.update_of_ne (Sum.inl_injective.ne h)] + · simp + +/-- Recording a right answer in a joined cache records it in the right cache. -/ +theorem Cache.update_sum_inr (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) (v : spec₂ q) : + Function.update (Cache.sum c₁ c₂) (.inr q) (some v) = + Cache.sum c₁ (Function.update c₂ q (some v)) := by + funext q' + rcases q' with q' | q' + · simp + · by_cases h : q' = q + · subst h; simp + · simp [Function.update_of_ne h, Function.update_of_ne (Sum.inr_injective.ne h)] + +/-- A lazy call on the left side of a joined cache is a lazy call on the left cache. -/ +theorem answer_sum_inl (d₁ : (q : ι₁) → PMF (spec₁ q)) (d₂ : (q : ι₂) → PMF (spec₂ q)) + (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) : + answer (OracleComp.sumHandler d₁ d₂) (.inl q) (Cache.sum c₁ c₂) = + (answer d₁ q c₁).map fun z => (z.1, Cache.sum z.2 c₂) := by + cases h : c₁ q with + | none => + rw [answer_of_none d₁ c₁ q h, + answer_of_none (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inl q) h, PMF.map_comp] + exact congrArg (PMF.map · (d₁ q)) + (funext fun v => Prod.ext rfl (Cache.update_sum_inl c₁ c₂ q v)) + | some v => + rw [answer_of_some d₁ c₁ q v h, + answer_of_some (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inl q) v h, PMF.pure_map] + +/-- A lazy call on the right side of a joined cache is a lazy call on the right cache. -/ +theorem answer_sum_inr (d₁ : (q : ι₁) → PMF (spec₁ q)) (d₂ : (q : ι₂) → PMF (spec₂ q)) + (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) : + answer (OracleComp.sumHandler d₁ d₂) (.inr q) (Cache.sum c₁ c₂) = + (answer d₂ q c₂).map fun z => (z.1, Cache.sum c₁ z.2) := by + cases h : c₂ q with + | none => + rw [answer_of_none d₂ c₂ q h, + answer_of_none (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inr q) h, PMF.map_comp] + exact congrArg (PMF.map · (d₂ q)) + (funext fun v => Prod.ext rfl (Cache.update_sum_inr c₁ c₂ q v)) + | some v => + rw [answer_of_some d₂ c₂ q v h, + answer_of_some (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inr q) v h, PMF.pure_map] + +end Sum + +end Cslib.RandomOracle diff --git a/CslibFrontier/Probability/Product/Split.lean b/CslibFrontier/Probability/Product/Split.lean index 46a6ea7067..89b4c3ab07 100644 --- a/CslibFrontier/Probability/Product/Split.lean +++ b/CslibFrontier/Probability/Product/Split.lean @@ -20,6 +20,7 @@ Split the coordinates of an independent product by a predicate `P`, along Mathli `Equiv.piEquivPiSubtypeProd`. The coordinates satisfying `P` and the remaining coordinates form two independent products. In particular, the restriction of an independent product to some of its coordinates, or its composition with an injection of index types, is again an independent product. +An independent product over a sum of index types splits the same way into its two summands. Conditioning a uniform tuple on the value of one coordinate splits that coordinate off: it becomes constant, and the other coordinates stay uniform. @@ -30,6 +31,8 @@ constant, and the other coordinates stay uniform. * `Cslib.Probability.PMF.pi_map_restrict`, `Cslib.Probability.PMF.pi_map_comp_injective`: restricting an independent product, or reading it along an injection, gives the independent product of the selected factors. +* `Cslib.Probability.PMF.pi_map_sumPiEquivProdPi`, `Cslib.Probability.PMF.bind_pi_sum`: an + independent product over `ι ⊕ κ` is a pair of independent products over `ι` and `κ`. * `Cslib.Probability.PMF.filter_uniformOfFintype_eval_eq`: conditioning a uniform tuple on one coordinate sets that coordinate. -/ @@ -83,6 +86,32 @@ theorem pi_map_comp_injective {κ : Type*} [Fintype κ] (p : ∀ i, PMF (α i)) (pi p).map (fun a k => a (e k)) = pi fun k => p (e k) := pi_map_comp_subtypeEquiv (· ∈ Set.range e) p (Equiv.ofInjective e he) +section Sum + +variable {ι κ : Type*} [Fintype ι] [Fintype κ] {β : ι ⊕ κ → Type*} [∀ i, Finite (β i)] + +/-- An independent product over `ι ⊕ κ` splits, along Mathlib's `Equiv.sumPiEquivProdPi`, into +independent products over `ι` and over `κ`. -/ +theorem pi_map_sumPiEquivProdPi (p : ∀ i, PMF (β i)) : + (pi p).map (Equiv.sumPiEquivProdPi β) = + (pi fun i => p (.inl i)).bind fun a => (pi fun j => p (.inr j)).map (a, ·) := by + ext ⟨a, b⟩ + rw [map_equiv_apply, bind_map_prodMk_apply, pi_apply, pi_apply, pi_apply, + Fintype.prod_sum_type] + rfl + +/-- An experiment reading the two halves of an independent product over `ι ⊕ κ` samples each half +independently. -/ +theorem bind_pi_sum {γ : Type*} (p : ∀ i, PMF (β i)) + (F : (∀ i, β (.inl i)) → (∀ j, β (.inr j)) → PMF γ) : + (pi p).bind (fun f => F (fun i => f (.inl i)) fun j => f (.inr j)) = + (pi fun i => p (.inl i)).bind fun a => (pi fun j => p (.inr j)).bind fun b => F a b := by + have h := congrArg (fun q => q.bind fun ab => F ab.1 ab.2) (pi_map_sumPiEquivProdPi p) + simp only [PMF.bind_bind, PMF.map, PMF.pure_bind, Function.comp_def] at h + exact h + +end Sum + /-- Conditioning a uniform tuple on the value `c` of its coordinate `j` sets that coordinate to `c` and keeps the other coordinates uniform. -/ theorem filter_uniformOfFintype_eval_eq [DecidableEq ι] {γ : Type*} [Fintype γ] [Nonempty γ] diff --git a/manual/workflows/probabilistic-workflows.md b/manual/workflows/probabilistic-workflows.md index bf3d467493..019de8254c 100644 --- a/manual/workflows/probabilistic-workflows.md +++ b/manual/workflows/probabilistic-workflows.md @@ -1465,6 +1465,40 @@ child-table equations remain local mathematical obligations. These eager tables prove distributional equalities; the executable reductions retain their existing separate runtime and query-bound certificates. +## Replace a game's own caches by complete tables + +Use this route when a game keeps its random-oracle caches in its own state, such as association +lists in a record or several caches for several oracles, rather than running a program through +`memoize`. Import +[RandomOracle/Represented.lean](../../CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean). + +1. Join the oracles into one interface with `⊕ₒ` and write each game query as an oracle program + over it. Write the lazy handler and the complete-table handler as `interpret` of the same + program, so the game logic is stated once. +2. Choose `rep : State → RandomOracle.Cache spec`, the cache that the state represents. Build it + from one cache per oracle with `RandomOracle.Cache.sum`, and read list caches with + `RandomOracle.ofList`. +3. Prove the per-call step: each lazy handler call, with its next state mapped by `rep`, is + `RandomOracle.answer` on the represented cache. On a joined cache, `answer_sum_inl` and + `answer_sum_inr` reduce this to one side, and `RandomOracle.eval_zoom_memoize_map` proves it for + a `zoomStateT`/`memoize` cache. +4. Apply `RandomOracle.eval_run_interpret_eq_complete_bind`, or `run_interpret_eq_complete_bind` + for a handler that already returns `PMF`. The run's result law becomes completing `rep s` once + and answering every call from the table. `RandomOracle.bind_complete_sum` then splits the + completed table into one independent table per oracle. + +This proves an exact equality, so step 3 fails when a handler also inspects its caches directly, +for example a decapsulation filter that checks whether a message was already queried. Such a hop +needs an approximate simulation instead, as in `closeTo_cachedCoupledBotGame_eagerCoupledBotGame` +in [FujisakiOkamoto/Bot.lean](../../CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Bot.lean). + +The checked clients are `Scheme.eval_roIndCcaGame_eq_eagerIndCcaGame` in +[FujisakiOkamoto/Security.lean](../../CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/Security.lean), +with two caches for `G` and `H`, and `Scheme.eval_coupledGame_eq_eagerCoupledGame` in +[NotBot/Lazy.lean](../../CslibFrontier/Crypto/Protocols/KEM/FujisakiOkamoto/NotBot/Lazy.lean), +with three caches and a key-derivation query that calls two oracles. Check them with +`scripts/build.sh --wfail --iofail CslibFrontier.Crypto.Protocols.KEM.FujisakiOkamoto.NotBot.Lazy`. + ## Certify an adaptive oracle client For native oracle efficiency, start with `WordOracleComp Operation α`: finite operation names and word payloads/replies. From 24823ddab0c291082e4213e1661f610ee29e9803 Mon Sep 17 00:00:00 2001 From: Sam Schlesinger Date: Fri, 13 Nov 2026 04:11:06 +0000 Subject: [PATCH 7/7] refactor(RandomOracle): Cache.sum and answer_sum_* in a Split-free RandomOracle/Sum module Move the joined-cache API out of Represented so that the two-list memoizeSum (proofs P3) can build on it without importing Probability.Product.Split. Represented keeps the represented-handler theorems and bind_complete_sum, and re-exports Sum. --- CslibFrontier.lean | 1 + .../RandomOracle/Represented.lean | 86 +------------ .../Probabilistic/RandomOracle/Sum.lean | 115 ++++++++++++++++++ manual/workflows/probabilistic-workflows.md | 5 +- 4 files changed, 123 insertions(+), 84 deletions(-) create mode 100644 CslibFrontier/Languages/Probabilistic/RandomOracle/Sum.lean diff --git a/CslibFrontier.lean b/CslibFrontier.lean index e8c0dc9b2f..f684e99fac 100644 --- a/CslibFrontier.lean +++ b/CslibFrontier.lean @@ -6083,6 +6083,7 @@ public import CslibFrontier.Languages.Probabilistic.RandomOracle public import CslibFrontier.Languages.Probabilistic.RandomOracle.Probabilistic public import CslibFrontier.Languages.Probabilistic.RandomOracle.Programming public import CslibFrontier.Languages.Probabilistic.RandomOracle.Represented +public import CslibFrontier.Languages.Probabilistic.RandomOracle.Sum public import CslibFrontier.Languages.Probabilistic.RandomPermutation public import CslibFrontier.Languages.Probabilistic.RandomPermutation.ExtPermsCard public import CslibFrontier.Languages.Probabilistic.Rational diff --git a/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean b/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean index f17fcf0293..f4bfd25fde 100644 --- a/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean +++ b/CslibFrontier/Languages/Probabilistic/RandomOracle/Represented.lean @@ -7,8 +7,7 @@ Authors: Samuel Schlesinger module -- upstream: candidate -public import CslibFrontier.Languages.Probabilistic.RandomOracle -public import CslibFrontier.Languages.Probabilistic.Sum +public import CslibFrontier.Languages.Probabilistic.RandomOracle.Sum import CslibFrontier.Languages.Probabilistic.Semantics import CslibFrontier.Languages.Probabilistic.Simulation import CslibFrontier.Probability.Product.Split @@ -23,21 +22,15 @@ game has the law of completing the represented cache once and answering every ca completed table (`eval_run_interpret_eq_complete_bind`). The per-call hypothesis is the only game-specific step of an eager/lazy argument. -Several oracles are one oracle on the sum of their interfaces. `Cache.sum` joins their caches, so -that one represented state serves them all, and `bind_complete_sum` splits the completed table back -into one table per oracle. - -## Main definitions - -* `Cslib.RandomOracle.Cache.sum`: the cache of `spec₁ ⊕ₒ spec₂` built from one cache for each side. +Several oracles are one oracle on the sum of their interfaces. `RandomOracle.Cache.sum`, re-exported +here, joins their caches, so that one represented state serves them all, and `bind_complete_sum` +splits the completed table back into one table per oracle. ## Main results * `Cslib.RandomOracle.run_interpret_eq_complete_bind`, `Cslib.RandomOracle.eval_run_interpret_eq_complete_bind`: a handler that is a lazy random oracle on a represented cache has the law of the completed table. -* `Cslib.RandomOracle.answer_sum_inl`, `Cslib.RandomOracle.answer_sum_inr`: a lazy call on one side - of a joined cache is a lazy call on that side's cache. * `Cslib.RandomOracle.bind_complete_sum`: completing a joined cache completes each side independently. -/ @@ -100,23 +93,6 @@ universe u variable {ι₁ ι₂ : Type u} {spec₁ : OracleSpec.{u, u} ι₁} {spec₂ : OracleSpec.{u, u} ι₂} -/-- The cache of the joined interface `spec₁ ⊕ₒ spec₂`: left queries read `c₁`, right queries read -`c₂`. -/ -@[expose] def Cache.sum (c₁ : Cache spec₁) (c₂ : Cache spec₂) : Cache (spec₁ ⊕ₒ spec₂) - | .inl q => c₁ q - | .inr q => c₂ q - -@[simp] theorem Cache.sum_inl (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) : - Cache.sum c₁ c₂ (.inl q) = c₁ q := rfl - -@[simp] theorem Cache.sum_inr (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) : - Cache.sum c₁ c₂ (.inr q) = c₂ q := rfl - -/-- The responses of a joined interface are finite when both sides' responses are. -/ -instance [∀ q, Finite (spec₁ q)] [∀ q, Finite (spec₂ q)] : ∀ q, Finite ((spec₁ ⊕ₒ spec₂) q) - | .inl q => inferInstanceAs (Finite (spec₁ q)) - | .inr q => inferInstanceAs (Finite (spec₂ q)) - /-- Completing a joined cache completes its two sides independently. -/ theorem bind_complete_sum [Fintype ι₁] [Fintype ι₂] [∀ q, Finite (spec₁ q)] [∀ q, Finite (spec₂ q)] {γ : Type u} (d₁ : (q : ι₁) → PMF (spec₁ q)) @@ -128,60 +104,6 @@ theorem bind_complete_sum [Fintype ι₁] [Fintype ι₂] [∀ q, Finite (spec PMF.bind_pi_sum (β := spec₁ ⊕ₒ spec₂) (fun q => (Cache.sum c₁ c₂ q).elim (OracleComp.sumHandler d₁ d₂ q) PMF.pure) F -variable [DecidableEq ι₁] [DecidableEq ι₂] - -/-- Recording a left answer in a joined cache records it in the left cache. -/ -theorem Cache.update_sum_inl (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) (v : spec₁ q) : - Function.update (Cache.sum c₁ c₂) (.inl q) (some v) = - Cache.sum (Function.update c₁ q (some v)) c₂ := by - funext q' - rcases q' with q' | q' - · by_cases h : q' = q - · subst h; simp - · simp [Function.update_of_ne h, Function.update_of_ne (Sum.inl_injective.ne h)] - · simp - -/-- Recording a right answer in a joined cache records it in the right cache. -/ -theorem Cache.update_sum_inr (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) (v : spec₂ q) : - Function.update (Cache.sum c₁ c₂) (.inr q) (some v) = - Cache.sum c₁ (Function.update c₂ q (some v)) := by - funext q' - rcases q' with q' | q' - · simp - · by_cases h : q' = q - · subst h; simp - · simp [Function.update_of_ne h, Function.update_of_ne (Sum.inr_injective.ne h)] - -/-- A lazy call on the left side of a joined cache is a lazy call on the left cache. -/ -theorem answer_sum_inl (d₁ : (q : ι₁) → PMF (spec₁ q)) (d₂ : (q : ι₂) → PMF (spec₂ q)) - (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) : - answer (OracleComp.sumHandler d₁ d₂) (.inl q) (Cache.sum c₁ c₂) = - (answer d₁ q c₁).map fun z => (z.1, Cache.sum z.2 c₂) := by - cases h : c₁ q with - | none => - rw [answer_of_none d₁ c₁ q h, - answer_of_none (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inl q) h, PMF.map_comp] - exact congrArg (PMF.map · (d₁ q)) - (funext fun v => Prod.ext rfl (Cache.update_sum_inl c₁ c₂ q v)) - | some v => - rw [answer_of_some d₁ c₁ q v h, - answer_of_some (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inl q) v h, PMF.pure_map] - -/-- A lazy call on the right side of a joined cache is a lazy call on the right cache. -/ -theorem answer_sum_inr (d₁ : (q : ι₁) → PMF (spec₁ q)) (d₂ : (q : ι₂) → PMF (spec₂ q)) - (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) : - answer (OracleComp.sumHandler d₁ d₂) (.inr q) (Cache.sum c₁ c₂) = - (answer d₂ q c₂).map fun z => (z.1, Cache.sum c₁ z.2) := by - cases h : c₂ q with - | none => - rw [answer_of_none d₂ c₂ q h, - answer_of_none (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inr q) h, PMF.map_comp] - exact congrArg (PMF.map · (d₂ q)) - (funext fun v => Prod.ext rfl (Cache.update_sum_inr c₁ c₂ q v)) - | some v => - rw [answer_of_some d₂ c₂ q v h, - answer_of_some (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inr q) v h, PMF.pure_map] - end Sum end Cslib.RandomOracle diff --git a/CslibFrontier/Languages/Probabilistic/RandomOracle/Sum.lean b/CslibFrontier/Languages/Probabilistic/RandomOracle/Sum.lean new file mode 100644 index 0000000000..e7d69f336a --- /dev/null +++ b/CslibFrontier/Languages/Probabilistic/RandomOracle/Sum.lean @@ -0,0 +1,115 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import CslibFrontier.Languages.Probabilistic.RandomOracle +public import CslibFrontier.Languages.Probabilistic.Sum + +/-! +# Random-oracle caches on a sum of interfaces + +Several independent lazy random oracles are one lazy random oracle on the sum of their interfaces, +answered by `OracleComp.sumHandler`. `RandomOracle.Cache.sum` joins one cache per side into a cache +of the joined interface, and a lazy call on one side of a joined cache is a lazy call on that side's +cache (`answer_sum_inl`, `answer_sum_inr`). A game that keeps one cache per oracle can therefore be +read as one represented lazy oracle. + +## Main definitions + +* `Cslib.RandomOracle.Cache.sum`: the cache of `spec₁ ⊕ₒ spec₂` built from one cache for each side. + +## Main results + +* `Cslib.RandomOracle.answer_sum_inl`, `Cslib.RandomOracle.answer_sum_inr`: a lazy call on one side + of a joined cache is a lazy call on that side's cache. +* `Cslib.RandomOracle.Cache.update_sum_inl`, `Cslib.RandomOracle.Cache.update_sum_inr`: recording + an answer in a joined cache records it on its side. +-/ + +public section + +namespace Cslib.RandomOracle + +open scoped Cslib + +universe u + +variable {ι₁ ι₂ : Type u} {spec₁ : OracleSpec.{u, u} ι₁} {spec₂ : OracleSpec.{u, u} ι₂} + +/-- The cache of the joined interface `spec₁ ⊕ₒ spec₂`: left queries read `c₁`, right queries read +`c₂`. -/ +@[expose] def Cache.sum (c₁ : Cache spec₁) (c₂ : Cache spec₂) : Cache (spec₁ ⊕ₒ spec₂) + | .inl q => c₁ q + | .inr q => c₂ q + +@[simp] theorem Cache.sum_inl (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) : + Cache.sum c₁ c₂ (.inl q) = c₁ q := rfl + +@[simp] theorem Cache.sum_inr (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) : + Cache.sum c₁ c₂ (.inr q) = c₂ q := rfl + +/-- The responses of a joined interface are finite when both sides' responses are. -/ +instance [∀ q, Finite (spec₁ q)] [∀ q, Finite (spec₂ q)] : ∀ q, Finite ((spec₁ ⊕ₒ spec₂) q) + | .inl q => inferInstanceAs (Finite (spec₁ q)) + | .inr q => inferInstanceAs (Finite (spec₂ q)) + +variable [DecidableEq ι₁] [DecidableEq ι₂] + +/-- Recording a left answer in a joined cache records it in the left cache. -/ +theorem Cache.update_sum_inl (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) (v : spec₁ q) : + Function.update (Cache.sum c₁ c₂) (.inl q) (some v) = + Cache.sum (Function.update c₁ q (some v)) c₂ := by + funext q' + rcases q' with q' | q' + · by_cases h : q' = q + · subst h; simp + · simp [Function.update_of_ne h, Function.update_of_ne (Sum.inl_injective.ne h)] + · simp + +/-- Recording a right answer in a joined cache records it in the right cache. -/ +theorem Cache.update_sum_inr (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) (v : spec₂ q) : + Function.update (Cache.sum c₁ c₂) (.inr q) (some v) = + Cache.sum c₁ (Function.update c₂ q (some v)) := by + funext q' + rcases q' with q' | q' + · simp + · by_cases h : q' = q + · subst h; simp + · simp [Function.update_of_ne h, Function.update_of_ne (Sum.inr_injective.ne h)] + +/-- A lazy call on the left side of a joined cache is a lazy call on the left cache. -/ +theorem answer_sum_inl (d₁ : (q : ι₁) → PMF (spec₁ q)) (d₂ : (q : ι₂) → PMF (spec₂ q)) + (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₁) : + answer (OracleComp.sumHandler d₁ d₂) (.inl q) (Cache.sum c₁ c₂) = + (answer d₁ q c₁).map fun z => (z.1, Cache.sum z.2 c₂) := by + cases h : c₁ q with + | none => + rw [answer_of_none d₁ c₁ q h, + answer_of_none (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inl q) h, PMF.map_comp] + exact congrArg (PMF.map · (d₁ q)) + (funext fun v => Prod.ext rfl (Cache.update_sum_inl c₁ c₂ q v)) + | some v => + rw [answer_of_some d₁ c₁ q v h, + answer_of_some (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inl q) v h, PMF.pure_map] + +/-- A lazy call on the right side of a joined cache is a lazy call on the right cache. -/ +theorem answer_sum_inr (d₁ : (q : ι₁) → PMF (spec₁ q)) (d₂ : (q : ι₂) → PMF (spec₂ q)) + (c₁ : Cache spec₁) (c₂ : Cache spec₂) (q : ι₂) : + answer (OracleComp.sumHandler d₁ d₂) (.inr q) (Cache.sum c₁ c₂) = + (answer d₂ q c₂).map fun z => (z.1, Cache.sum c₁ z.2) := by + cases h : c₂ q with + | none => + rw [answer_of_none d₂ c₂ q h, + answer_of_none (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inr q) h, PMF.map_comp] + exact congrArg (PMF.map · (d₂ q)) + (funext fun v => Prod.ext rfl (Cache.update_sum_inr c₁ c₂ q v)) + | some v => + rw [answer_of_some d₂ c₂ q v h, + answer_of_some (OracleComp.sumHandler d₁ d₂) (Cache.sum c₁ c₂) (.inr q) v h, PMF.pure_map] + +end Cslib.RandomOracle diff --git a/manual/workflows/probabilistic-workflows.md b/manual/workflows/probabilistic-workflows.md index 93bacfed87..5820000fc0 100644 --- a/manual/workflows/probabilistic-workflows.md +++ b/manual/workflows/probabilistic-workflows.md @@ -1516,8 +1516,9 @@ lists in a record or several caches for several oracles, rather than running a p over it. Write the lazy handler and the complete-table handler as `interpret` of the same program, so the game logic is stated once. 2. Choose `rep : State → RandomOracle.Cache spec`, the cache that the state represents. Build it - from one cache per oracle with `RandomOracle.Cache.sum`, and read list caches with - `RandomOracle.ofList`. + from one cache per oracle with `RandomOracle.Cache.sum` from + [RandomOracle/Sum.lean](../../CslibFrontier/Languages/Probabilistic/RandomOracle/Sum.lean), + which `Represented.lean` re-exports, and read list caches with `RandomOracle.ofList`. 3. Prove the per-call step: each lazy handler call, with its next state mapped by `rep`, is `RandomOracle.answer` on the represented cache. On a joined cache, `answer_sum_inl` and `answer_sum_inr` reduce this to one side, and `RandomOracle.eval_zoom_memoize_map` proves it for