https://github.com/EasyCrypt/easycrypt
Raw File
Tip revision: 879d8424a60941bc3cb59fee3dfc02f03f193421 authored by Pierre-Yves Strub on 03 May 2020, 22:02:33 UTC
ax. for polynomials
Tip revision: 879d842
RP_RF.eca
(* --------------------------------------------------------------------
 * Copyright (c) - 2012--2016 - IMDEA Software Institute
 * Copyright (c) - 2012--2018 - Inria
 * Copyright (c) - 2012--2018 - Ecole Polytechnique
 *
 * Distributed under the terms of the CeCILL-B-V1 license
 * -------------------------------------------------------------------- *)

require import AllCore List FSet SmtMap Real Distr.
require import Dexcepted.
require (*--*) NewPRP WeakPRP IdealPRP.
require (*--*) NewPRF IdealPRF.
require (*--*) Birthday.

(** We assume a finite domain D, equipped with its uniform
    distribution. **)
type D.
op uD: { D distr | is_uniform uD /\ is_lossless uD /\ is_full uD} as uD_uf_fu.

lemma uD_ll:  is_lossless uD by smt(uD_uf_fu).
lemma uD_uni: is_uniform uD by smt(uD_uf_fu).
lemma uD_fu:  is_full uD by smt(uD_uf_fu).

(** and a type K equipped with a lossless distribution **)
type K.
op dK: { K distr | is_lossless dK } as dK_ll.

clone import WeakPRP as PRPt with
  type K  <- K,
    op dK <- dK,
  type D  <- D
proof * by smt(dK_ll).

clone import IdealPRP as PRPi with
  type K  <- K,
    op dK <- dK,
  type D  <- D,
    op dD <- uD
proof * by smt(dK_ll uD_ll)
rename "RandomPermutation" as "PRPi".

clone import IdealPRF as PRFi with
  type K  <- K,
  type D  <- D,
  type R  <- D,
  op   dK <- dK,
  op   dR <- uD
proof * by smt(dK_ll uD_ll)
rename "RandomFunction" as "PRFi".

op q : { int | 0 <= q } as ge0_q.

(* In the proof, we consider the following bad event (applied to the
    PRF's internal map):
      "A collision occurs in map m whenever there exist distinct x and
      x' that are both in m's domain and have the same image by m." *)
pred collision (m:(D,D) fmap) = exists x x',
  x' <> x /\
  x \in m /\ x' \in m /\ m.[x] = m.[x'].

(* Some useful facts about the bad event *)
lemma no_collision (m:(D,D) fmap):
  !collision m <=>
  forall x x',
    x' = x \/
    !x \in m  \/
    !x' \in m \/
    m.[x] <> m.[x'].
proof.
rewrite /collision negb_exists /=; apply/forall_iff=> /= x.
by rewrite negb_exists /=; apply/forall_iff=> /= x'; rewrite !negb_and.
qed.

lemma collision_add (m:(D,D) fmap) x y:
  !x \in m =>
  collision m.[x <- y] <=> collision m \/ rng m y.
proof.
move=> x_notin_m; split=> [[z z' [z'_neq_z]]|].
+ rewrite mem_set=> -[z_in_m] [z'_in_m] mz_eq_mz'.
  case (rng m y)=> //= y_notin_rngm.
  by exists z z'; smt(@SmtMap).
move=> [[z z' [z'_neq_z] [z_in_m] [z'_in_m] mz_eq_mz']|].
+ exists z z'; rewrite z'_neq_z !mem_set !get_setE mz_eq_mz' z_in_m z'_in_m /=.
  rewrite (contra _ _ (congr1 (dom m) z x)) 1:x_notin_m 1:z_in_m //=.
  by rewrite (contra _ _ (congr1 (dom m) z' x)) 1:x_notin_m 1:z'_in_m.
rewrite rngE=> - /= [x'] mx'_y.
by exists x x'; smt(@SmtMap).
qed.

lemma collision_stable (m:(D,D) fmap) y y':
  collision m =>
  y \notin m =>
  collision m.[y <- y']
by [].

(** To factor out the difficult step, we parameterize the PRP by a
    procedure that samples its output, and provide two instantiations
    of it. **)
module type Sample_t = {
  proc sample(X:D -> bool): D
}.

module Direct = {
  proc sample(X:D -> bool): D = {
    var r;

    r = $uD \ X;
    return r;
  }
}.

module Indirect = {
  proc sample(X:D -> bool): D = {
    var r;

    r = $uD;
    if (X r) {
      r = $uD \ X;
    }
    return r;
  }
}.

module PRPi'(S:Sample_t) = {
  proc init =  PRPi.init

  proc f(x:D): D = {
    if (x \notin PRPi.m)
      PRPi.m.[x] = S.sample(rng PRPi.m);
    return oget PRPi.m.[x];
  }
}.

lemma nosmt notin_supportIP (P : 'a -> bool) (d : 'a distr):
  (exists a, support d a /\ !P a) <=> mu d P < mu d predT.
proof.
rewrite (mu_split _ predT P) /predI /predT /predC /=.
rewrite (exists_eq (fun a => support d a /\ !P a) (fun a => !P a /\ a \in d)) /=.
+ by move=> a /=; rewrite andbC.
by rewrite -(witness_support (predC P)) -/(predC _) /#.
qed.

(* Some losslessness lemmas *)
lemma excepted_lossless (m:(D,D) fmap):
  (exists x, x \notin m) =>
  mu (uD \ (rng m)) predT = 1%r.
proof.
move=> /endo_dom_rng [x h]; rewrite dexcepted_ll 1:uD_ll //.
by rewrite -uD_ll; apply/notin_supportIP; exists x=> />; exact/uD_fu.
qed.

lemma excepted_lossless_mem (m:(D,D) fmap):
  (exists x, x \notin m) =>
  mu (uD \ (mem (frng m))) predT = 1%r.
proof.
have ->: mem (frng m) = rng m.
+ by apply/fun_ext=> a; rewrite mem_frng.
exact/excepted_lossless.
qed.

phoare Indirect_ll: [Indirect.sample: exists x, support uD x /\ !X x ==> true] = 1%r.
proof.
proc; seq  1: (exists x, support uD x /\ !X x)=> //=.
+ by rnd (predT); auto; rewrite uD_ll /#.
if=> //=.
+ rnd (predT); auto=> /> &m x _ x_notin_X _.
  by rewrite dexcepted_ll 1:uD_ll // -uD_ll; apply/notin_supportIP; exists x.
by hoare; auto=> />.
qed.

lemma PRPi'_Indirect_ll: islossless PRPi'(Indirect).f.
proof.
proc; if=> //=; wp; call Indirect_ll.
auto=> /> &m _.
have:= excepted_lossless (PRPi.m{m}) _.
+ by exists x{m}.
rewrite weight_dexcepted.
case (weight uD = mu uD (rng PRPi.m{m}))=> //=.
rewrite notin_supportIP /=.
by rewrite StdOrder.RealOrder.ltr_def=> -> /=; exact/mu_sub.
qed.

(** The proof is cut into 3 parts (sections):
      - We first focus on proving
         Pr[IND(PRPi'(Indirect),D).main() @ &m: res]
         <= Pr[IND(PRFi,D).main() @ &m: res]
          + Pr[IND(PRFi,D).main() @ &m: collision PRFi.m].
      - Second, we concretely bound (when the PRF oracle stops
        answering queries after the q-th):
          Pr[IND(PRFi,D).main() @ &m: collision PRFi.m]
          <= q^2 * Pr[x = $uD: x = witness]
      - We conclude by proving (difficult!)
         Pr[IND(PRPi,D).main() @ &m: res]
         = Pr[IND(PRPi'(Indirect),D).main() @ &m: res].

     Purists are then invited to turn the security statement about
     restricted oracles into a security statement about restricted
     adversaries. **)
section Upto.
  declare module D:PRF_Distinguisher {PRPi, PRFi}.
  axiom D_ll (O <: PRF_Oracle {D}): islossless O.f => islossless D(O).distinguish.

  local module PRP_indirect_bad = {
    var bad : bool

    proc init(): unit = {
             PRPi.init();
      bad <- false;
    }

    proc sample(X:D -> bool): D = {
      var r;

      r = $uD;
      if (X r) {
        bad <- true;
        r = $uD \ X;
      }
      return r;
    }

    proc f(x:D): D = {
      if (x \notin PRPi.m)
        PRPi.m.[x] = sample(rng PRPi.m);
      return oget PRPi.m.[x];
    }
  }.

  local lemma PRPi'_Indirect_eq &m:
    Pr[IND(PRPi'(Indirect),D).main() @ &m: res]
    = Pr[IND(PRP_indirect_bad,D).main() @ &m: res].
  proof. by byequiv=> //=; proc; inline *; sim. qed.

  (** Upto failure: if a collision does not occur in PRFi.m, then the
      programs are equivalent **)
  lemma pr_PRPi'_Indirect_PRFi &m:
    `|Pr[IND(PRPi'(Indirect),D).main() @ &m: res]
      - Pr[IND(PRFi,D).main() @ &m: res]|
    <= Pr[IND(PRFi,D).main() @ &m: collision PRFi.m].
  proof.
  rewrite (PRPi'_Indirect_eq &m).
  byequiv: PRP_indirect_bad.bad=> //=; 2:by smt().
  proc.
  call (_: collision PRFi.m,
           ={m}(PRPi,PRFi) /\ (PRP_indirect_bad.bad{1} <=> collision PRFi.m{2}),
           PRP_indirect_bad.bad{1} <=> collision PRFi.m{2}).
  + exact D_ll.
  + proc.
    if=> //=; inline *.
    swap{1} 1.
    seq  1  1: (={x} /\
                x{1} \notin PRPi.m{1} /\
                PRFi.m{2} = PRPi.m.[x <- r]{1} /\
                ((PRP_indirect_bad.bad \/ rng PRPi.m r){1} <=> collision PRFi.m{2})).
    + auto => /> &1 &2 coll _ x_notin_m r _; split=> [|x0 x'].
      + rewrite rngE /= /collision=> - [x'] mx'; exists x{2} x'; smt(domE get_setE).
      smt(@SmtMap).
    sp; if{1}.
    + conseq (_: _ ==> collision PRFi.m{2} /\ PRP_indirect_bad.bad{1})=> //.
      auto=> /> &1 &2 x_notin_m coll_def rng_m_r; smt.
    by auto; smt. (** FIXME: Investigate **)
    move=> &2 bad; conseq (_: true ==> true: =1%r) (_: PRP_indirect_bad.bad ==> PRP_indirect_bad.bad)=> //=.
    + by proc; if=> //=; inline *; seq  2: PRP_indirect_bad.bad; [auto|if=> //=; auto].
    proc; if=> //=; inline *.
    seq  2: (X = rng PRPi.m /\ x \notin PRPi.m) 1%r 1%r 0%r _=> //=;
         [auto|if=> //=; auto|hoare; auto]=> />;rewrite ?dD_ll //.
    by move=> ???; apply excepted_lossless; exists x{hr}.
    move=> &1; conseq (_: collision PRFi.m ==> collision PRFi.m: =1%r)=> //=.
    by proc; if; auto=> />; rewrite uD_ll //=; smt(domE get_setE).
  inline *; auto=> />; split=> [|_].
  + by rewrite no_collision=> x x'; rewrite mem_empty.
  move=> rL rR DL b mL DR mR [-> //|^ /no_collision + -> [#] -> _ _ ^ + {1}-> /= x x'].
  by move=> /(_ x x') [->|[->|[->|->]]].
  qed.
end section Upto.

(** We now bound the probability of collisions by instantiating a
    generic Birthday Bound result:
      Pr[IND(PRFi,DBounder(D)).main() @ &m: collision PRFi.m]
      <= q^2 * Pr[x = $uD: x = witness],

    where DBounder prevents the distinguisher from calling the
    f-oracle more than q times. **)
module DBounder (D:PRF_Distinguisher,F:PRF_Oracle) = {
  module FBounder = {
    var c:int

    proc f(x:D): D = {
      var r = witness;

      if (c < q) {
        r = F.f(x);
        c = c + 1;
      }
      return r;
    }
  }

  module D = D(FBounder)

  proc distinguish(): bool = {
    var b;

    FBounder.c = 0;
    b = D.distinguish();
    return b;
  }
}.

section CollisionProbability.
  declare module D:PRF_Distinguisher {PRFi, DBounder}.
  axiom D_ll (O <: PRF_Oracle {D}): islossless O.f => islossless D(O).distinguish.

  local clone import Birthday as BBound with
    op   q <- q,
    type T <- D,
    op   uT <- uD,
    op maxu <- witness
  proof *.
  realize ge0_q by apply ge0_q.
  realize maxuP. 
  proof.
  move=> x;apply StdOrder.RealOrder.lerr_eq.
  case: uD_uf_fu => uni [ll fu];apply uni;apply fu.
  qed.

  (* We construct a Birthday Bound adversary from the IND
     experiment. *)
  local module (A:Adv)(S:ASampler) = {
    (* We simulate an f-oracle using the s-oracle *)
    module F = {
      proc init = PRFi.init

      proc f(x:D): D = {
        if (x \notin PRFi.m) {
          PRFi.m.[x] = S.s();
        }
        return oget PRFi.m.[x];
      }
    }

    (* Recall from the Birthday clone that Birthday Bound adversaries
       are restricted to make at most q oracle queries. *)
    module IND = IND(F,DBounder(D))

    proc a(): unit = {
      var b:bool;
      b = IND.main();
    }
  }.

  local lemma A_ll (S <: ASampler {A}) &m: islossless S.s => islossless A(S).a.
  proof.
  move=> S_ll; proc; inline*; wp.
  call (_: true).
  + exact D_ll.
  + by proc; inline*; do!(sp; if=> //=; auto); wp; call S_ll.
  by inline*; auto.
  qed.

  local hoare A_bounded: A(Sample).a: size Sample.l = 0 ==> size Sample.l <= q.
  proof.
  proc; inline *; wp.
  call (_: size Sample.l <= DBounder.FBounder.c /\ DBounder.FBounder.c <= q).
  + by proc; inline *; do !(sp; if=> //=); auto=> /#.
  by auto; smt w=ge0_q.
  qed.

  local lemma pr_PRFi_Exp_collision &m:
    Pr[IND(PRFi,DBounder(D)).main() @ &m: collision PRFi.m]
    = Pr[Exp(Sample,A).main() @ &m: !uniq Sample.l].
  proof.
  byequiv (_: ={glob D} ==> collision PRFi.m{1} <=> !uniq Sample.l{2})=> //=.
  proc; inline*; wp.
  call (_: ={PRFi.m,DBounder.FBounder.c} /\
           (forall x, mem (frng PRFi.m) x <=> mem Sample.l x){2} /\
           (collision PRFi.m{1} <=> !uniq Sample.l{2})).
    proc; inline*.
    sp; if=> //=.
    sp; if=> //=; auto.
    progress [-split].
    rewrite H3 //=; split.
    + move=> x0; rewrite -H !mem_frng !rngE /=; split=> [[x]|].
      + by rewrite get_setE /#.
      case=> [<<-|[x'] mx'].
      + by exists x{2}; rewrite get_setE.
      by exists x'; rewrite get_setE /#.
    by rewrite negb_and /= collision_add // -mem_frng H H0 orbC.
  auto=> />; split=> [x|].
  search rng empty.
  + by rewrite mem_frng mem_rng_empty.
  smt.
  qed.

  lemma pr_PRFi_collision &m:
    Pr[IND(PRFi,DBounder(D)).main() @ &m: collision PRFi.m]
    <= (q^2)%r * mu1 uD witness.
  proof.
  rewrite (pr_PRFi_Exp_collision &m) (pr_collision A A_ll A_bounded &m).
  qed.
end section CollisionProbability.

(* We pull together the results of the first two sections *)
lemma PartialConclusion (D <: PRF_Distinguisher {PRPi, PRFi, DBounder}) &m:
  (forall (O <: PRF_Oracle {D}), islossless O.f => islossless D(O).distinguish) =>
  `|Pr[IND(PRPi'(Indirect),DBounder(D)).main() @ &m: res]
    - Pr[IND(PRFi,DBounder(D)).main() @ &m: res]|
  <= (q^2)%r * mu1 uD witness.
proof.
move=> D_ll.
have:= pr_PRFi_collision D D_ll &m.
have:= pr_PRPi'_Indirect_PRFi (DBounder(D)) _ &m.
  move=> O O_ll; proc.
  call (D_ll (<: DBounder(D,O).FBounder) _).
    by proc; sp; if=> //=; wp; call O_ll.
  by auto.
smt.
qed.

(** This section proves the equivalence between the Ideal PRP and the
    module PRPi'(Indirect) used in section Upto. **)
section PRPi_PRPi'_Indirect.
  (* The key is in proving that Direct.sample and Indirect.sample
     define the same distribution. We do this by extensional equality
     of distributions:
       forall a, Pr[Direct.sample: res = a] = Pr[Indirect.sample: res = a]. *)
  equiv eq_Direct_Indirect: Direct.sample ~ Indirect.sample: ={X} ==> ={res}.
  proof.
  bypr (res{1}) (res{2})=> //. (* Pointwise equality of distributions *)
  progress.
  (* We first perform the computation on the easy side,... *)
  cut ->: Pr[Direct.sample(X{1}) @ &1: res = a] = mu (uD \ X{1}) (pred1 a).
    byphoare (_: X = X{1} ==> _)=> //=.
    by proc; rnd=> //=; auto.
  subst X{1}.
  (* ... and we are left with the difficult side *)
  byphoare (_: X = X{2} ==> _)=> //=.
  (* We deal separately with the case where a is in X and thus has
     probability 0 of being sampled) *)
  case (X{2} a)=> [a_in_X | a_notin_X].
    conseq (_: _ ==> _: 0%r); first smt.
    proc.
    seq  1: (X r)
            _ 0%r
            _ 0%r
            (X = X{2}).
      by auto.
      by rcondt 1=> //=; rnd=> //=; skip; smt.
      by rcondf 1=> //=; hoare; skip; smt.
      done.
  (* And we are now left with the case where a is not in X *)
  proc.
    alias 2 r0 = r.
    (* There are two scenarios that lead to a = r:
         - r0 = a /\ r = a (with probability mu uD (pred1 a));
         - r0 <> a /\ r = a (with probability mu uD (mem X) * mu (uD \ X) (pred1 a)). *)
    phoare split (mu uD (pred1 a)) (mu uD X * mu (uD \ X) (pred1 a)): (r0 = a).
      (* Bound *)
      move=> />.
      rewrite dexcepted1E a_notin_X /=. 
      cut not_empty: 0%r < mu uD predT - mu uD X{2}.
        rewrite -mu_not.
        cut: 0%r < mu uD (predC X{2}); last smt.
        by rewrite witness_support; exists a; rewrite /predC /= a_notin_X /= uD_fu.       
      smt. (** Investigate **)
      (* case r0 = a *)
      seq  2: (a = r0) (mu uD (pred1 a)) 1%r _ 0%r (r0 = r /\ X = X{2}).
        by auto.
        by wp; rnd; skip; progress; rewrite pred1E.
        by rcondf 1.
        by hoare; conseq (_: _ ==> true)=> //=; smt.
        done.
    (* case r0 <> a *)
    seq  2: (!X r)
            _                 0%r
            (mu uD X) (mu (uD \ X) (pred1 a))
            (r0 = r /\ X = X{2}).
      by auto.
      by hoare; rcondf 1=> //=; skip; smt.
      by wp; rnd.
      rcondt 1=> //=; rnd (pred1 a).
      by skip; smt.
      done.
  qed.

  (* The rest is easy *)
  local equiv eq_PRPi_PRPi'_Indirect: PRPi.f ~ PRPi'(Indirect).f:
    ={x, PRPi.m} ==> ={res, PRPi.m}.
  proof.
  transitivity PRPi'(Direct).f (={PRPi.m,x} ==> ={PRPi.m,res}) (={PRPi.m,x} ==> ={PRPi.m,res}).
  + by move=> &1 &2 [->> ->>]; exists PRPi.m{2} x{2}.
  + done.
  + by proc; inline *; if=> //=; auto.
  + by proc; if=> //=; wp; call eq_Direct_Indirect.
  qed.

  declare module D:PRF_Distinguisher {PRPi}.

  lemma pr_PRPi_PRPi'_Indirect &m:
    Pr[IND(PRPi,D).main() @ &m: res] = Pr[IND(PRPi'(Indirect),D).main() @ &m: res].
  proof.
  byequiv=> //=.
  proc.
  call (_: ={PRPi.m}).
    by apply eq_PRPi_PRPi'_Indirect.
  by inline*; auto.
  qed.
end section PRPi_PRPi'_Indirect.

lemma Conclusion (D <: PRF_Distinguisher {PRPi, PRFi, DBounder}) &m:
  (forall (O <: PRF_Oracle {D}), islossless O.f => islossless D(O).distinguish) =>
  `|Pr[IND(PRPi,DBounder(D)).main() @ &m: res]
    - Pr[IND(PRFi,DBounder(D)).main() @ &m: res]|
  <= (q^2)%r * mu1 uD witness.
proof.
move=> D_ll.
by rewrite (pr_PRPi_PRPi'_Indirect (DBounder(D)) &m) (PartialConclusion D &m D_ll).
qed.
back to top