Section hF.
Variable b k c : nat.
Variable h : Bvector c -> Bvector b -> Bvector c.
Variable F : Bvector k -> list (Bvector b) -> Bvector b.
Definition hF key m :=
[k_Out, k_In] <-2 splitVector c k key;
h k_Out (F k_In m).
Variable A : OracleComp (list (Bvector b)) (Bvector c) bool.
Hypothesis A_wf : well_formed_oc A.
(* Step 0: inline and simplify *)
Definition G0 :=
k_in <-$ {0, 1}^k;
k_out <-$ {0, 1}^c;
[b, _] <-$2 A _ _ (fun (_ : unit) m => ret (h k_out (F k_in m), tt)) tt;
ret b.
(* Step 1: replace h with a random function *)
Definition G1 :=
k_in <-$ {0, 1}^k;
[b, _] <-$2 A _ _
(fun (s : list (Bvector b * Bvector c)) m =>
randomFunc ({0, 1}^c) _ s (F k_in m)) nil;
ret b.
(* Step 2: from cAU, identical random function inputs imply identical messages.  So this is equivalent to a random function with the domain of messages. *)
Definition G2 :=
[b, _] <-$2 A _ _
(fun (s : list (list (Bvector b) * Bvector c)) m =>
randomFunc ({0, 1}^c) _ s m) nil;
ret b.
Definition G0_1 :=
[k_out, k_in] <-$2 (
key <-$ {0, 1}^(c + k);
ret splitVector c k key);
[b, _] <-$2 A _ _ (fun (_ : unit) m => ret (h k_out (F k_in m), tt)) tt;
ret b.
unfold PRF_G_A, G0_1.
inline_first.
comp_skip.
unfold f_oracle, hF.
remember (splitVector c k x) as z.
comp_simp.
reflexivity.
Qed.
unfold G0_1, G0.
eapply eqRat_trans.
eapply evalDist_seq_eq.
eapply Rnd_split_equiv.
intros.
eapply eqRat_refl.
inline_first.
comp_swap_r.
comp_skip.
inline_first.
comp_skip.
Qed.
rewrite G0_1_equiv.
apply G0_1_G0_equiv.
Qed.
Definition hF_oracle k_in (s : unit) m : OracleComp (Bvector b) (Bvector c) (Bvector c * unit) :=
r <--$ OC_Query _ (F k_in m);
$ ret (r, tt).
(* Need to use OC_Run here because hF_oracle calls F before querying the oracle. *)
Definition PRF_h_A : OracleComp (Bvector b) (Bvector c) bool :=
k_in <--$$ {0, 1}^k;
[r, _] <--$2 OC_Run _ _ _ A (hF_oracle k_in) tt;
$ ret r.
Definition G1_1 :=
k_out <-$ { 0 , 1 }^c;
[b, _] <-$2 PRF_h_A _ _ (f_oracle h _ k_out) tt;
ret b.
Definition G1_2 :=
[b, _] <-$2 PRF_h_A _ _ (randomFunc ({0, 1}^c) _) nil;
ret b.
Local Opaque evalDist.
unfold G0, G1_1.
comp_swap_l.
comp_skip.
unfold PRF_h_A.
simpl.
inline_first.
comp_skip.
comp_simp.
inline_first.
unfold f_oracle.
eapply comp_spec_eq_impl_eq.
comp_skip.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun a b => True)).
trivial.
intuition.
inline_first.
comp_simp.
inline_first.
comp_simp.
simpl.
eapply comp_spec_ret; intuition.
simpl in *.
intuition; subst.
comp_simp.
simpl.
inline_first.
comp_simp.
eapply comp_spec_eq_refl.
Qed.
reflexivity.
Qed.
unfold G1_2, G1.
unfold PRF_h_A.
simpl.
inline_first.
comp_skip.
inline_first.
eapply comp_spec_eq_impl_eq.
comp_skip.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun a b => snd a = b)).
trivial.
intuition.
subst.
inline_first.
simpl.
eapply comp_spec_eq_trans_r.
2: eapply comp_spec_right_ident.
comp_skip.
apply (oneVector c).
apply (oneVector c).
inline_first.
simpl.
eapply comp_spec_ret; intuition.
simpl in *.
intuition; subst.
inline_first.
comp_simp.
simpl.
eapply comp_spec_eq_refl.
Qed.
rewrite G0_G1_1_equiv.
rewrite <- G1_2_G1_equiv.
eapply G1_1_2_close.
Qed.
Definition randomFunc_mem (D R : Set)(eqdd : EqDec D)(eqdr : EqDec R)(RndR : Comp R) (ls : list (D * R))d :=
match (arrayLookup _ ls d) with
| Some r =>
ret (r, (d, r) :: ls)
| None =>
r <-$ RndR; ret (r, (d, r) :: ls)
end.
intuition.
unfold randomFunc, randomFunc_mem.
rewrite H.
case_eq (arrayLookup eqdd x2 a ); intuition.
eapply comp_spec_ret; intuition.
simpl.
case_eq (eqb x a); intuition.
rewrite eqb_leibniz in H1.
subst.
intuition.
comp_skip.
eapply comp_base_exists; intuition.
eapply comp_base_exists; intuition.
eapply comp_spec_ret; intuition.
simpl.
rewrite H.
trivial.
Qed.
Definition G2_1 :=
k_in <-$ {0, 1}^k;
[b, _] <-$2 A _ _ (fun s d => randomFunc_mem _ _ ({0, 1}^c) s (F k_in d)) nil;
ret b.
unfold G1, G2_1.
comp_skip.
eapply comp_spec_eq_impl_eq.
comp_skip.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun a b => forall x, arrayLookup _ a x = arrayLookup _ b x));
intuition.
eapply comp_spec_symm.
eapply comp_spec_consequence.
eapply randomFunc_mem_spec; intuition.
intuition.
comp_simp.
simpl in *.
intuition.
subst.
eapply comp_spec_eq_refl.
Qed.
Fixpoint arrayLookup_f (A B C : Set)(eqd : EqDec B)(ls : list ((A * B) * C))(a : B) :=
match ls with
| nil => None
| ((x, z), y) :: ls' =>
if (eqb a z) then (Some y) else (arrayLookup_f _ ls' a)
end.
Definition F_randomFunc k_in f d :=
match arrayLookup_f _ f (F k_in d) with
| Some r => ret (r, ((d, F k_in d), r) :: f)
| None => r <-$ {0, 1}^c; ret (r, ((d, F k_in d), r) :: f)
end.
Definition G2_2 :=
k_in <-$ {0, 1}^k;
[b, _] <-$2 A _ _ (F_randomFunc k_in) nil;
ret b.
induction x1; inversion 1; intuition; simpl in *.
subst.
destruct a.
destruct a2.
simpl in *.
subst.
destruct p.
simpl.
destruct (eqb a0 b0 ); intuition.
Qed.
unfold G2_1, G2_2.
comp_skip.
eapply comp_spec_eq_impl_eq.
comp_skip.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun a b => list_pred (fun c d =>  fst c = (snd (fst d)) /\ snd c = snd d) a b)).
econstructor.
intuition.
unfold randomFunc_mem, F_randomFunc.
erewrite arrayLookup_f_equiv ; eauto.
destruct (arrayLookup_f (Bvector_EqDec b) x2 (F x a) ); intuition.
eapply comp_spec_ret; intuition.
simpl.
econstructor.
simpl.
intuition.
trivial.
comp_skip.
apply (oneVector c).
apply (oneVector c).
eapply comp_spec_ret; intuition.
simpl.
econstructor.
intuition.
eauto.
comp_simp.
simpl in *; intuition.
subst.
eapply comp_spec_eq_refl.
Qed.
Fixpoint findCollision_1 (A B : Set)(eqda : EqDec A)(eqdb : EqDec B)(ls : list (A * B))(a : A)(b : B) :=
match ls with
| nil => None
| (a', b') :: ls' => if
(eqb b b' && (negb (eqb a a')))
then (Some a') else (findCollision_1 _ _ ls' a b)
end.
Definition collidesWith (A B : Set)(eqda : EqDec A)(eqdb : EqDec B)(ls : list (A * B))(a : A) b :=
if (findCollision_1 _ _ ls a b) then true else false.
Fixpoint findCollision(A B : Set)(eqda : EqDec A)(eqdb : EqDec B)(ls : list (A * B)) : option (A * A * B) :=
match ls with
| nil => None
| (a, b) :: ls' => let a' := (findCollision_1 _ _ ls' a b) in
match a' with
| None =>  (findCollision _ _ ls')
| Some p' => Some (a, p', b)
end
end.
Definition funcCollision (A B : Set)(eqda : EqDec A)(eqdb : EqDec B)(ls : list (A * B)) :=
if (findCollision _ _ ls) then true else false.
Definition G2_3 :=
k_in <-$ {0, 1}^k;
[b, f] <-$2 A _ _ (F_randomFunc k_in) nil;
ret (b, funcCollision _ _ (fst (split f))).

Theorem G2_2_3_equiv :
Pr[G2_2] == Pr[x <-$ G2_3; ret fst x].