Section PRF_Encryption_IND_CPA_concrete.
Variable State : Set.
Hypothesis State_EqDec : EqDec State.
Variable A1 : OracleComp Plaintext Ciphertext (Plaintext * Plaintext * State).
Variable A2 : State -> Ciphertext -> OracleComp Plaintext Ciphertext bool.
Hypothesis A1_wf : well_formed_oc A1.
Hypothesis A2_wf : forall x y, well_formed_oc (A2 x y).
Variable q1 q2 : nat.
Hypothesis A1_qam : queries_at_most A1 q1.
Hypothesis A2_qam : forall s c, queries_at_most (A2 s c) q2.
Definition PRFE_EncryptOracle := EncryptOracle PRFE_Encrypt _.
Local Open Scope nat_scope.
(* Step 1: Inline some definitions and simplify. *)
Definition G1 :=
key <-$ PRFE_KeyGen;
[b, _] <-$2
(
[p0, p1, s_A] <--$3 A1;
b <--$$ {0, 1};
pb <- if b then p1 else p0;
c <--$$ PRFE_Encrypt key pb;
b' <--$ (A2 s_A c);
$ ret (eqb b b')
)
_ _
(PRFE_EncryptOracle key) tt;
ret b.
reflexivity.
Qed.
(* Step 2 : Replace the PRF with a random function. *)
Definition PRFE_RandomFunc := @randomFunc (Bvector eta) (Bvector eta) ({0,1}^eta) _.
Definition RF_Encrypt s p :=
r <-$ {0, 1} ^ eta;
[pad, s] <-$2 PRFE_RandomFunc s r;
ret (r, p xor pad, s).
Definition G2 :=
[a, o] <-$2 A1 _ _ (RF_Encrypt) nil;
[p0, p1, s_A] <-3 a;
b <-$ {0, 1};
pb <- if b then p1 else p0;
[c, o] <-$2 RF_Encrypt o pb;
[b', o] <-$2 (A2 s_A c) _ _ RF_Encrypt o;
ret (eqb b b').
(* To prove this step, we put the game in the form of a computation that access the PRF as an oracle.  This allows us to apply the PRF definition. *)
Definition PRFE_Encrypt_OC (x : unit)(p : Plaintext) : OracleComp (Bvector eta) (Bvector eta) (Ciphertext * unit) :=
r <--$$ {0, 1} ^ eta;
pad <--$ OC_Query _ r;
$ (ret (r, p xor pad, tt)).
Definition PRF_A : OracleComp (Bvector eta) (Bvector eta) bool :=
[a, n] <--$2 OC_Run _ _ _ A1 PRFE_Encrypt_OC tt;
[p0, p1, s_A] <-3 a;
b <--$$ {0, 1};
pb <- if b then p1 else p0;
r <--$$ {0, 1} ^ eta;
pad <--$ OC_Query _ r;
c <- (r, pb xor pad);
z <--$ OC_Run  _ _ _ (A2 s_A c) PRFE_Encrypt_OC n;
[b', _] <-2 z;
$ ret (eqb b b').
(* Later we will need to reason about the cost of PRF_A, so we make an equivalent procedure without some of the complications. *)
Definition PRF_A' :=
z <--$
OC_Run _ _ _ A1 PRFE_Encrypt_OC tt;
b <--$$
(m <-$ { 0 , 1 }^1; ret Vector.hd m);
r <--$$ { 0 , 1 }^eta;
pad <--$ OC_Query (Vector.t bool eta) r;
z0 <--$
OC_Run _ _ _ (A2 (snd (fst z)) (r,
(if b then  (snd (fst (fst z)))  else (fst (fst (fst z))))
xor pad)) PRFE_Encrypt_OC (snd z);
$ ret eqb b (fst z0).
intuition.
unfold PRF_A, PRF_A'.
do 5 (comp_simp; simpl;
comp_skip; try eapply eqRat_refl).
comp_simp; simpl.
comp_simp.
intuition.
Qed.
Definition f_oracle(k : Bvector eta)(x : unit)(v : Bvector eta) :=
ret (f k v, tt).
apply (oneVector eta, oneVector eta).
Qed.
Hint Resolve Ciphertext_inh : inhabited.
intuition.
unfold PRFE_EncryptOracle, EncryptOracle, PRFE_Encrypt.
simpl.
prog_inline_l.
do 2 prog_inline_r.
comp_skip.
prog_simp.
unfold f_oracle.
do 2 (prog_inline_r;
prog_simp).
eapply comp_spec_ret.
simpl.
intuition.
Qed.
intuition.
unfold RF_Encrypt, PRFE_RandomFunc, randomFunc.
simpl.
do 2 prog_inline_r.
comp_skip.
prog_simp.
prog_inline_r.
comp_skip.
prog_inline_r.
prog_simp.
eapply comp_spec_ret.
simpl.
intuition.
Qed.
Opaque PRFE_Encrypt_OC.
unfold G1, PRF_A, PRFE_KeyGen.
comp_skip.
simpl.
inline_first.
eapply comp_spec_impl_eq.
eapply (@comp_spec_seq _ _
(fun a1 a2 => fst a1 = fst a2 /\ snd a1 = fst (snd a2))); eauto with inhabited.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun a b => a = fst b)).
trivial.
intuition.
subst.
eapply PRFE_EncryptOracle_spec.
intuition.
destruct b2.
simpl in *.
subst.
prog_simp.
simpl.
do 2 prog_inline_l.
do 2 prog_inline_r.
comp_skip.
prog_simp.
unfold PRFE_Encrypt.
do 3 prog_inline_l.
do 2 (prog_inline_r; prog_simp).
comp_skip.
prog_simp.
unfold f_oracle.
prog_inline_l.
do 3 (prog_inline_r; prog_simp).
eapply comp_spec_seq; eauto with inhabited.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _
(fun a b => a = fst b)).
trivial.
intuition.
subst.
eapply PRFE_EncryptOracle_spec.
intuition.
destruct b4; simpl in *; subst.
prog_simp.
simpl.
prog_inline_l; prog_simp.
prog_inline_r; prog_simp.
eapply comp_spec_ret.
intuition.
Qed.
intuition.
comp_skip.
comp_skip.
eapply PRF_A'_equiv.
Qed.
pose proof RF_EncryptOracle_spec as RFE_spec.
unfold G2, PRF_A.
simpl.
inline_first.
eapply comp_spec_impl_eq.
eapply (@comp_spec_seq _ _
(fun a1 a2 => fst a1 = fst a2 /\ snd a1 = snd (snd a2))); eauto with inhabited.
eapply comp_spec_consequence.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun a b => a = snd b)).
trivial.
intuition.
subst.
eapply RFE_spec.
intuition.
intuition.
prog_simp.
simpl.
do 2 prog_inline_r.
comp_skip.
unfold RF_Encrypt at 1.
prog_simp.
prog_inline_l.
do 2 prog_inline_r.
comp_skip.
prog_simp.
simpl in *.
subst.
prog_inline_l.
prog_inline_r.
comp_skip.
inversion H3; clear H3; subst.
do 2 prog_inline_r.
eapply comp_spec_seq; eauto with inhabited.
eapply (@oc_comp_spec_eq _ _ _ _ _ _ _ _ _ _ _ _ _ _ (fun x y => x = snd y)).
trivial.
intuition.
subst.
eapply RFE_spec.
intuition.
prog_simp.
simpl.
prog_inline_r.
simpl.
prog_simp.
eapply comp_spec_ret.
simpl in *.
intuition.
subst.
intuition.
subst; intuition.
Qed.
intuition.
comp_skip.
eapply PRF_A'_equiv.
Qed.
rewrite G1_PRF_A_equiv.
rewrite G1_PRF_A'_equiv.
rewrite G2_PRF_A_equiv.
rewrite G2_PRF_A'_equiv.
reflexivity.
Qed.
(* Step 3 : replace the PRF output with random value *)
(* This is identical to G2 as long as the adversary does not encounter the encryption nonce during a query. *)
Definition G3 :=
[a, o] <-$2 A1 _ _ (RF_Encrypt) nil;
[p0, p1, s_A] <-3 a;
b <-$ {0, 1};
pb <- if b then p1 else p0;
r <-$ {0, 1}^eta;
pad <-$ {0, 1}^eta;
c <- (r, pb xor pad);
[b', o] <-$2 (A2 s_A c) _ _ RF_Encrypt o;
ret (eqb b b').
(* start with G2 and move the challenge encryption sampling to the front. *)
Definition G2_1 :=
r <-$ {0, 1}^eta;
[a, o] <-$2 A1 _ _ (RF_Encrypt) nil;
[p0, p1, s_A] <-3 a;
b <-$ {0, 1};
pb <- if b then p1 else p0;
[pad, o] <-$2 PRFE_RandomFunc o r;
c <- (r, pb xor pad);
[b', o] <-$2 (A2 s_A c) _ _ RF_Encrypt o;
ret (eqb b b').
unfold G2, G2_1.
do 2 (comp_swap_r; comp_skip; comp_simp).
unfold RF_Encrypt.
do 2(inline_first; comp_skip; comp_simp).
intuition.
Qed.
(* We will modify the procedure one adversary call at a time.  Start with the first one. *)
(* make the bad event visible. *)
Definition G2_2 :=
r <-$ {0, 1}^eta;
[a, o] <-$2 A1 _ _ (RF_Encrypt) nil;
bad <- if (arrayLookup _ o r) then true else false;
[p0, p1, s_A] <-3 a;
b <-$ {0, 1};
pb <- if b then p1 else p0;
[pad, o] <-$2 PRFE_RandomFunc o r;
c <- (r, pb xor pad);
[b', o] <-$2 (A2 s_A c) _ _ RF_Encrypt o;
ret (eqb b b', bad).

Theorem G2_1_2_equiv :
Pr[G2_1] == Pr[x <-$ G2_2; ret fst x].