/-
 -----------------------------------------------------------
  Security games as pmfs
 -----------------------------------------------------------
-/

import data.zmod.basic
import probability.probability_mass_function.basic
import to_mathlib
import uniform

noncomputable theory

/-
  G1 = public key space, G2 = private key space,
  M = message space, C = ciphertext space
  A_state = type for state A1 passes to A2
-/
variables {G1 G2 M C A_state: Type} [decidable_eq M]
          (keygen : pmf (G1 × G2))
          (encrypt : G1 → M → pmf C)
          (decrypt : G2 → C → M)
          (A1 : G1 → pmf (M × M × A_state))
          (A2 : C → A_state → pmf (zmod 2)) -- Should have G1 else how can A2 do further encryptions? Any general result about encryptions before getting challenge ciphertext...?

/-
  Executes the a public-key protocol defined by keygen,
  encrypt, and decrypt
-/
-- Simulates running the program and returns 1 with prob 1 if:
-- `Pr[D(sk, E(pk, m)) = m] = 1` holds
def enc_dec  (m : M) : pmf (zmod 2) :=  -- given a message m
do
  k ← keygen, -- produces a public / secret key pair
  c ← encrypt k.1 m, -- encrypts m using pk
  pure (if decrypt k.2 c = m then 1 else 0) -- decrypts using sk and checks for equality with m

/-
  A public-key encryption protocol is correct if decryption undoes
  encryption with probability 1
-/

def pke_correctness : Prop := ∀ (m : M), enc_dec keygen encrypt decrypt m = pure 1 -- This chain of encryption/decryption matches the monadic actions in the `enc_dec` function

/-
  The semantic security game.
  Returns 1 if the attacker A2 guesses the correct bit
-/
def SSG : pmf (zmod 2):=
do
  k ← keygen,
  m ← A1 k.1,
  b ← uniform_2,
  c ← encrypt k.1 (if b = 0 then m.1 else m.2.1),
  b' ← A2 c m.2.2,
  pure (1 + b + b')

-- SSG(A) denotes the event that A wins the semantic security game
local notation `Pr[SSG(A)]` := (SSG keygen encrypt A1 A2 1)

def pke_semantic_security (ε : nnreal) : Prop := abs (Pr[SSG(A)] - 1/2) ≤ ε
