import InductiveVerification.Event
import Init.Data.Nat.Lemmas
import Mathlib.Data.Nat.Basic
import Mathlib.Data.Nat.Dist
import Mathlib.Order.Defs.PartialOrder

-- Theory of Public Keys (common to all public-key protocols)

-- Private and public keys; initial states of agents

variable [InvKey]
open InvKey

lemma invKey_K {K : Key} : K ∈ symKeys → invKey K = K := by
  intro h
  simp [symKeys] at h
  exact h

-- Asymmetric Keys
inductive KeyMode
  | Signature
  | Encryption

axiom publicKey : KeyMode → Agent → Key
axiom injective_publicKey : ∀ {b c : KeyMode} {A A' : Agent},
  publicKey b A = publicKey c A' → b = c ∧ A = A'

noncomputable abbrev pubEK (A : Agent) : Key := publicKey KeyMode.Encryption A
noncomputable abbrev pubSK (A : Agent) : Key := publicKey KeyMode.Signature A
noncomputable abbrev privateKey (b : KeyMode) (A : Agent) : Key := invKey (publicKey b A)
noncomputable abbrev priEK (A : Agent) : Key := privateKey KeyMode.Encryption A
noncomputable abbrev priSK (A : Agent) : Key := privateKey KeyMode.Signature A
noncomputable abbrev pubK (A : Agent) : Key := pubEK A
noncomputable abbrev priK (A : Agent) : Key := invKey (pubEK A)

-- Axioms for private and public keys
@[simp]
axiom privateKey_neq_publicKey {b c : KeyMode} {A A' : Agent} :
    privateKey b A ≠ publicKey c A'

@[simp]
lemma publicKey_neq_privateKey {b c : KeyMode} {A A' : Agent} :
    publicKey b A ≠ privateKey c A' := by
  exact privateKey_neq_publicKey.symm

-- Basic properties of pubK and priK
omit [InvKey] in
@[simp]
lemma publicKey_inject {b c : KeyMode} {A A' : Agent} :
    (publicKey b A = publicKey c A') ↔ (b = c ∧ A = A') := by
  grind[injective_publicKey]

lemma invKey_injective: Function.Injective invKey := by
  intro _ _ _
  simp_all[invKey_eq]

lemma not_symKeys_pubK {b : KeyMode} {A : Agent} :
    publicKey b A ∉ symKeys :=
by
  grind[symKeys, privateKey_neq_publicKey]

lemma not_symKeys_priK {b : KeyMode} {A : Agent} :
    privateKey b A ∉ symKeys := by
  simp [symKeys, privateKey, invKey_eq, privateKey_neq_publicKey]

lemma syKey_neq_priEK :
  K ∈ symKeys → K ≠ priEK A := by
  intro _ _
  have _ := not_symKeys_pubK (b := KeyMode.Encryption) (A := A)
  simp_all[symKeys, invKey_eq]

lemma symKeys_neq_imp_neq :
  ((K ∈ symKeys) ≠ (K' ∈ symKeys)) → K ≠ K' := by
  intro h eq
  rw[eq] at h
  contradiction

@[simp]
lemma symKeys_invKey_iff : (invKey K ∈ symKeys) = (K ∈ symKeys) := by
  simp [symKeys, invKey_eq]

lemma analz_symKeys_Decrypt :
    Msg.Crypt K X ∈ analz H → K ∈ symKeys → Msg.Key K ∈ analz H → X ∈ analz H := by
  simp [symKeys]
  intro _ _ _
  aapply analz.decrypt; simp_all

-- "Image" equations that hold for injective functions

@[simp]
lemma invKey_image_eq : (invKey x ∈ invKey '' A) ↔ (x ∈ A) := by
  simp [Set.mem_image]

omit [InvKey] in
@[simp]
lemma publicKey_image_eq :
    (publicKey b x ∈ publicKey c '' AA) ↔ (b = c ∧ x ∈ AA) := by
  simp [Set.mem_image, And.comm, Eq.comm]

@[simp]
lemma privateKey_notin_image_publicKey :
    privateKey b x ∉ publicKey c '' AA := by
  simp[publicKey_neq_privateKey]

@[simp]
lemma privateKey_image_eq :
    (privateKey b A ∈ invKey '' (publicKey c '' AS)) ↔ (b = c ∧ A ∈ AS) := by
  rw[←publicKey_image_eq]; simp [Set.mem_image, privateKey]

@[simp]
lemma publicKey_notin_image_privateKey :
    publicKey b A ∉ invKey '' ( publicKey c '' AS ) := by
  simp [privateKey_neq_publicKey]
 
-- Symmetric Keys
-- For some protocols, it is convenient to equip agents with symmetric as
-- well as asymmetric keys.  The theory ‹Shared› assumes that all keys
-- are symmetric.
axiom shrK : Agent → Key

axiom inj_shrK : Function.Injective shrK

-- All shared keys are symmetric
axiom sym_shrK : ∀ {A : Agent}, shrK A ∈ symKeys 

-- Injectiveness: Agents' long-term keys are distinct.
@[simp]
lemma invKey_shrK :
    invKey (shrK A) = shrK A := by
  simp [invKey_K, sym_shrK]
  
lemma analz_shrK_Decrypt :
    Msg.Crypt (shrK A) X ∈ analz H → Msg.Key (shrK A) ∈ analz H → X ∈ analz H :=
by
  intro _ _
  aapply analz.decrypt; rw[invKey_shrK]; assumption


lemma analz_Decrypt' :
    Msg.Crypt K X ∈ analz H → K ∈ symKeys → Msg.Key K ∈ analz H → X ∈ analz H := by
  intro _ _ _
  aapply analz.decrypt; simp_all[symKeys]

@[simp]
lemma priK_neq_shrK {A : Agent} : shrK A ≠ privateKey b C := by
  intro h
  apply not_symKeys_priK
  rw[←h]
  exact sym_shrK

@[simp]
lemma shrK_neq_priK : privateKey b C ≠ shrK A := by
  exact priK_neq_shrK.symm

@[simp]
lemma pubK_neq_shrK : shrK A ≠ publicKey b C := by
  intro h
  apply not_symKeys_pubK
  rw[←h]
  exact sym_shrK

@[simp]
lemma shrK_neq_pubK : publicKey b C ≠ shrK A := by
  exact pubK_neq_shrK.symm

@[simp]
lemma priEK_noteq_shrK : priEK A ≠ shrK B := by
  simp

@[simp]
lemma publicKey_notin_image_shrK : publicKey b x ∉ shrK '' AA := by
  simp

@[simp]
lemma privateKey_notin_image_shrK : privateKey b x ∉ shrK '' AA := by
  simp

@[simp]
lemma shrK_notin_image_publicKey : shrK x ∉ publicKey b '' AA := by
  simp

@[simp]
lemma shrK_notin_image_privateKey :
  shrK x ∉ (invKey '' ((publicKey b) '' AA )) := by
  simp

omit [InvKey] in
@[simp]
lemma shrK_image_eq : (shrK x ∈ shrK '' AA) ↔ (x ∈ AA) := by
  grind[inj_shrK]

attribute [simp] invKey_K

variable [Bad]
open Bad
-- Fill in definition for Initial States of Agents
@[simp]
instance : HasInitState Agent where
  initState
    | Agent.Server =>
      {Msg.Key (priEK Agent.Server), Msg.Key (priSK Agent.Server)} ∪
      (Msg.Key '' Set.range pubEK) ∪ (Msg.Key '' Set.range pubSK) ∪ (Msg.Key '' Set.range shrK)
    | Agent.Friend i =>
      {Msg.Key (priEK (Agent.Friend i)), Msg.Key (priSK (Agent.Friend i)), Msg.Key (shrK (Agent.Friend i))} ∪
      (Msg.Key '' Set.range pubEK) ∪ (Msg.Key '' Set.range pubSK)
    | Agent.Spy =>
      (Msg.Key '' (invKey '' (pubEK '' bad))) ∪ (Msg.Key '' ( invKey '' ( pubSK '' bad  ))) ∪
      (Msg.Key '' ( shrK '' bad )) ∪
      (Msg.Key '' Set.range pubEK) ∪ (Msg.Key '' Set.range pubSK)

open HasInitState

-- These lemmas allow reasoning about `used evs` rather than `knows Spy evs`,
-- which is useful when there are private Notes. Because they depend upon the
-- definition of `initState`, they cannot be moved up.

lemma used_parts_subset_parts :
  X ∈ used evs → ( parts {X} ⊆ used evs ) := by
  induction evs with
  | nil => 
    simp[used]; intro A h₁ X h₂; simp; exists A
    cases A
    all_goals (
      simp_all[-parts_union]
      apply_rules [parts_trans, h₂, Set.singleton_subset_iff.mpr]
    )
  | cons e evs ih =>
    intro h₁ X
    cases e <;> simp[used] <;> simp[used] at h₁ <;> try aapply ih;
    all_goals (
    cases h₁ with
      | inl h₁ => intro _; left; apply_rules [parts_trans, h₁, Set.singleton_subset_iff.mpr]
      | inr h₁ => intro h₂; right; apply ih at h₁; aapply h₁
    )

lemma MPair_used_D :
    ⦃X, Y⦄ ∈ used evs → (X ∈ used evs ∧ Y ∈ used evs) := by
  intro h
  apply used_parts_subset_parts (X := Msg.MPair X Y) (evs := evs) at h
  rw[Set.singleton_def, parts_insert_MPair] at h
  simp at h
  apply And.intro <;> apply h <;> tauto

lemma MPair_used {P : Prop} :
    ⦃X, Y⦄ ∈ used evs →
    (X ∈ used evs → Y ∈ used evs → P) →
    P := by
  intro hXY hP
  have ⟨hX, hY⟩ := MPair_used_D hXY
  exact hP hX hY

-- Define `@[simp]` lemmas for `initState` for each case of `Agent`
@[simp]
lemma keysFor_parts_initState {C : Agent} :
    keysFor (parts (initState C)) = ∅ := by
  cases C <;>
  simp[initState, keysFor]

lemma Crypt_notin_initState {B : Agent} :
  Msg.Crypt K X ∉ parts ( initState B ) := by
  cases B <;> simp[initState, priEK, priSK]

@[simp]
lemma Crypt_notin_used_empty :
  Msg.Crypt K X ∉ used [] := by
  simp[used]; intro A; cases A <;> simp

-- Basic properties of shrK

-- Agents see their own shared keys
-- iff
@[simp]
lemma shrK_in_initState {A : Agent} :
    Msg.Key (shrK A) ∈ initState A := by
  induction A <;> simp [HasInitState.initState, initState]
  exists Agent.Spy; simp[Spy_in_bad]

-- iff
@[simp]
lemma shrK_in_knows {A : Agent} : Msg.Key (shrK A) ∈ knows A evs := by
  apply initState_subset_knows
  exact shrK_in_initState

-- iff
@[simp]
lemma shrK_in_used {A : Agent} : Msg.Key (shrK A) ∈ used evs := by
  apply_rules [initState_into_used, parts.inj, shrK_in_initState]

-- Fresh keys never clash with long-term shared keys

-- Used in parts_induct_tac and analz_Fake_tac to distinguish session keys
-- from long-term shared keys
@[simp]
lemma Key_not_used {K : Key} : Msg.Key K ∉ used evs → K ∉ Set.range shrK := by
  simp
  intro h₁ _ h₂
  apply h₁
  rw[←h₂]
  apply shrK_in_used

lemma shrK_neq {K : Key} {B : Agent} : Msg.Key K ∉ used evs → shrK B ≠ K := by
  intro h h'
  apply h
  rw [←h']
  exact shrK_in_used

@[simp]
lemma neq_shrK {K : Key} {B : Agent} : Msg.Key K ∉ used evs → K ≠ shrK B := by
  intro h; apply shrK_neq at h; exact h.symm

-- Function spies

omit [InvKey] [Bad] in
lemma not_SignatureE {b : KeyMode} : b ≠ KeyMode.Signature → b = KeyMode.Encryption := by
  cases b <;> simp
  
-- Agents see their own private keys
@[simp]
lemma priK_in_initState {b : KeyMode} {A : Agent} :
    Msg.Key (privateKey b A) ∈ initState A := by
  induction A <;>
  simp [HasInitState.initState, initState, privateKey, pubEK, pubSK] <;>
  cases b <;> simp[Spy_in_bad]

@[simp]
lemma publicKey_in_initState {b : KeyMode} {A : Agent} {B : Agent} :
    Msg.Key (publicKey b A) ∈ initState B := by
  induction B <;>
  simp [HasInitState.initState, initState, priEK, priSK, pubEK, pubSK] <;>
  cases b <;>
  simp
  
-- All public keys are visible
@[simp]
lemma spies_pubK : Msg.Key (publicKey b A) ∈ spies evs := by
  induction evs with
  | nil => simp [spies, knows]
           cases b <;> tauto
  | cons e evs ih =>
    cases e <;> rw [spies] <;> apply knows_subset_knows_Cons <;> assumption

@[simp]
lemma analz_spies_pubK : Msg.Key (publicKey b A) ∈ analz (spies evs) := by
  exact analz.inj spies_pubK

-- Spy sees private keys of bad agents
lemma Spy_spies_bad_privateKey { h : A ∈ bad } : Msg.Key (privateKey b A) ∈ spies evs := by
  induction evs with
  | nil => simp_all [spies, knows, pubSK, pubEK]; cases b <;> tauto
  | cons e evs ih =>
    cases e <;> rw[spies] <;> aapply knows_subset_knows_Cons

-- Spy sees long-term shared keys of bad agents
lemma Spy_spies_bad_shrK {h : A ∈ bad} : Msg.Key (shrK A) ∈ spies evs := by
  induction evs with
  | nil => simp [spies, knows]; exists A
  | cons e evs ih =>
    cases e <;> rw [spies] <;> aapply knows_subset_knows_Cons

@[simp]
lemma publicKey_into_used : Msg.Key (publicKey b A) ∈ used evs := by
  aapply initState_into_used
  apply parts_increasing
  exact publicKey_in_initState

@[simp]
lemma privateKey_into_used : Msg.Key (privateKey b A) ∈ used evs := by
  aapply initState_into_used
  apply parts_increasing
  exact priK_in_initState

-- For case analysis on whether or not an agent is compromised
lemma Crypt_Spy_analz_bad :
    Msg.Crypt (shrK A) X ∈ analz (knows Agent.Spy evs) → A ∈ bad → X ∈ analz (knows Agent.Spy evs) := by
    intro h₁ h₂
    aapply analz.decrypt
    apply analz_increasing
    simp[invKey_shrK]
    aapply Spy_spies_bad_shrK

@[simp]
lemma Crypt_synth_pubK :
  (Msg.Crypt (pubEK A) X ∈ synth (spies evs)) ↔
  (Msg.Crypt (pubEK A) X ∈ (spies evs) ∨ ( X ∈ synth (spies evs))) :=
by simp[Crypt_synth_EK]; 

@[simp]
lemma Crypt_synth_analz_pubK :
  (Msg.Crypt (pubEK A) X ∈ synth (analz (spies evs))) ↔
  (Msg.Crypt (pubEK A) X ∈ (analz (spies evs)) ∨ ( X ∈ synth (analz (spies evs)))) :=
by simp[Crypt_synth_EK]; 

@[simp]
lemma Nonce_notin_initState {B : Agent} : Msg.Nonce N ∉ parts (initState B) := by
  cases B <;>
  simp [initState]

@[simp]
lemma Nonce_notin_used_empty : Msg.Nonce N ∉ used [] := by
  simp [used]; intro A; cases A <;> simp

-- Supply fresh nonces for possibility theorems
lemma Nonce_supply_lemma : ∃ N, ∀ n, N ≤ n → Msg.Nonce n ∉ used evs := by
  induction evs with
  | nil =>
    use 0; simp
  | cons e evs ih =>
    obtain ⟨N₁, hN⟩ := ih
    cases e with
    | Says _ _ m => simp[used]
                    have ns := msg_Nonce_supply (msg := m)
                    obtain ⟨N₂, ns⟩ := ns
                    exists Nat.max N₁ N₂
                    simp_all
    | Notes _ m => simp[used]
                   have ns := msg_Nonce_supply (msg := m)
                   obtain ⟨N₂, ns⟩ := ns
                   exists Nat.max N₁ N₂
                   simp_all
    | Gets => simp[used]; exists N₁


lemma Nonce_supply1 : ∃ N, Msg.Nonce N ∉ used evs := by
  obtain ⟨N, h⟩ := Nonce_supply_lemma
  exact ⟨N, h N (le_refl N)⟩

-- TODO is this really needed?
-- lemma Nonce_supply : Msg.Nonce (Classical.some (Nonce_supply_lemma.some_spec)) ∉ used evs := by
--   obtain ⟨N, h⟩ := Nonce_supply_lemma
--   exact h (Classical.some (Nonce_supply_lemma.some_spec)) (le_refl _)

-- Specialized Rewriting for Theorems About `analz` and Image
omit [InvKey] [Bad] in
lemma insert_Key_singleton : insert (Msg.Key K) H = Msg.Key '' {K} ∪ H := by
  simp

omit [InvKey] [Bad] in
@[simp]
lemma insert_Key_image : insert (Msg.Key K) (Msg.Key '' KK ∪ C) = Msg.Key '' (insert K KK) ∪ C := by
  rw[insert_Key_singleton, Set.image_insert_eq, Set.insert_eq, Set.union_assoc, Set.image_singleton]

omit [Bad] in
lemma Crypt_imp_keysFor :
    Msg.Crypt K X ∈ H → K ∈ symKeys → K ∈ keysFor H := by
  intro h₁ h₂
  apply invKey_K at h₂
  rw[←h₂]
  aapply Crypt_imp_invKey_keysFor

-- Lemma for the trivial direction of the if-and-only-if of the 
-- Session Key Compromise Theorem
omit [Bad] in
@[simp]
lemma analz_image_freshK_lemma :
    ((Msg.Key K ∈ analz (Msg.Key '' nE ∪ H)) →
    (K ∈ nE ∨ Msg.Key K ∈ analz H)) →
    (Msg.Key K ∈ analz (Msg.Key '' nE ∪ H)) = (K ∈ nE ∨ Msg.Key K ∈ analz H) :=
by
  intro h
  ext; constructor; assumption;
  intro h₁; simp_all; cases h₁
  · apply analz_increasing; left; simp; assumption
  · apply analz_union; right; assumption
