https://github.com/EasyCrypt/easycrypt
Raw File
Tip revision: 2075873964476cccac0722f071bfaf1252c3c2ac authored by François Dupressoir on 10 May 2022, 11:31:18 UTC
Birthday refinements
Tip revision: 2075873
Birthday.ec
(* -------------------------------------------------------------------- *)
require import AllCore List Distr Ring.
require import StdRing StdOrder StdBigop FelTactic.
require (*--*) Mu_mem.
(*---*) import RField IntOrder RealOrder.

(** An input type **)
type in_t.

(** An output type out_t with an input-parameterised distribution **)
type out_t.

op dout: in_t -> out_t distr.

(**
(** A further transformation under which we catch collisions **)
type fin_t.

op f : out_t -> fin_t.
op pr_predict : { real | forall x y, mu1 (dmap (dout x) f) y <= pr_predict }
  as predictP.
**)
(** A module that samples in `dout x` on queries `s(x)` **)
module Sample = {
  var l : out_t list

  proc init(): unit = {
    l <- [];
  }

  proc s(x : in_t): out_t = {
    var r;

    r <$ dout x;
    l <- r::l;
    return r;
  }
}.

module type Sampler = {
  proc init(): unit
  proc s(_ : in_t): out_t
}.

(** Adversaries that may query an s oracle **)
module type ASampler = {
  include Sampler [s]
}.

module type Adv (S : ASampler) = {
  proc a(): unit
}.

(** And an experiment that initializes the sampler and runs the adversary **)
module Exp (S : Sampler) (A : Adv) = {
  proc main(): unit = {
    S.init();
    A(S).a();
  }
}.

(** Forall adversary A that makes at most q queries to its s oracle,
    the probability that the same output is sampled twice is bounded
    by q^2/|T|                                                        **)
section.
  declare module A <: Adv {-Sample}.
  declare axiom A_ll (S <: ASampler {-A}): islossless S.s => islossless A(S).a.

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

  declare type fin_t.
  declare op f : out_t -> fin_t.

  declare op pr_predict : { real | forall x y, mu1 (dmap (dout x) f) y <= pr_predict }
    as predictP.

  lemma pr_Sample_le &m:
       Pr[Exp(Sample,A).main() @ &m : size Sample.l <= q /\ !uniq (map f Sample.l)]
    <= (q * (q - 1))%r/2%r * pr_predict.
  proof.
  fel 1 (size Sample.l) (fun x, x%r * pr_predict) q (!uniq (map f Sample.l)) []=> //.
  + by rewrite -Bigreal.BRA.mulr_suml  Bigreal.sumidE 1:ge0_q.
  + by inline*; auto.
  + proc;wp; rnd (mem (map f Sample.l) \o f); skip=> // /> &hr ???.
    rewrite -dmapE -(size_map f).
    apply: (Mu_mem.mu_mem_le_size (map f Sample.l{hr}) (dmap (dout x{hr}) f) pr_predict). 
    by move=> x _;rewrite predictP.
  by move=> c; proc; auto=> /#.
  qed.

  lemma pr_Sample_le_q2 &m:
    Pr[Exp(Sample,A).main() @ &m: size Sample.l <= q /\ !uniq (map f Sample.l)]
    <= (q^2)%r * pr_predict.
  proof.
  apply (ler_trans _ _ _ (pr_Sample_le &m)). 
  apply ler_wpmul2r.
  + exact/(ler_trans _ _ _ _ (predictP witness witness))/ge0_mu.
  have -> : q^2 = q*q by ring.
  smt(ge0_q).
  qed.

  declare axiom A_bounded: hoare [A(Sample).a : size Sample.l = 0 ==> size Sample.l <= q].

  local lemma aux &m :
      Pr[Exp(Sample,A).main() @ &m: !uniq (map f Sample.l)]
    = Pr[Exp(Sample,A).main() @ &m: size Sample.l <= q /\ !uniq (map f Sample.l)].
  proof.
  byequiv (: ={glob A} ==> ={Sample.l} /\ size Sample.l{2} <= q)=> //=.
  conseq (: _ ==> ={Sample.l}) _ (: _ ==> size Sample.l <= q)=> //=;2:by sim.
  by proc; call A_bounded; inline *; auto.
  qed.

  lemma pr_collision &m:
       Pr[Exp(Sample,A).main() @ &m: !uniq (map f Sample.l)]
    <= (q * (q - 1))%r / 2%r * pr_predict.
  proof. by rewrite (aux &m); exact: (pr_Sample_le &m). qed.

  lemma pr_collision_q2 &m:
       Pr[Exp(Sample,A).main() @ &m: !uniq (map f Sample.l)]
    <= (q^2)%r * pr_predict.
  proof. by rewrite (aux &m); apply (pr_Sample_le_q2 &m). qed.

end section.

(*** The same result using a bounding module ***)
abstract theory BoundedOracle.
op q : { int | 0 <= q } as ge0_q.

module Bounder (S : Sampler) = {
  var c : int

  proc init(): unit = {
         S.init();
    c <- 0;
  }

  proc s(x : in_t): out_t = {
    var r <- witness;

    if (c < q) {
      r <@ S.s(x);
      c <- c + 1;
    }
    return r;
  }
}.

module ABounder (S : ASampler) = {
  proc s(x : in_t): out_t = {
    var r <- witness;

    if (Bounder.c < q) {
      r         <@ S.s(x);
      Bounder.c <- Bounder.c + 1;
    }
    return r;
  }
}.

module Bounded (A : Adv) (S : ASampler) = {
  proc a(): unit = {
    Bounder.c <- 0;
    A(ABounder(S)).a();
  }
}.

equiv PushBound (S <: Sampler {-Bounder}) (A <: Adv {-S,-Bounder}):
  Exp(Bounder(S),A).main ~ Exp(S,Bounded(A)).main:
    ={glob A, glob S} ==> ={glob A,glob S}.
proof. by proc; inline *; sim. qed.

(** Forall adversary A with access to the bounded s oracle, the
    probability that the same output is sampled twice is bounded by
    q^2/|T|                                                         **)
section.
  declare module A <: Adv {-Sample, -Bounder}.
  declare axiom A_ll (S <: ASampler {-A}): islossless S.s => islossless A(S).a.

  declare type fin_t.
  declare op f : out_t -> fin_t.

  declare op pr_predict : { real | forall x y, mu1 (dmap (dout x) f) y <= pr_predict }
    as predictP.


  lemma pr_collision_bounded_oracles &m:
       Pr[Exp(Bounder(Sample),A).main() @ &m: !uniq (map f Sample.l)]
    <= (q^2)%r * pr_predict.
  proof.
  have ->: Pr[Exp(Bounder(Sample),A).main() @ &m: !uniq (map f Sample.l)] =
           Pr[Exp(Sample,Bounded(A)).main() @ &m: !uniq (map f Sample.l)].
  + by byequiv (PushBound Sample A).
  apply (pr_collision_q2 (Bounded(A)) _ _ ge0_q _ _ predictP _ &m)=> //.
  + move=> S HS; proc; call (A_ll (ABounder(S)) _); 2:by auto.
    by proc; sp; if; auto; call HS.
  move=> /=; proc; call (: size Sample.l <= Bounder.c <= q).
  + by proc; sp; if=> //; inline *; auto=> /#.
  by auto; smt(ge0_q).
  qed.

end section.
end BoundedOracle.
back to top