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.
Focus 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.
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.

Theorem arrayLookup_f_equiv :
forall (A B C : Set) (eqdb : EqDec B) (x1 : list (B * C)) (x2 : list (A * B * C)) (a : B),
list_pred
(fun c0 d =>
fst c0 = snd (fst d) /\ snd c0 = snd d) x1 x2 ->
arrayLookup _ x1 a = arrayLookup_f _ x2 a.