import InductiveVerification.Public
set_option diagnostics true

-- The Needham-Schroeder Public-Key Protocol
namespace NS_Public

variable [InvKey]
variable [Bad]
open Msg
open Event
open Bad
open HasInitState
open InvKey

-- Define the inductive set `ns_public`
inductive ns_public : List Event → Prop
| Nil : ns_public []
| Fake : ns_public evsf →
    X ∈ synth (analz (spies evsf)) →
    ns_public (Says Agent.Spy B X :: evsf)
| NS1 : ns_public evs1 →
    Nonce NA ∉ used evs1 →
    ns_public (Says A B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) :: evs1)
| NS2 : ns_public evs2 →
    Nonce NB ∉ used evs2 →
    Says A' B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) ∈ evs2 →
    ns_public (Says B A (Crypt (pubEK A) ⦃Nonce NA, Nonce NB, Agent B⦄) :: evs2)
| NS3 : ns_public evs3 →
    Says A B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) ∈ evs3 →
    Says B' A (Crypt (pubEK A) ⦃Nonce NA, Nonce NB, Agent B⦄) ∈ evs3 →
    ns_public (Says A B (Crypt (pubEK B) (Nonce NB)) :: evs3)

-- A "possibility property": there are traces that reach the end
theorem possibility_property :
  ∃ NB, ∃ evs, ns_public evs ∧ Says A B (Crypt (pubEK B) (Nonce NB)) ∈ evs := by
  exists 1
  exists [ Says A B (Crypt (pubEK B) (Nonce 1)),
           Says B A (Crypt (pubEK A) ⦃Nonce 0, Nonce 1, Agent B⦄),
           Says A B (Crypt (pubEK B) ⦃Nonce 0, Agent A⦄),
         ]
  constructor
  · apply ns_public.NS3
    · apply ns_public.NS2
      · apply_rules [ns_public.NS1, ns_public.Nil, Nonce_notin_used_empty]
      · simp
      · left
    all_goals tauto
  · simp

-- Spy never sees another agent's private key unless it's bad at the start
set_option trace.aesop true
@[simp]
theorem Spy_see_priEK {h : ns_public evs} :
  (Key (priEK A) ∈ parts (spies evs)) ↔ A ∈ bad := by
  constructor
  -- · induction h <;> aesop (add norm spies, norm knows, norm initState, norm pubEK, norm priEK, norm pubSK, norm priSK, norm injective_publicKey)
  · induction h with
    | Nil => simp[spies, knows, initState]; intro h; cases h with
             | inl h => cases h with
               | inl => aesop (add norm pubEK, norm pubSK, safe forward injective_publicKey)
               | inr h => simp[pubEK, priEK] at h; cases h with
                | intro _ h => apply publicKey_neq_privateKey at h; contradiction
             | inr h => simp[pubSK, priEK] at h; cases h with
               | intro _ h => apply publicKey_neq_privateKey at h; contradiction
    | Fake _ h ih => apply Fake_parts_sing at h
                     simp[spies, knows]
                     intro h₁; apply ih;
                     cases h₁ with
                      | inl h₁ => apply h at h₁; cases h₁ with 
                        | inl h₁ => cases h₁; aapply analz_subset_parts
                        | inr => assumption
                      | inr => assumption
    | NS1 _ _ ih => aesop (add norm spies, norm knows)
    | NS2 _ _ _ ih => aesop (add norm spies, norm knows)
    | NS3 _ _ _ ih => rw[spies, knows]; simp; intro _; apply ih; grind
  · intro h₁; apply parts_increasing; aapply Spy_spies_bad_privateKey

@[simp]
theorem Spy_analz_priEK {h : ns_public evs} :
  Key (priEK A) ∈ analz (spies evs) ↔ A ∈ bad := by
  constructor
  · intro h₁; apply analz_subset_parts at h₁; aapply Spy_see_priEK.mp
  · intro h₁; apply analz_increasing; aapply Spy_spies_bad_privateKey

-- Lammata for some very specific recurring cases in the following proof
lemma no_nonce_NS1_NS2_helper1
{h : synth (analz (spies evsf)) ⦃Nonce NA, Msg.Agent A⦄}
: Nonce NA ∈ analz (knows Agent.Spy evsf) := by
  cases h with
  | inj => aapply analz.fst
  | mpair n => cases n; assumption

lemma no_nonce_NS1_NS2_helper2
{ih : Crypt (pubEK C) ⦃NA', ⦃Nonce NA, Msg.Agent D⦄⦄ ∈ parts (spies evsf)
  → Crypt (pubEK B) ⦃Nonce NA, Msg.Agent A⦄ ∈ parts (spies evsf)
  → Nonce NA ∈ analz (spies evsf)}
{h : parts {X} ⊆ synth (analz (spies evsf)) ∪ parts (spies evsf)}
{h₁ : Crypt (pubEK C) ⦃NA', ⦃Nonce NA, Msg.Agent D⦄⦄ ∈ parts (knows Agent.Spy evsf)}
{h₂ : Crypt (pubEK B) ⦃Nonce NA, Msg.Agent A⦄ ∈ parts {X} ∨
      Crypt (pubEK B) ⦃Nonce NA, Msg.Agent A⦄ ∈ parts (knows Agent.Spy evsf)}
: Nonce NA ∈ analz (knows Agent.Spy evsf) := by
  apply ih at h₁; cases h₂ with
  | inl h₂ => apply h at h₂; cases h₂ with
    | inl h₂ => cases h₂ with
      | inj => apply h₁; aapply analz_subset_parts
      | crypt h₂ => aapply no_nonce_NS1_NS2_helper1
    | inr => aapply h₁
  | inr => aapply h₁

-- It is impossible to re-use a nonce in both NS1 and NS2, provided the nonce is secret
theorem no_nonce_NS1_NS2 { evs: List Event} { h : ns_public evs  } {A B C D : Agent} :
  (Crypt (pubEK C) ⦃NA', Nonce NA, Agent D⦄ ∈ parts (spies evs) →
  (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄ ∈ parts (spies evs) →
  Nonce NA ∈ analz (spies evs))) := by
  intro h₁ h₂
  induction h with
  | Nil => rw[spies, knows] at h₂; simp[initState] at h₂
  | Fake _ h ih => 
      apply Fake_parts_sing at h
      simp[spies, knows] at h₁
      apply analz_insert; right;
      cases h₁ with 
      | inl h₁ => simp_all; apply h at h₁; cases h₁ with
        | inl h₁ => cases h₁ with
          | inj h₁ => apply analz_subset_parts at h₁
                      aapply no_nonce_NS1_NS2_helper2
          | crypt h₁ => cases h₁ with
            | inj => apply analz.fst; aapply analz.snd
            | mpair _ h₁ => aapply no_nonce_NS1_NS2_helper1
        | inr h₁ => aapply no_nonce_NS1_NS2_helper2
      | inr h₁ => simp[spies, knows] at h₂; aapply no_nonce_NS1_NS2_helper2
  | NS1 =>
    simp[spies] at h₁; simp[spies] at h₂; cases h₂ with
    | inl h => rcases h with ⟨_ , ⟨n , _⟩⟩;
               apply parts.body at h₁; apply parts.snd at h₁; apply parts.fst at h₁
               apply parts_knows_Spy_subset_used at h₁; rw[n] at h₁; contradiction
    | inr => rw[spies, knows]; apply analz_mono; apply Set.subset_insert; simp_all
  | NS2 =>
    simp[spies] at h₁; simp[spies] at h₂; cases h₁ with
    | inl h => rcases h with ⟨_, ⟨_, ⟨n, _⟩⟩⟩
               apply parts.body at h₂; apply parts.fst at h₂
               apply parts_knows_Spy_subset_used at h₂; rw[n] at h₂; contradiction
    | inr => rw[spies, knows]; apply analz_mono; apply Set.subset_insert; simp_all
  | NS3 => rw[spies, knows]; apply analz_mono; apply Set.subset_insert; simp_all

lemma unique_Nonce_apply_ih {P : Prop}
{h₃ : Nonce NA ∉ analz (spies (Says Agent.Spy C X :: evsf))}
{h₁ : M₁ ∈ parts (spies evsf)}
{h₂ : M₂ ∈ parts (spies evsf)}
{a_ih : M₁ ∈ parts (spies evsf)
  → M₂ ∈ parts (spies evsf)
  → Nonce NA ∉ analz (spies evsf) → P}
: P := by
  simp[spies, knows] at h₃; apply Set.notMem_subset at h₃
  · aapply a_ih;
  · apply analz_mono; apply Set.subset_insert    

lemma unique_NA_apply_ih {P : Prop}
{a_ih : Crypt (pubEK B) ⦃Nonce NA, Msg.Agent A⦄ ∈ parts (spies evsf)
  → Crypt (pubEK B') ⦃Nonce NA, Msg.Agent A'⦄ ∈ parts (spies evsf)
  → Nonce NA ∉ analz (spies evsf) → P}
{h₃ : Nonce NA ∉ analz (spies (Says Agent.Spy C X :: evsf))}
{h₁ : Crypt (pubEK B) ⦃Nonce NA, Msg.Agent A⦄ ∈ parts (spies evsf)}
{h₂ : Crypt (pubEK B') ⦃Nonce NA, Msg.Agent A'⦄ ∈ parts (spies evsf)}
: P := by
  aapply unique_Nonce_apply_ih (h₁ := h₁) (h₂ := h₂)

lemma unique_NA_contradict
{h₃ : Nonce NA ∉ analz (spies (Says Agent.Spy B X :: evsf))}
{h₂ : synth (analz (spies evsf)) ⦃Nonce NA, Msg.Agent A'⦄}
{P : Prop}
: P := by
  apply MPair_synth_analz.mp at h₂; rcases h₂ with ⟨⟨n⟩, m⟩;
  simp[spies, knows] at h₃; apply Set.notMem_subset at h₃
  · contradiction;
  · apply analz_mono; apply Set.subset_insert 

-- Unicity for NS1: nonce NA identifies agents A and B
theorem unique_NA { h : ns_public evs } :
  (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄ ∈ parts (spies evs) →
  (Crypt (pubEK B') ⦃Nonce NA, Agent A'⦄ ∈ parts (spies evs) →
  (Nonce NA ∉ analz (spies evs) →
  A = A' ∧ B = B'))) := by
  induction h with
  | Nil => aesop (add norm spies, norm knows, safe analz_insertI)
  | Fake _ a a_ih =>
    apply Fake_parts_sing at a; intro h₁ h₂ h₃;
    simp[spies, knows] at h₁; cases h₁ with
    | inl h₁ => apply a at h₁; cases h₁ with
      | inl h₁ => cases h₁ with
        | inj h₁ => simp[spies, knows] at h₂; cases h₂ with
          | inl h₂ => apply a at h₂; cases h₂ with
            | inl h₂ => cases h₂ with
              | inj h₂ => apply analz_subset_parts at h₁
                          apply analz_subset_parts at h₂
                          aapply unique_NA_apply_ih   
              | crypt h₂ => aapply unique_NA_contradict
            | inr h₂ => apply analz_subset_parts at h₁
                        aapply unique_NA_apply_ih
          | inr h₂ => apply analz_subset_parts at h₁
                      aapply unique_NA_apply_ih
        | crypt h₁ => aapply unique_NA_contradict
      | inr h₁ => simp[spies] at h₁; simp[spies, knows] at h₂; cases h₂ with
        | inl h₂ => apply a at h₂; cases h₂ with
          | inl h₂ => cases h₂ with
            | inj h₂ => apply analz_subset_parts at h₂
                        aapply unique_NA_apply_ih
            | crypt => aapply unique_NA_contradict
          | inr => aapply unique_NA_apply_ih
        | inr => aapply unique_NA_apply_ih
    | inr => simp[spies, knows] at h₂; cases h₂ with
      | inl h₂ => apply a at h₂; cases h₂ with
        | inl h₂ => cases h₂ with
          | inj h₂ => apply analz_subset_parts at h₂
                      aapply unique_NA_apply_ih
          | crypt => aapply unique_NA_contradict
        | inr => aapply unique_NA_apply_ih
      | inr => aapply unique_NA_apply_ih
  | NS1 _ _ a_ih => intro h₁ h₂ h₃; simp at h₁; cases h₁ with
    | inl h₁ => simp at h₂; cases h₂ with
      | inl h₂ => rcases h₁ with ⟨h₁, _⟩; rcases h₂ with ⟨h₂, _⟩;
                  apply injective_publicKey at h₁;
                  apply injective_publicKey at h₂;
                  simp_all;
      | inr h₂ => apply parts.body at h₂; apply parts.fst at h₂; 
                  apply parts_knows_Spy_subset_used at h₂; simp_all;
    | inr h₁ => simp at h₂; cases h₂ with
      | inl h₂ => apply parts.body at h₁; apply parts.fst at h₁;
                  apply parts_knows_Spy_subset_used at h₁; simp_all;
      | inr => aapply unique_NA_apply_ih;
  | NS2 _ _ _ a_ih => intro h₁ h₂ h₃; simp_all; apply a_ih
                      apply Set.notMem_subset at h₃
                      · apply h₃;
                      · apply_rules [analz_mono, Set.subset_insert]
  | NS3 _ _ _ a_ih => intro h₁ h₂ h₃; simp_all; apply a_ih; 
                      apply Set.notMem_subset at h₃
                      · apply h₃;
                      · apply_rules [analz_mono, Set.subset_insert]

-- Spy does not see the nonce sent in NS1 if A and B are secure
theorem Spy_not_see_NA { h : ns_public evs  }
  { not_bad_A : A ∉ bad }
  { not_bad_B : B ∉ bad } :
  Says A B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) ∈ evs →
  Nonce NA ∉ analz (spies evs) := by
  intro h₁ h₄
  induction h with
  | Nil => simp_all
  | Fake _ a a_ih => 
    apply Fake_analz_insert at a; apply a at h₄; simp_all; cases h₁ with
    | inl h => rcases h with ⟨l, _⟩; simp_all [Spy_in_bad];
    | inr h => cases h₄ with
      | inl h₄ => cases h₄; apply a_ih at h; contradiction;
      | inr => apply a_ih at h; contradiction;
  | NS1 _ a a_ih => simp_all; cases h₁ with
    | inl h => rcases h with ⟨_, ⟨_, ⟨_, ⟨h, _⟩⟩⟩⟩; simp_all; apply a
               apply parts_knows_Spy_subset_used
               aapply analz_subset_parts
    | inr h => apply analz_insert_Crypt_subset at h₄; cases h₄ with
      | inl h₄ => contradiction; 
      | inr h₄ => simp at h₄; cases h₄
                  · simp_all; apply a; apply Says_imp_used at h;
                    apply used_parts_subset_parts at h; simp at h; apply h;
                    tauto;
                  · simp_all;
  | NS2 _ not_used_NB a a_ih =>
    cases h₁ with | tail _ b =>
      have _ := h₄
      simp at h₄; apply analz_insert_Crypt_subset at h₄
      simp at h₄; rcases h₄ with ( h | ( h | h ))
      · simp at a_ih; have c := b; apply a_ih at c; rw[h] at b;
        have _ := c; rw[h] at c;
        apply Says_imp_parts_knows_Spy at b
        apply Says_imp_parts_knows_Spy at a
        apply unique_NA at b; apply b at a; apply a at c; simp_all
        assumption
      · rw [h] at b
        apply not_used_NB; apply parts_knows_Spy_subset_used; apply parts.fst;
        apply parts.body; apply Says_imp_parts_knows_Spy; assumption
      · aapply a_ih
  | NS3 _ a₁ a₂ a_ih => 
    cases h₁ with | tail _ b =>
      have _ := h₄
      simp at h₄; apply analz_insert_Crypt_subset at h₄
      simp at h₄; rcases h₄ with ( h | ( h | h ))
      · have c := b; have d := a₁; have e := a₂
        rw[h] at b; apply Says_imp_parts_knows_Spy at b
        apply Says_imp_parts_knows_Spy at a₂
        aapply a_ih; apply no_nonce_NS1_NS2
        · assumption
        · rw[h]; exact a₂
        · rw[h]; exact b
      · aapply a_ih; aapply analz.inj
      · aapply a_ih; aapply analz.fst
      · aapply a_ih; aapply analz.snd
      · aapply a_ih; aapply analz.decrypt
      
-- Authentication for `A`: if she receives message 2 and has used `NA` to start a run, then `B` has sent message 2.
theorem A_trusts_NS2 {h : ns_public evs }
  { not_bad_A : A ∉ bad }
  { not_bad_B : B ∉ bad } :
  Says A B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) ∈ evs →
  Says B' A (Crypt (pubEK B) ⦃Nonce NA, Nonce NB, Agent B⦄) ∈ evs →
  Says B A (Crypt (pubEK B) ⦃Nonce NA, Nonce NB, Agent B⦄) ∈ evs
:= by
   intro h₁ h₂;
   -- have snsNA := h₁; apply Spy_not_see_NA at snsNA <;> try assumption
   apply Says_imp_parts_knows_Spy at h₂
   -- use unique_NA to show that B' = B
   induction h with
  | Nil => simp_all
  | Fake _ a a_ih => 
    have snsNA := h₁; apply Spy_not_see_NA at snsNA <;> try assumption
    cases h₁
    · have _ := Spy_in_bad; contradiction
    · right; simp at h₂; cases h₂ with
    | inl h₂ => apply Fake_parts_sing at a; apply a at h₂; cases h₂ with
      | inl h₂ => aapply a_ih; cases h₂ with
        | inj => aapply analz_subset_parts
        | crypt h => apply False.elim; apply snsNA
                     apply MPair_synth_analz.mp at h; rcases h with ⟨⟨l⟩ , _⟩; 
                     aapply analz_spies_mono
      | inr h₂ => aapply a_ih;
    | inr => aapply a_ih;
    · aapply ns_public.Fake
  | NS1 _ a a_ih => right; simp at h₂; cases h₁
                    · apply False.elim; apply a
                      apply parts_knows_Spy_subset_used; apply parts.fst
                      aapply parts.body
                    · aapply a_ih; 
  | NS2 _ _ a a_ih => simp at h₁; have b := h₁; have snsNA := h₁
                      apply Spy_not_see_NA at snsNA <;> try assumption
                      simp at h₂; cases h₂ with
      | inl h => apply Says_imp_parts_knows_Spy at a
                 apply Says_imp_parts_knows_Spy at b
                 rcases h with ⟨e₁ , ⟨e₂ , ⟨e₃, e₄⟩⟩⟩
                 apply unique_NA at a
                 rw[e₂] at b; rw[e₂] at snsNA
                 apply a at b
                 apply b at snsNA
                 simp_all[-e₄]; assumption
      | inr => right; aapply a_ih
  | NS3 _ _ a a_ih => simp at h₁; simp at h₂; right; aapply a_ih
  
-- If the encrypted message appears then it originated with Alice in `NS1`
lemma B_trusts_NS1 { h : ns_public evs} :
  Crypt (pubEK B) ⦃Nonce NA, Agent A⦄ ∈ parts (spies evs) →
  Nonce NA ∉ analz (spies evs) →
  Says A B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) ∈ evs
:= by
  intro h₁ h₂
  induction h with
  | Nil => simp[spies] at h₁; rw[knows] at h₁; simp[initState] at h₁
  | Fake _ a a_ih => simp at h₁; cases h₁ with
    | inl h₁ => apply Fake_parts_sing at a; apply a at h₁; cases h₁ with
      | inl h₁ => cases h₁ with
        | inj h₁ => right; apply analz_subset_parts at h₁; aapply a_ih
                    aapply analz_spies_mono_neg
        | crypt h₁ => apply False.elim;
                      apply MPair_synth_analz.mp at h₁
                      rcases h₁ with ⟨⟨h₁⟩, _⟩; aapply analz_spies_mono_neg
      | inr => right; aapply a_ih; aapply analz_spies_mono_neg
    | inr h₁ => right; aapply a_ih; aapply analz_spies_mono_neg
  | NS1 _ _ a_ih => simp at h₁; cases h₁ with
    | inl h₁ => rcases h₁ with ⟨e₁, ⟨e₂, e₃⟩⟩; apply injective_publicKey at e₁
                simp_all
    | inr => right; aapply a_ih; aapply analz_spies_mono_neg
  | NS2 _ _ _ a_ih => simp at h₁; right; aapply a_ih; aapply analz_spies_mono_neg
  | NS3 _ _ _ a_ih => simp at h₁; right; aapply a_ih; aapply analz_spies_mono_neg
  
-- Authenticity Properties obtained from `NS2`

-- Helper lemmas for unique_NB
lemma unique_NB_apply_ih {P : Prop}
{ a_ih :
  Crypt (pubEK A) ⦃Nonce NA, ⦃Nonce NB, Msg.Agent B⦄⦄ ∈ parts (spies evsf) →
    Crypt (pubEK A') ⦃Nonce NA', ⦃Nonce NB, Msg.Agent B'⦄⦄ ∈ parts (spies evsf) →
      Nonce NB ∉ analz (spies evsf) → P }
{ h₁ : Crypt (pubEK A) ⦃Nonce NA, ⦃Nonce NB, Msg.Agent B⦄⦄ ∈ parts (spies evsf) }
{ h₂ : Crypt (pubEK A') ⦃Nonce NA', ⦃Nonce NB, Msg.Agent B'⦄⦄ ∈ parts (spies evsf) }
{ h₃ : Nonce NB ∉ analz (spies (Says Agent.Spy B X :: evsf)) }
: P := by
  aapply unique_Nonce_apply_ih (h₁ := h₁) (h₂ := h₂) (h₃ := h₃)

lemma unique_NB_contradict
{ h₃ : Nonce NB ∉ analz (spies (Says Agent.Spy B X :: evsf)) }
{ h₂ : synth (analz (spies evsf)) ⦃Nonce NA', ⦃Nonce NB, Msg.Agent B'⦄⦄ }
{P : Prop}
: P := by
  apply MPair_synth_analz.mp at h₂; apply False.elim
  rcases h₂ with ⟨_, r⟩; cases r with
    | inj r => aapply analz_spies_mono_neg; aapply analz.fst
    | mpair r₁ _ => cases r₁; aapply analz_spies_mono_neg

-- Unicity for `NS2`: nonce `NB` identifies nonce `NA` and agent `A`
theorem unique_NB { h : ns_public evs } :
  (Crypt (pubEK A) ⦃Nonce NA, Nonce NB, Agent B⦄ ∈ parts (spies evs) →
  (Crypt (pubEK A') ⦃Nonce NA', Nonce NB, Agent B'⦄ ∈ parts (spies evs) →
  (Nonce NB ∉ analz (spies evs) →
  A = A' ∧ NA = NA' ∧ B = B'))) := by
  -- Proof closely follows that of unique_NA
  induction h with
  | Nil => aesop (add norm spies, norm knows, safe analz_insertI)
  | Fake _ a a_ih =>
    apply Fake_parts_sing at a; intro h₁ h₂ h₃;
    simp[spies, knows] at h₁; cases h₁ with
    | inl h₁ => apply a at h₁; cases h₁ with
      | inl h₁ => cases h₁ with
        | inj h₁ => simp[spies, knows] at h₂; cases h₂ with
          | inl h₂ => apply a at h₂; cases h₂ with
            | inl h₂ => cases h₂ with
              | inj h₂ => apply analz_subset_parts at h₁
                          apply analz_subset_parts at h₂
                          aapply unique_NB_apply_ih
              | crypt h₂ => aapply unique_NB_contradict
            | inr h₂ => apply analz_subset_parts at h₁
                        aapply unique_NB_apply_ih
          | inr h₂ => apply analz_subset_parts at h₁
                      aapply unique_NB_apply_ih
        | crypt h₁ => aapply unique_NB_contradict
      | inr h₁ => simp[spies] at h₁; simp[spies, knows] at h₂; cases h₂ with
        | inl h₂ => apply a at h₂; cases h₂ with
          | inl h₂ => cases h₂ with
            | inj h₂ => apply analz_subset_parts at h₂
                        aapply unique_NB_apply_ih
            | crypt => aapply unique_NB_contradict
          | inr => aapply unique_NB_apply_ih
        | inr => aapply unique_NB_apply_ih
    | inr => simp[spies, knows] at h₂; cases h₂ with
      | inl h₂ => apply a at h₂; cases h₂ with
        | inl h₂ => cases h₂ with
          | inj h₂ => apply analz_subset_parts at h₂
                      aapply unique_NB_apply_ih
          | crypt => aapply unique_NB_contradict
        | inr => aapply unique_NB_apply_ih
      | inr => aapply unique_NB_apply_ih
  | NS1 _ _ a_ih => intro h₁ h₂ h₃; simp at h₁; simp at h₂; aapply a_ih
                    aapply analz_spies_mono_neg
  | NS2 _ _ _ a_ih => intro h₁ h₂ h₃; simp at h₁; simp at h₂; cases h₁ with
    | inl h₁ => rcases h₁ with ⟨e₁, _⟩; apply injective_publicKey at e₁
                cases h₂ with
      | inl h₂ => rcases h₂ with ⟨e₂, _⟩; apply injective_publicKey at e₂
                  simp_all
      | inr h₂ => apply parts.body at h₂; apply parts.snd at h₂
                  apply parts.fst at h₂; apply parts_knows_Spy_subset_used at h₂;
                  simp_all;
    | inr h₁ => cases h₂ with
      | inl h₂ => apply parts.body at h₁; apply parts.snd at h₁
                  apply parts.fst at h₁; apply parts_knows_Spy_subset_used at h₁;
                  simp_all
      | inr => aapply a_ih; aapply analz_spies_mono_neg
  | NS3 _ _ _ a_ih => intro h₁ h₂ h₃; simp_all; apply a_ih; 
                      apply Set.notMem_subset at h₃
                      · apply h₃;
                      · apply_rules [analz_mono, Set.subset_insert]

-- `NB` remains secret 
theorem Spy_not_see_NB { h : ns_public evs }
  { not_bad_A : A ∉ bad }
  { not_bad_B : B ∉ bad } :
  Says B A (Crypt (pubEK A) ⦃Nonce NA, Nonce NB, Agent B⦄) ∈ evs →
  Nonce NB ∉ analz (spies evs)
:= by
  intro h₁ h₄
  induction h with
  | Nil => simp_all
  | Fake _ a a_ih => 
    apply Fake_analz_insert at a; apply a at h₄; simp_all; cases h₁ with
    | inl h => rcases h with ⟨l, _⟩; simp_all [Spy_in_bad];
    | inr h => cases h₄ with
      | inl h₄ => cases h₄; apply a_ih at h; contradiction;
      | inr => apply a_ih at h; contradiction;
  | NS1 _ a a_ih => simp at h₁; simp[spies, knows] at h₄
                    apply analz_insert_Crypt_subset at h₄; simp at h₄
                    cases h₄ with
    | inl e => rw[e] at h₁; apply a; apply parts_knows_Spy_subset_used
               apply parts.fst; apply parts.snd; apply parts.body
               aapply Says_imp_parts_knows_Spy
    | inr => aapply a_ih
  | NS2 _ not_used_NB a a_ih => simp at h₁; simp[spies, knows] at h₄; 
                                cases h₁ with
    | inl h => rcases h with ⟨_, ⟨_, ⟨e₃, _⟩⟩⟩; apply injective_publicKey at e₃;
               simp_all; apply not_used_NB; apply parts_knows_Spy_subset_used;
               aapply analz_subset_parts
    | inr h => apply analz_insert_Crypt_subset at h₄; simp at h₄; cases h₄ with
      | inl e => aapply a_ih; rw[e]; apply Says_imp_parts_knows_Spy at a
                 apply Says_imp_parts_knows_Spy at h; rw[e] at h
                 aapply no_nonce_NS1_NS2
      | inr h => cases h
                 · simp_all; apply not_used_NB; apply parts_knows_Spy_subset_used
                   apply parts.fst; apply parts.snd; apply parts.body
                   aapply Says_imp_parts_knows_Spy
                 · aapply a_ih
  | @NS3 evs3 _ B' _ _ _ _ a₁ a₂ a_ih => 
    cases h₁ with | tail _ b =>
      simp at h₄; by_cases bad_B' : Key (invKey (pubEK B')) ∈ analz (spies evs3)
      · have aC := bad_B'; apply analz_subset_parts at bad_B'
        apply Spy_see_priEK.mp at bad_B'; have c := b; apply a_ih at c; 
        apply analz_insert_Decrypt at aC; rw[aC] at h₄; simp at h₄; cases h₄ with
    | inl h₄ =>
        apply Says_imp_parts_knows_Spy at a₂
        apply Says_imp_parts_knows_Spy at b; rw[h₄] at b
        apply unique_NB at a₂; apply a₂ at b;
        rw[h₄] at c; simp_all; assumption
    | inr h₄ => aapply a_ih
      · apply analz_Crypt at aC; rw[aC] at h₄; simp at h₄; aapply a_ih;

-- Authentication for `B`: if he receives message 3 and has used `NB` in message 2, then `A` has sent message 3.
theorem B_trusts_NS3 { h : ns_public evs } 
  { not_bad_A : A ∉ bad }
  { not_bad_B : B ∉ bad } :
  Says B A (Crypt (pubEK A) ⦃Nonce NA, Nonce NB, Agent B⦄) ∈ evs →
  Says A' B (Crypt (pubEK B) (Nonce NB)) ∈ evs →
  Says A B (Crypt (pubEK B) (Nonce NB)) ∈ evs
:= by 
  intro h₁ h₂
  apply Says_imp_parts_knows_Spy at h₂
  induction h with
  | Nil => simp_all
  | Fake _ a a_ih => right; simp at h₁; simp at h₂; cases h₁ with
    | inl => simp_all[Spy_in_bad]
    | inr h₁ => cases h₂ with
      | inl h₂ => apply Fake_parts_sing at a; apply a at h₂; cases h₂ with
        | inl h₂ => simp at h₂; cases h₂ with
          | inj => aapply a_ih; aapply analz_subset_parts;
          | crypt h₂ => cases h₂; apply Spy_not_see_NB at h₁ <;> simp_all
        | inr => aapply a_ih
      | inr => aapply a_ih
  | NS1 _ a a_ih => right; simp at h₂; simp at h₁; aapply a_ih;
  | NS2 _ _ a a_ih => right; simp at h₁; simp at h₂; cases h₁ with
    | inl => apply parts.body at h₂; apply parts_knows_Spy_subset_used at h₂
             simp_all
    | inr => aapply a_ih
  | NS3 _ a₁ a₂ a_ih => simp at h₁; simp at h₂; cases h₂ with
    | inl h₂ => simp_all; left; rcases h₂ with ⟨e₁, _⟩
                apply injective_publicKey at e₁; simp_all
                have h₁c := h₁
                apply Says_imp_parts_knows_Spy at h₁
                apply Says_imp_parts_knows_Spy at a₂
                apply unique_NB at h₁; apply h₁ at a₂
                apply Spy_not_see_NB at h₁c
                apply a₂ at h₁c
                all_goals simp_all
    | inr => right; aapply a_ih

-- Overall guarantee for `B`
  
-- If NS3 has been sent and the nonce NB agrees with the nonce B joined with NA, then A initiated the run using NA
theorem B_trusts_protocol { h : ns_public evs  }
  { not_bad_A : A ∉ bad }
  { not_bad_B : B ∉ bad } :
  Crypt (pubEK B) (Nonce NB) ∈ parts (spies evs) →
  Says B A (Crypt (pubEK A) ⦃Nonce NA, Nonce NB, Agent B⦄) ∈ evs →
  Says A B (Crypt (pubEK B) ⦃Nonce NA, Agent A⦄) ∈ evs := by
  intro h₁ h₂
  induction h with
  | Nil => simp_all
  | Fake _ a a_ih => right; simp at h₁; simp at h₂; cases h₂ with
    | inl => simp_all[Spy_in_bad]
    | inr h₂ => cases h₁ with
      | inl h₁ => apply Fake_parts_sing at a; apply a at h₁; cases h₁ with
        | inl h₁ => simp at h₁; cases h₁ with
          | inj => aapply a_ih; aapply analz_subset_parts
          | crypt h₁ => cases h₁; apply Spy_not_see_NB at h₂ <;> simp_all
        | inr => aapply a_ih
      | inr => aapply a_ih
  | NS1 _ a a_ih => right; simp at h₂; simp at h₁; aapply a_ih;
  | NS2 _ _ a a_ih => right; simp at h₁; simp at h₂; cases h₂ with
    | inl => apply parts.body at h₁; apply parts_knows_Spy_subset_used at h₁
             simp_all
    | inr => aapply a_ih
  | NS3 _ a₁ a₂ a_ih => simp at h₁; simp at h₂; cases h₁ with
    | inl h₁ => simp_all; rcases h₁ with ⟨e₁, _⟩
                apply injective_publicKey at e₁; simp_all
                have h₂c := h₂
                apply Says_imp_parts_knows_Spy at h₂
                apply Says_imp_parts_knows_Spy at a₂
                apply unique_NB at h₂; apply h₂ at a₂
                apply Spy_not_see_NB at h₂c
                apply a₂ at h₂c
                all_goals simp_all
    | inr => right; aapply a_ih

end NS_Public
