Theory KZG_poly_bind

theory KZG_poly_bind

imports KZG_correct CryptHOL_ext "tDL_assumption"
 "Berlekamp_Zassenhaus.Finite_Field_Factorization" "Elimination_Of_Repeated_Factors.ERF_Algorithm"

begin

section ‹Polynomial Binding of the KZG›
text ‹We show that the KZG is polynomial binding for every polynomial of degree <= max\_deg.
We use the Sigma\_Commit\_Crypto template to prove the binding game.
The proof is adapted from Appendix C.1 of the original KZG paper citeKZG10.›

locale KZG_CS_binding = KZG_PCS_correct
begin

subsection ‹t-DL game›
text ‹We reduce the polynomial binding to the t-DL assumption›

sublocale t_DL_Gp: t_DL Gp max_deg "of_int_mod_ring  int" "pow_mod_ring Gp"
  unfolding t_DL_def 
  by (rule Gp.cyclic_group_axioms)

text ‹Intuetively what we want to show is that if we have an adversary that can compute two 
polynomials such that they have the same commitment in polynomial time, we can construct an 
algorithm, using that adversary, that can solve the t-DL in polynomial time, thus breaking the 
t-DL assumption.›

text ‹This functions purpose is to extract $\alpha$ based on the inputs $g^\alpha$ and $\phi$, where $\phi$ has a root at $\alpha$. 
The function factorizes $\phi$ and filters for all roots. Since $\alpha$'s mod\_ring is of the same cardinality 
as g's group's order, we can conclude that if $g^r = g^\alpha$ then $r=\alpha$›
fun find_α_square_free :: "'a  'e mod_ring poly  'e mod_ring" where
  "find_α_square_free g_pow_α φ = (let (c, polys) = finite_field_factorization φ;
    deg1_polys = filter (λf. degree f = 1) polys;
    root_list = map (λp. poly.coeff p 0) deg1_polys;
    α_roots = filter (λr. g_pow_α = gGp^Gp-r) root_list
in -α_roots!0)"

text ‹The radical is executable via the formalization of the 
'Elimination of Repeated Factors Algorithm' in the AFP 
(see https://www.isa-afp.org/entries/Elimination\_Of\_Repeated\_Factors.html)›
fun find_α :: "'a  'e mod_ring poly  'e mod_ring" where
  "find_α g_pow_α φ = find_α_square_free g_pow_α (radical φ)"

text ‹The reduction: 
An adversary for the KZG polynomial binding can output two polynomials $\phi$ and $\phi'$ that have the same 
commitment, i.e $g^{\phi(\alpha)} = g^{\phi(\alpha)}$, which is equivalent to $\phi(\alpha) = \phi'(\alpha)$ (same argument as in the 
function above). Hence $(\phi-\phi')(\alpha) = 0$, so $(\phi-\phi')$ has a root at $\alpha$. Furthermore we have $g^\alpha$ in the 
public key at position 1. Hence we can use the find_α› function to compute $\alpha$ in 
polynomial time. Given $\alpha$ we can easily compute a c and a g' such that $g^{1/(\alpha+c)} = g'$.
E.g. c=0 and $g' = g^{1/\alpha}$
Hence we can break the t-DL assumption, as we have a polynomial-time algorithm to compute (c,g).
›
fun bind_reduction
  :: "('a ck, 'e mod_ring poly, 'a commit, unit) bind_adversary  ('a,'e) t_DL.adversary"                     
where
  "bind_reduction 𝒜 PK = do {
  (C, φ, _, φ', _)  𝒜 PK;
  let α = find_α (PK!1) (φ - φ');
  return_spmf α}"

text ‹This function captures the bind\_reduction with the asserts from the binding game, essentially 
allowing us to prove the equivalence of binding game to t-DL game with stronger\_bind\_reduction. 
From this equivalence it is easy to proof the theorem winnnng the binding game is less than or equal 
to breaking the t-DL assumption›
fun stronger_bind_reduction
  :: "('a ck, 'e mod_ring poly, 'a commit, unit)  bind_adversary  ('a,'e) t_DL.adversary"                     
where
  "stronger_bind_reduction 𝒜 PK = do {
  (C, φ, _, φ', _)  𝒜 PK;
  _ :: unit  assert_spmf(φ  φ' 
     valid_poly φ 
     valid_poly φ' 
     (C = g_pow_PK_Prod PK φ) 
     (C = g_pow_PK_Prod PK φ'));
  let α = find_α (PK!1) (φ - φ');
  return_spmf α}"


subsection ‹Helping lemmas›

text ‹$g^{\phi(\alpha)}=g^{\phi'(\alpha)}$ is equivalent to $(\phi-\phi')(\alpha)=0$›
lemma commit_eq_is_poly_diff_α_eq_0: 
  assumes "degree φ  max_deg" "degree φ'  max_deg"
  shows "g_pow_PK_Prod (map (λt. gGp^Gp(α^t)) [0..<max_deg+1]) φ
= g_pow_PK_Prod (map (λt. gGp^Gp(α^t)) [0..<max_deg+1]) φ'
   poly (φ - φ') α = 0"
proof 
  assume commit_eq: 
    "g_pow_PK_Prod (map (λt. gGp^Gpα ^ t) [0..<max_deg + 1]) φ 
   = g_pow_PK_Prod (map (λt. gGp^Gpα ^ t) [0..<max_deg + 1]) φ'"
  have acc_φ: "g_pow_PK_Prod (map (λt. gGp^Gpα ^ t) [0..<max_deg + 1]) φ 
              =  gGp^Gp(poly φ α )"
    by (metis g_pow_PK_Prod_correct assms(1))
  moreover have acc_φ': 
    "g_pow_PK_Prod (map (λt. gGp^Gpα ^ t) [0..<max_deg + 1]) φ' 
    =  gGp^Gp(poly φ' α )"
    by (metis g_pow_PK_Prod_correct assms(2))
  ultimately show "(poly (φ - φ') α = 0)"
    using pow_on_eq_card commit_eq by fastforce
next
  assume poly_eq_0: "poly (φ - φ') α = 0"
  have acc_φ: "g_pow_PK_Prod (map (λt. gGp^Gpα ^ t) [0..<max_deg + 1]) φ 
            =  gGp^Gp(poly φ α )"
    by (metis g_pow_PK_Prod_correct assms(1))
  moreover have acc_φ': 
    "g_pow_PK_Prod (map (λt. gGp^Gpα ^ t) [0..<max_deg + 1]) φ' 
    =  gGp^Gp(poly φ' α )"
    by (metis g_pow_PK_Prod_correct assms(2))
  ultimately show "g_pow_PK_Prod (map (λt. g ^Gpα ^ t) [0..<max_deg + 1]) φ 
                 = g_pow_PK_Prod (map (λt. g ^Gpα ^ t) [0..<max_deg + 1]) φ'" 
    using poly_eq_0 by fastforce 
qed

lemma finite_field_factorization_roots:
fixes φ::"'e mod_ring poly"
  assumes sf_f: "square_free φ"
    and us: "finite_field_factorization φ = (c,us)"
  shows "poly φ α = 0  c=0  (uset us. poly u α = 0)"
proof 
  assume asm: "poly φ α = 0"
  have smult: "φ = Polynomial.smult c (prod_list us)"
    using finite_field_factorization_explicit[OF assms] ..
  then show "c = 0  (uset us. poly u α = 0)"
  proof (cases "c=0")
    case True
    then show ?thesis ..
  next
    case False
    then show ?thesis
      using asm
      by (simp add: poly_prod_list_zero_iff smult)
  qed
next 
  assume asm: "c = 0  (uset us. poly u α = 0)"
  have smult: "φ = Polynomial.smult c (prod_list us)"
    using finite_field_factorization_explicit[OF assms] ..
  show "poly φ α = 0"
  proof (cases "c=0")
    case True
    then show ?thesis using smult by force
  next
    case False
    then have "(uset us. poly u α = 0)" using asm by blast 
    then show ?thesis
      using smult poly_prod_list_zero_iff poly_smult_zero_iff by blast
  qed
qed

lemma root_imp_deg_1:
  assumes "monic (u::'e mod_ring poly)  irreducible u"
  shows "(x. poly u x = 0)  degree u = 1"
proof 
  assume asm: "x. poly u x = 0"
  show "degree u = 1"
  proof (rule ccontr)
    assume deg_ne_1: "degree u  1"
    obtain c where c: "poly u c = 0" using asm by blast
    with synthetic_div_correct' [of c u] have split_u: "u = [:-c, 1:] * synthetic_div u c" by simp
    from c deg_ne_1 have deg_u_pos: "degree u  2"
      by (metis One_nat_def assms leading_coeff_0_iff less_2_cases not_le poly_zero rel_simps(93))
    then have "degree (synthetic_div u c)  1" using degree_synthetic_div[of u c] by linarith  
    then have "¬(synthetic_div u c) dvd 1" by auto
    moreover have "¬[:-c, 1:] dvd 1" by auto
    ultimately show "False"
      using irreducible_def[of u] split_u assms by blast  
  qed
next
  assume asm: "degree u = 1"  
  have poly_deg_1: "x. poly u x = poly.coeff u 0 + x"
  proof 
    fix x 
    have "poly u x = (idegree u. poly.coeff u i * x ^ i)"
      using poly_altdef by fast
    also have " = poly.coeff u 0 + poly.coeff u 1 * x"
      using asm by force
    also have " = poly.coeff u 0 + x"
    proof -
      have "poly.coeff u 1 = 1"
        using asm assms by force
      then show ?thesis  by fastforce
    qed
    finally show "poly u x = poly.coeff u 0 + x" .
  qed    
  show "x. poly u x = 0"
  proof
    show "poly u (-poly.coeff u 0) = 0"
      using poly_deg_1 by fastforce
  qed
qed


lemma poly_eq0_is_find_α_sf_eq_α: 
  assumes "φ  0  square_free φ" 
  shows "poly φ α = 0  find_α_square_free (gGp^Gpα) φ = α"
proof -
  assume asm: "poly φ α = 0"
  obtain c polys where c_polys: "(c, polys) = finite_field_factorization φ"
    by (metis prod.exhaust)
  then have "c0" using assms
    by (metis finite_field_factorization_explicit smult_0_left)
  then have "u  set polys. poly u α = 0" using c_polys asm
    by (metis assms finite_field_factorization_roots)
  then obtain u where u: "u  set polys  poly u α = 0" by blast
  then have "degree u = 1" using root_imp_deg_1 
    by (metis (mono_tags, lifting) assms c_polys finite_field_factorization_explicit)
  moreover have "monic u" using u c_polys
    by (metis assms finite_field_factorization_explicit)
  ultimately have u_coeff0: "poly.coeff u 0 = -α" using u
    by (metis (no_types, lifting) One_nat_def add_0_right coeff_pCons_0 coeff_pCons_Suc degree1_coeffs degree_1 mpoly_base_conv(2) mult_cancel_left1 one_pCons pCons_0_hom.hom_zero synthetic_div_correct' synthetic_div_eq_0_iff synthetic_div_pCons)
  then show "find_α_square_free (g ^Gpα) φ = α"
  proof -
    have "find_α_square_free (g ^Gpα) φ 
    = (- (filter (λr. g ^Gpα = g ^Gp-r) (map (λp. poly.coeff p 0) (filter (λf. degree f = 1) (snd (finite_field_factorization φ))))) ! 0)"
      unfolding find_α_square_free.simps by (simp add: split_def)
    also have " = (- (filter (λr. g ^Gpα = g ^Gp-r) (map (λp. poly.coeff p 0) (filter (λf. degree f = 1) (polys)))) ! 0)"
      using c_polys by (smt (verit, best) snd_conv)
    also have " = α"
    proof -
      have "u  set (filter (λf. degree f = 1) polys)"
        by (simp add: degree u = 1 u)
      then have "-α  set (map (λp. poly.coeff p 0) (filter (λf. degree f = 1) polys))"
        using u_coeff0 degree u = 1 by force
      then have "-α  set (filter (λr. g ^Gpα = g ^Gp- r) (map (λp. poly.coeff p 0) (filter (λf. degree f = 1) polys)))"
        by fastforce
      moreover have "xs. -α  set xs  set (filter (λr. g ^Gpα = g ^Gp- r) xs) = {-α}"
        by (auto simp: pow_on_eq_card)
      ultimately have "set (filter (λr. g ^Gpα = g ^Gp- r) (map (λp. poly.coeff p 0) (filter (λf. degree f = 1) polys))) = {-α}"
        by fastforce
      then have "filter (λr. g ^Gpα = g ^Gp- r) (map (λp. poly.coeff p 0) (filter (λf. degree f = 1) polys)) ! 0 = -α"
        by (metis length_pos_if_in_set nth_mem singleton_iff)
      then show ?thesis by force
      qed
    finally show ?thesis . 
  qed
qed

text ‹show find_α› correctly finds(factorizes) $\alpha$, if $\alpha$ is a root and $\phi$ is not a zero-polynomial.›
lemma poly_eq0_imp_find_α_eq_α: "φ  0  poly φ α = 0  find_α (gGp^Gpα) φ = α"
proof -
  assume φ_ne_0: "φ  0"
  assume α_root: "poly φ α = 0"
  have "(radical φ)  0"
    by (simp add: φ_ne_0)
  moreover have "poly (radical φ) α = 0" 
    using α_root same_zeros_radical by blast
  moreover have "square_free (radical φ)" 
    using (radical φ)  0 φ_ne_0 squarefree_radical squarefree_square_free by blast
  ultimately show "find_α (gGp^Gpα) φ = α"
    unfolding find_α.simps using poly_eq0_is_find_α_sf_eq_α by blast
qed

subsubsection ‹Literal helping lemmas›

text ‹From here on we define literal helping lemmas, with name-pattern: helping\_*number*\_*content-reference*. 
These lemmas are literal logic blocks that are used in the actual equivalence proof. 
They all capture one step in the transition (from poly\_bind game to t-DL reduction game logic), that is either 
too complicated for Isabelle to prove in the monadic/game-based form or requires some additional proving steps 
that would complicate the equivalence proof.
Basically extracted logical reasoning.›

text ‹The logical addition of $(\phi-\phi')(\alpha)=0$, which is implied by $\phi(\alpha)=\phi'(\alpha)$, which we derive from $C=g^{\phi(\alpha)} \wedge C=g^{\phi'(\alpha)}$›
lemma helping_1_add_poly_φ_m_φ': "(φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ) 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ')) 
        = (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ) 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ')
         (poly (φ - (φ')) (of_int_mod_ring (int α)::'e mod_ring) = 0))"
  unfolding valid_poly_def 
  using commit_eq_is_poly_diff_α_eq_0 by fast

lemma helping_2_factorize_α: "φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ) 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ')
         (poly (φ - φ') (of_int_mod_ring (int α)::'e mod_ring) = 0)
         φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ) 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ')
         (find_α (gGp^Gp((of_int_mod_ring (int α)::'e mod_ring))) (φ - φ') = (of_int_mod_ring (int α)::'e mod_ring))"
  (is "?lhs = ?rhs")
proof
  assume ?lhs
  then show ?rhs using poly_eq0_imp_find_α_eq_α
    by (meson right_minus_eq)
next 
  assume ?rhs
  then show ?lhs 
    unfolding valid_poly_def
    using commit_eq_is_poly_diff_α_eq_0 by fast
qed
                                
lemma helping_3_α_is_found: "φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ)  (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ')
         (find_α (gGp^Gp((of_int_mod_ring (int α)::'e mod_ring))) (φ - φ') = (of_int_mod_ring (int α)::'e mod_ring)) 
 φ  φ'  valid_poly φ  valid_poly φ' 
           (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ)  (C = g_pow_PK_Prod (map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1]) φ')
           (find_α (gGp^Gp((of_int_mod_ring (int α)::'e mod_ring))) (φ - φ') = (of_int_mod_ring (int α)::'e mod_ring)) 
           of_int_mod_ring (int α) = find_α ((map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1])!1) (φ - φ')"
  (is "?lhs = ?rhs")
proof -
  have "map (λt::nat. g ^Gpof_int_mod_ring (int α) ^ t) [0::nat..<max_deg + (1::nat)] ! (1::nat) = g ^Gpof_int_mod_ring (int α)"
    using PK_i d_pos by force
  then show ?thesis by metis

qed

subsection ‹KZG poly bind game to strong reduction game - equivalence theorem›

text ‹showing the equivalence of the KZG poly bind game to the stronger bind\_reduction game, which we 
show to be at least as hard as the real reduction game in the next subsection.›

theorem poly_bind_game_eq_t_DL_strong_red: 
  shows "cs.bind_game 𝒜 = t_DL_Gp.game (stronger_bind_reduction 𝒜)"
proof -
  note [simp] = Let_def split_def

  text ‹abbreviations for the mod\_ring version of sample uniform nat 
  and the public key›
  let  = "λα. (of_int_mod_ring (int α)::'e mod_ring)"
  let ?PK = "λα. (map (λt. gGp^Gp(( α)^t)) [0..<max_deg+1])"

  text ‹We start with the poly bind game and perform logical 
  transitions until we obtain the t-DL game with the (stronger-)reduction›
  have "cs.bind_game 𝒜 = TRY do {
    (ck,vk)  key_gen;
    (c, m, d, m', d')  𝒜 ck;
    _ :: unit  assert_spmf(m  m'  valid_poly m  valid_poly m'); 
    let b = verify_poly vk m c d;
    let b' = verify_poly vk m' c d';
    _ :: unit  assert_spmf (b  b');
    return_spmf True} ELSE return_spmf False" 
    by (simp add: abstract_commitment.bind_game_alt_def) 
    also have "= TRY do {
    α  sample_uniform (order Gp);
    let PK = map (λt. gGp^Gp((of_int_mod_ring (int α)::'e mod_ring)^t)) [0..<max_deg+1];
    (C, φ, d, φ', d')  𝒜 PK;
    _ :: unit  assert_spmf(φ  φ'  valid_poly φ  valid_poly φ'); 
    _ :: unit  assert_spmf ((C = g_pow_PK_Prod PK φ)  (C = g_pow_PK_Prod PK φ'));
    return_spmf True} ELSE return_spmf False"
      unfolding key_gen_def verify_poly_def Setup_def by simp
    also have " = TRY do {
    α  sample_uniform (order Gp);
    TRY do {
    (C, φ, d, φ', d')  𝒜 (?PK α);
      TRY do {
         _ :: unit  assert_spmf(φ  φ'  valid_poly φ  valid_poly φ'); 
         _ :: unit  assert_spmf ((C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ'));
        return_spmf True
      } ELSE return_spmf False
    } ELSE return_spmf False
  } ELSE return_spmf False"
    unfolding split_def
    by(fold try_bind_spmf_lossless2[OF lossless_return_spmf])simp
  also have "= TRY do {
    α  sample_uniform (order Gp);
    TRY do {
    (C, φ, d, φ', d')  𝒜 (?PK α);
      TRY do {
        _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ'));
        return_spmf True
      } ELSE return_spmf False
    } ELSE return_spmf False
  } ELSE return_spmf False"
    by (presburger add: assert_collapse)
  also have "= TRY do {
    α  sample_uniform (order Gp);
    TRY do {
    (C, φ, d, φ', d')  𝒜 (?PK α);
      TRY do {
        _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ')
         (poly (φ - φ') ( α) = 0));
        return_spmf True
      } ELSE return_spmf False
    } ELSE return_spmf False
  } ELSE return_spmf False"    
    using helping_1_add_poly_φ_m_φ' by presburger
 also have "= TRY do {
    α  sample_uniform (order Gp);
    TRY do {
    (C, φ, d, φ', d')  𝒜 (?PK α);
      TRY do {
        _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ')
         (find_α (gGp^Gp(( α))) (φ - φ') = ( α)));
        return_spmf True
      } ELSE return_spmf False
    } ELSE return_spmf False
  } ELSE return_spmf False"    
   using helping_2_factorize_α by presburger
 also have "= TRY do {
    α  sample_uniform (order Gp);
    TRY do {
    (C, φ, d, φ', d')  𝒜 (?PK α);
      TRY do {
        _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ')
         (find_α (gGp^Gp(( α))) (φ - φ') = ( α))
         (( α) = find_α ((?PK α)!1) (φ - φ')));
        return_spmf True
      } ELSE return_spmf False
    } ELSE return_spmf False
  } ELSE return_spmf False" 
   using helping_3_α_is_found
   by presburger
  also have "= TRY do { 
    α  sample_uniform (order Gp);
    TRY do {
    (C, φ, _, φ', _)  𝒜 (?PK α);
       TRY do {
       _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
           (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ')
           (find_α (gGp^Gp(( α))) (φ - φ') = ( α)));   
      _::unit  assert_spmf (( α) = find_α ((?PK α)!1) (φ - φ'));
      return_spmf True } ELSE return_spmf False
      } ELSE return_spmf False 
    } ELSE return_spmf False"
    by (presburger add: assert_collapse)
  also have "= TRY do { 
    α  sample_uniform (order Gp);
    (C, φ, _, φ', _)  𝒜 (?PK α);
     _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ')
         (find_α (gGp^Gp(( α))) (φ - φ') = ( α)));   
    _::unit  assert_spmf (( α) = find_α ((?PK α)!1) (φ - φ'));
    return_spmf True } ELSE return_spmf False " 
    unfolding split_def
    by(fold try_bind_spmf_lossless2[OF lossless_return_spmf])simp
  also have "= TRY do { 
    α  sample_uniform (order Gp);
    (C, φ, _, φ', _)  𝒜 (?PK α);
    _ :: unit  assert_spmf (φ  φ'  valid_poly φ  valid_poly φ' 
         (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ')
         (poly (φ - φ') ( α) = 0));    
  _::unit  assert_spmf (( α) = find_α ((?PK α)!1) (φ - φ'));
    return_spmf True } ELSE return_spmf False"
    using helping_2_factorize_α by presburger
   also have "  = TRY do { 
    α  sample_uniform (order Gp);
    (C, φ, _, φ', _)  𝒜 (?PK α);
    _ :: unit  assert_spmf(φ  φ'  valid_poly φ  valid_poly φ' 
       (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ'));
    _::unit  assert_spmf (( α) = find_α ((?PK α)!1) (φ - φ'));
    return_spmf True } ELSE return_spmf False "
     using helping_1_add_poly_φ_m_φ' by presburger
  also have "= TRY do { 
    α  sample_uniform (order Gp);
    α'   do {
      (C, φ, _, φ', _)  𝒜 (?PK α);
      _ :: unit  assert_spmf(φ  φ'  valid_poly φ  valid_poly φ' 
       (C = g_pow_PK_Prod (?PK α) φ)  (C = g_pow_PK_Prod (?PK α) φ'));
      let α = find_α ((?PK α)!1) (φ - φ');
      return_spmf α };
    _::unit  assert_spmf (( α) = α');
    return_spmf True } ELSE return_spmf False"
    by fastforce
  also have " = TRY do { 
    α  sample_uniform (order Gp);
    α'  (stronger_bind_reduction 𝒜) (?PK α);
    _::unit  assert_spmf (( α) = α');
    return_spmf True } ELSE return_spmf False"
    unfolding stronger_bind_reduction.simps 
    using g_pow_to_int_mod_ring_of_int_mod_ring_pow_t by presburger
   also have "= t_DL_Gp.game (stronger_bind_reduction 𝒜)"
    using t_DL_Gp.game_alt_def[of "(stronger_bind_reduction 𝒜)"] by simp
  finally show ?thesis .
qed

lemma t_DL_advantage_stronger_red_le_red: 
"t_DL_Gp.advantage (stronger_bind_reduction 𝒜)  t_DL_Gp.advantage (bind_reduction 𝒜)"
  apply (simp only: t_DL_Gp.advantage_def t_DL_Gp.game_def 
      bind_reduction.simps stronger_bind_reduction.simps)
  apply (rule try_spmf_le)
  apply (simp only: bind_spmf_assoc)
  apply (rule bind_spmf_le)+
  apply (simp only: split_def Let_def bind_spmf_assoc bind_return_spmf)
  apply (rule del_assert)
  done

theorem polynomial_binding: 
  "poly_bind_advantage 𝒜   t_DL_Gp.advantage (bind_reduction 𝒜)"
  apply (simp add: poly_bind_advantage_def cs.bind_advantage_def)
  apply (simp add: poly_bind_game_eq_t_DL_strong_red)
  apply (insert t_DL_advantage_stronger_red_le_red)
  apply (simp add: t_DL_Gp.advantage_def)
  done

end

end