Section ElGamal.
(* Assume a finite cyclic group.   This command brings several names into the environment, including 'order' which describes the order of the group. *)
Context`{FCG : FiniteCyclicGroup}.
(* FiniteCyclicGroup is from a generic group theory library.  To use any type in FCF, equality must be decidable for that type.  Assume that equality is decidable for group elements. This fact will be located automatically when needed. *)
Hypothesis GroupElement_EqDec : EqDec GroupElement.
(* The ElGamal key generation procedure.  The notation [ .. order) is from the RndNat library, and it produces a uniformly-random natural number in the specified interval.  *)
Definition ElGamalKeygen :=
m <-$ [0 .. order);
ret (m, g^m).
(* The ElGamal encryption procedure. *)
Definition ElGamalEncrypt(msg key : GroupElement) :=
m <-$ [0 .. order);
ret (g^m, key^m * msg).
(* The ElGamal decryption procedure. *)
Definition ElGamalDecrypt(key : nat)(ct : GroupElement * GroupElement) :=
[c1, c2] <-2 ct;
s <- c1^key;
(inverse s) * c2.
unfold ElGamalKeygen, ElGamalEncrypt, ElGamalDecrypt.
intuition.
(* simp_in_support is an automated tactic that breaks down hypotheses that state that some value is in the support of some distribution.  The resulting hypotheses will be more atomic and more directly useful.  *)
repeat simp_in_support.
rewrite <- associativity.
repeat rewrite groupExp_mult.
rewrite mult_comm.
rewrite left_inverse.
apply left_identity.
Qed.
(* Assume an adversary composed of two procedures that share state.  We also need to assume that equality is decidable for the type of this state. *)
Variable A_State : Set.
Hypothesis A_State_EqDec : EqDec A_State.
Variable A1 : GroupElement -> Comp (GroupElement * GroupElement * A_State).
Variable A2 : (GroupElement * GroupElement * A_State) -> Comp bool.
(* It is necessary to assume that the adversary procedures are "well-formed."  That is, they terminate with probability 1. *)
Hypothesis wfA1 : forall x, well_formed_comp (A1 x).
Hypothesis wfA2 : forall x, well_formed_comp (A2 x).
(* Build an adversary from A1 and A2 that can win DDH *)
Definition B(g_xyz : (GroupElement * GroupElement * GroupElement)) : Comp bool :=
[gx, gy, gz] <-3 g_xyz;
[p0, p1, s] <-$3 A1(gx);
b <-$ {0,1};
pb <- if b then p0 else p1;
c <- (gy, gz * pb);
b' <-$ (A2 (c, s));
ret (b ?= b').
(* Specialize the DDH games to our group.  Note that definitions can also used modules or classes to make these explicit specializations unnecessary. *)
Definition DDH0 := (@DDH0 _ _ _ _ _ g order).
Definition DDH1 := (@DDH1 _ _ _ _ _ g order).
Definition DDH_Advantage := (@DDH_Advantage _ _ _ _ _ g order).
unfold IND_CPA_Advantage, DDH_Advantage, DiffieHellman.DDH_Advantage.
(* The goal is an equality on distances.  We will prove the equality of each pair of terms, which implies equality of the distances. *)
Abort.
unfold IND_CPA_G, DDH0, DiffieHellman.DDH0, ElGamalKeygen, ElGamalEncrypt, B.
(* Inline the first computation in the game on the left hand side. *)
inline_first.
(* Remove the first pair of terms, and assign the result of both to a single (new) variable.  *)
comp_skip.
(* apply the "dist_inline" tactic to the program on the right at location 1 (where 0 is the first statement).  We have to use %nat here because FCF is in rat_scope by default, and we want this 1 to be interpreted as a nat. *)
comp_at dist_inline rightc 1%nat.
(* Swap the first two statement of the program on the right. *)
comp_swap rightc.
comp_skip.
destruct x0.
destruct p.
comp_at dist_inline rightc 1%nat.
comp_swap rightc.
comp_skip.
comp_inline leftc.
comp_skip.
comp_inline rightc.
comp_skip.
rewrite groupExp_mult; intuition.
(* Simplify using left identity. *)
comp_simp.
(* The goal is an equality on two (obviously equal) rational numbers.  This equality is registered as a setoid in Coq, so it can be proved automatically using the intuition tactic. *)
intuition.
Qed.
(* The next equality is proved using a short sequence involving two intermediate games.  RndG produces a random group element as described in the RndGrpElem module.  *)
Definition G1 :=
gx <-$ RndG;
gy <-$ RndG;
[p0, p1, s] <-$3 (A1 gx);
b <-$ {0, 1};
gz' <-$ (
pb <- if b then p0 else p1;
gz <-$ RndG ; ret (gz * pb));
b' <-$ (A2 (gy, gz', s));
ret (b ?= b').
Definition G2 :=
gx <-$ RndG;
gy <-$ RndG;
[p0, p1, s] <-$3 (A1 gx);
gz <-$ RndG ;
b' <-$ (A2 (gy, gz, s));
b <-$ {0, 1};
ret (b ?= b').

Theorem ElGamal_G1_DDH1 :
Pr [G1] == Pr [DDH1 B].