/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
-/
import analysis.special_functions.pow

/-!
# Convergence of `p`-series

In this file we prove that the series `∑' k in ℕ, 1 / k ^ p` converges if and only if `p > 1`.
The proof is based on the
[Cauchy condensation test](https://en.wikipedia.org/wiki/Cauchy_condensation_test): `∑ k, f k`
converges if and only if so does `∑ k, 2 ^ k f (2 ^ k)`. We prove this test in
`nnreal.summable_condensed_iff` and `summable_condensed_iff_of_nonneg`, then use it to prove
`summable_one_div_rpow`. After this transformation, a `p`-series turns into a geometric series.

## TODO

It should be easy to generalize arguments to Schlömilch's generalization of the Cauchy condensation
test once we need it.

## Tags

p-series, Cauchy condensation test
-/

open filter
open_locale big_operators ennreal nnreal topological_space

/-!
### Cauchy condensation test

In this section we prove the Cauchy condensation test: for `f : ℕ → ℝ≥0` or `f : ℕ → ℝ`,
`∑ k, f k` converges if and only if so does `∑ k, 2 ^ k f (2 ^ k)`. Instead of giving a monolithic
proof, we split it into a series of lemmas with explicit estimates of partial sums of each series in
terms of the partial sums of the other series.
-/

namespace finset

variables {M : Type*} [ordered_add_comm_monoid M] {f : ℕ → M}

lemma le_sum_condensed' (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in Ico 1 (2 ^ n), f k) ≤ ∑ k in range n, (2 ^ k) • f (2 ^ k) :=
begin
  induction n with n ihn, { simp },
  suffices : (∑ k in Ico (2 ^ n) (2 ^ (n + 1)), f k) ≤ (2 ^ n) • f (2 ^ n),
  { rw [sum_range_succ, ← sum_Ico_consecutive],
    exact add_le_add ihn this,
    exacts [n.one_le_two_pow, nat.pow_le_pow_of_le_right zero_lt_two n.le_succ] },
  have : ∀ k ∈ Ico (2 ^ n) (2 ^ (n + 1)), f k ≤ f (2 ^ n) :=
    λ k hk, hf (pow_pos zero_lt_two _) (mem_Ico.mp hk).1,
  convert sum_le_sum this,
  simp [pow_succ, two_mul]
end

lemma le_sum_condensed (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in range (2 ^ n), f k) ≤ f 0 + ∑ k in range n, (2 ^ k) • f (2 ^ k) :=
begin
  convert add_le_add_left (le_sum_condensed' hf n) (f 0),
  rw [← sum_range_add_sum_Ico _ n.one_le_two_pow, sum_range_succ, sum_range_zero, zero_add]
end

lemma sum_condensed_le' (hf : ∀ ⦃m n⦄, 1 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in range n, (2 ^ k) • f (2 ^ (k + 1))) ≤ ∑ k in Ico 2 (2 ^ n + 1), f k :=
begin
  induction n with n ihn, { simp },
  suffices : (2 ^ n) • f (2 ^ (n + 1)) ≤ ∑ k in Ico (2 ^ n + 1) (2 ^ (n + 1) + 1), f k,
  { rw [sum_range_succ, ← sum_Ico_consecutive],
    exact add_le_add ihn this,
    exacts [add_le_add_right n.one_le_two_pow _,
      add_le_add_right (nat.pow_le_pow_of_le_right zero_lt_two n.le_succ) _] },
  have : ∀ k ∈ Ico (2 ^ n + 1) (2 ^ (n + 1) + 1), f (2 ^ (n + 1)) ≤ f k :=
    λ k hk, hf (n.one_le_two_pow.trans_lt $ (nat.lt_succ_of_le le_rfl).trans_le (mem_Ico.mp hk).1)
      (nat.le_of_lt_succ $ (mem_Ico.mp hk).2),
  convert sum_le_sum this,
  simp [pow_succ, two_mul]
end

lemma sum_condensed_le (hf : ∀ ⦃m n⦄, 1 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in range (n + 1), (2 ^ k) • f (2 ^ k)) ≤ f 1 + 2 • ∑ k in Ico 2 (2 ^ n + 1), f k :=
begin
  convert add_le_add_left (nsmul_le_nsmul_of_le_right (sum_condensed_le' hf n) 2) (f 1),
  simp [sum_range_succ', add_comm, pow_succ, mul_nsmul, sum_nsmul]
end

end finset

namespace ennreal

variable {f : ℕ → ℝ≥0∞}

lemma le_tsum_condensed (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) :
  ∑' k, f k ≤ f 0 + ∑' k : ℕ, (2 ^ k) * f (2 ^ k) :=
begin
  rw [ennreal.tsum_eq_supr_nat' (nat.tendsto_pow_at_top_at_top_of_one_lt _root_.one_lt_two)],
  refine supr_le (λ n, (finset.le_sum_condensed hf n).trans (add_le_add_left _ _)),
  simp only [nsmul_eq_mul, nat.cast_pow, nat.cast_two],
  apply ennreal.sum_le_tsum
end

lemma tsum_condensed_le (hf : ∀ ⦃m n⦄, 1 < m → m ≤ n → f n ≤ f m) :
  ∑' k : ℕ, (2 ^ k * f (2 ^ k)) ≤ f 1 + 2 * ∑' k, f k :=
begin
  rw [ennreal.tsum_eq_supr_nat' (tendsto_at_top_mono nat.le_succ tendsto_id), two_mul, ← two_nsmul],
  refine supr_le (λ n, le_trans _ (add_le_add_left (nsmul_le_nsmul_of_le_right
    (ennreal.sum_le_tsum $ finset.Ico 2 (2^n + 1)) _) _)),
  simpa using finset.sum_condensed_le hf n
end

end ennreal

namespace nnreal

/-- Cauchy condensation test for a series of `nnreal` version. -/
lemma summable_condensed_iff {f : ℕ → ℝ≥0} (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) :
  summable (λ k : ℕ, (2 ^ k) * f (2 ^ k)) ↔ summable f :=
begin
  simp only [← ennreal.tsum_coe_ne_top_iff_summable, ne.def, not_iff_not, ennreal.coe_mul,
    ennreal.coe_pow, ennreal.coe_two],
  split; intro h,
  { replace hf : ∀ m n, 1 < m → m ≤ n → (f n : ℝ≥0∞) ≤ f m :=
      λ m n hm hmn, ennreal.coe_le_coe.2 (hf (zero_lt_one.trans hm) hmn),
    simpa [h, ennreal.add_eq_top] using (ennreal.tsum_condensed_le hf) },
  { replace hf : ∀ m n, 0 < m → m ≤ n → (f n : ℝ≥0∞) ≤ f m :=
      λ m n hm hmn, ennreal.coe_le_coe.2 (hf hm hmn),
    simpa [h, ennreal.add_eq_top] using (ennreal.le_tsum_condensed hf) }
end

end nnreal

/-- Cauchy condensation test for series of nonnegative real numbers. -/
lemma summable_condensed_iff_of_nonneg {f : ℕ → ℝ} (h_nonneg : ∀ n, 0 ≤ f n)
  (h_mono : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) :
  summable (λ k : ℕ, (2 ^ k) * f (2 ^ k)) ↔ summable f :=
begin
  lift f to ℕ → ℝ≥0 using h_nonneg,
  simp only [nnreal.coe_le_coe] at *,
  exact_mod_cast nnreal.summable_condensed_iff h_mono
end

open real

/-!
### Convergence of the `p`-series

In this section we prove that for a real number `p`, the series `∑' n : ℕ, 1 / (n ^ p)` converges if
and only if `1 < p`. There are many different proofs of this fact. The proof in this file uses the
Cauchy condensation test we formalized above. This test implies that `∑ n, 1 / (n ^ p)` converges if
and only if `∑ n, 2 ^ n / ((2 ^ n) ^ p)` converges, and the latter series is a geometric series with
common ratio `2 ^ {1 - p}`. -/

/-- Test for convergence of the `p`-series: the real-valued series `∑' n : ℕ, (n ^ p)⁻¹` converges
if and only if `1 < p`. -/
@[simp] lemma real.summable_nat_rpow_inv {p : ℝ} : summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ) ↔ 1 < p :=
begin
  cases le_or_lt 0 p with hp hp,
  /- Cauchy condensation test applies only to antitone sequences, so we consider the
  cases `0 ≤ p` and `p < 0` separately. -/
  { rw ← summable_condensed_iff_of_nonneg,
    { simp_rw [nat.cast_pow, nat.cast_two, ← rpow_nat_cast, ← rpow_mul zero_lt_two.le, mul_comm _ p,
        rpow_mul zero_lt_two.le, rpow_nat_cast, ← inv_pow₀, ← mul_pow,
        summable_geometric_iff_norm_lt_1],
      nth_rewrite 0 [← rpow_one 2],
      rw [← division_def, ← rpow_sub zero_lt_two, norm_eq_abs,
        abs_of_pos (rpow_pos_of_pos zero_lt_two _), rpow_lt_one_iff zero_lt_two.le],
      norm_num },
    { intro n,
      exact inv_nonneg.2 (rpow_nonneg_of_nonneg n.cast_nonneg _) },
    { intros m n hm hmn,
      exact inv_le_inv_of_le (rpow_pos_of_pos (nat.cast_pos.2 hm) _)
         (rpow_le_rpow m.cast_nonneg (nat.cast_le.2 hmn) hp) } },
  /- If `p < 0`, then `1 / n ^ p` tends to infinity, thus the series diverges. -/
  { suffices : ¬summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ),
    { have : ¬(1 < p) := λ hp₁, hp.not_le (zero_le_one.trans hp₁.le),
      simpa [this, -one_div] },
    { intro h,
      obtain ⟨k : ℕ, hk₁ : ((k ^ p)⁻¹ : ℝ) < 1, hk₀ : k ≠ 0⟩ :=
        ((h.tendsto_cofinite_zero.eventually (gt_mem_nhds zero_lt_one)).and
          (eventually_cofinite_ne 0)).exists,
      apply hk₀,
      rw [← pos_iff_ne_zero, ← @nat.cast_pos ℝ] at hk₀,
      simpa [inv_lt_one_iff_of_pos (rpow_pos_of_pos hk₀ _), one_lt_rpow_iff_of_pos hk₀, hp,
        hp.not_lt, hk₀] using hk₁ } }
end

@[simp] lemma real.summable_nat_rpow {p : ℝ} : summable (λ n, n ^ p : ℕ → ℝ) ↔ p < -1 :=
by { rcases neg_surjective p with ⟨p, rfl⟩, simp [rpow_neg] }

/-- Test for convergence of the `p`-series: the real-valued series `∑' n : ℕ, 1 / n ^ p` converges
if and only if `1 < p`. -/
lemma real.summable_one_div_nat_rpow {p : ℝ} : summable (λ n, 1 / n ^ p : ℕ → ℝ) ↔ 1 < p :=
by simp

/-- Test for convergence of the `p`-series: the real-valued series `∑' n : ℕ, (n ^ p)⁻¹` converges
if and only if `1 < p`. -/
@[simp] lemma real.summable_nat_pow_inv {p : ℕ} : summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ) ↔ 1 < p :=
by simp only [← rpow_nat_cast, real.summable_nat_rpow_inv, nat.one_lt_cast]

/-- Test for convergence of the `p`-series: the real-valued series `∑' n : ℕ, 1 / n ^ p` converges
if and only if `1 < p`. -/
lemma real.summable_one_div_nat_pow {p : ℕ} : summable (λ n, 1 / n ^ p : ℕ → ℝ) ↔ 1 < p :=
by simp

/-- Harmonic series is not unconditionally summable. -/
lemma real.not_summable_nat_cast_inv : ¬summable (λ n, n⁻¹ : ℕ → ℝ) :=
have ¬summable (λ n, (n^1)⁻¹ : ℕ → ℝ), from mt real.summable_nat_pow_inv.1 (lt_irrefl 1),
by simpa

/-- Harmonic series is not unconditionally summable. -/
lemma real.not_summable_one_div_nat_cast : ¬summable (λ n, 1 / n : ℕ → ℝ) :=
by simpa only [inv_eq_one_div] using real.not_summable_nat_cast_inv

/-- **Divergence of the Harmonic Series** -/
lemma real.tendsto_sum_range_one_div_nat_succ_at_top :
  tendsto (λ n, ∑ i in finset.range n, (1 / (i + 1) : ℝ)) at_top at_top :=
begin
  rw ← not_summable_iff_tendsto_nat_at_top_of_nonneg,
  { exact_mod_cast mt (summable_nat_add_iff 1).1 real.not_summable_one_div_nat_cast },
  { exact λ i, div_nonneg zero_le_one i.cast_add_one_pos.le }
end

@[simp] lemma nnreal.summable_rpow_inv {p : ℝ} : summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ≥0) ↔ 1 < p :=
by simp [← nnreal.summable_coe]

@[simp] lemma nnreal.summable_rpow {p : ℝ} : summable (λ n, n ^ p : ℕ → ℝ≥0) ↔ p < -1 :=
by simp [← nnreal.summable_coe]

lemma nnreal.summable_one_div_rpow {p : ℝ} : summable (λ n, 1 / n ^ p : ℕ → ℝ≥0) ↔ 1 < p :=
by simp


import .int .clause

def sgm (v : nat → int) (b : int) (as : list int) (n) :=
let a_n : int := as.get n in
let m : int := a_n + 1 in
((symmod b m) + (coeffs.val v (as.map (λ x, symmod x m)))) / m

/- Given a term t such that 0 = t and t = b + a₀ * x₀ + ... aₘ * xₘ,
   solve for the nth variable xₙ, obtain equation of the form xₙ = r,
   and return r (the rhs r has no occurrences of xₙ and includes a new
   variable σ, which is assigned the index n which is no longer used). -/
def rhs : nat → int → coeffs → term
| n b as :=
  let m := as.get n + 1 in
  ⟨(symmod b m), (as.map (λ x, symmod x m)){n ↦ -m}⟩

lemma rhs_correct_aux {m v} {as : coeffs} :
∀ {k}, ∃ d, (m * d +
  coeffs.val_btw v (as.map (λ (x : ℤ), symmod x m)) 0 k =
      coeffs.val_btw v as 0 k)
| 0 := begin existsi (0 : int), simp_omega end
| (k+1) :=
  begin
    simp_omega,
    cases @rhs_correct_aux k with d h1, rw ← h1,
    by_cases hk : k < as.length,
    { rw [list.get_map hk, symmod_eq, sub_mul],
      existsi (d + (symdiv (list.get k as) m * v k)),
      ring },
    { rw not_lt at hk,
      repeat {rw list.get_eq_zero_of_le},
      existsi d, rw add_assoc, exact hk,
      simp only [hk, list.length_map] }
  end

lemma rhs_correct {v b} {as : coeffs} (n) :
  0 < as.get n →
  0 = term.val v (b,as) →
  v n = term.val (v⟨n ↦ sgm v b as n⟩) (rhs n b as) :=
begin
  intros h0 h1,
  let a_n := as.get n,
  let m := a_n + 1,
  have h3 : m ≠ 0 :=
       begin
         apply ne_of_gt, apply lt_trans h0,
         simp [a_n, m],
       end,
  have h2 : m * (sgm v b as n) = (symmod b m) +
    coeffs.val v (as.map (λ x, symmod x m)),
  { simp only [sgm, mul_comm m],
    rw [int.div_mul_cancel],
    have h4 : ∃ c,
    m * c + (symmod b (list.get n as + 1) +
      coeffs.val v (as.map (λ (x : ℤ), symmod x m))) =
      term.val v (b,as),
    { have h5: ∃ d,  m * d +
        (coeffs.val v (as.map (λ x, symmod x m))) =
        coeffs.val v as,
        { simp only [coeffs.val, list.length_map],
          apply rhs_correct_aux },
        cases h5 with d h5, rw symmod_eq,
        existsi (symdiv b m + d),
        simp only [term.val], rw ← h5,
        simp only [term.val, mul_add,
        add_mul, m, a_n], ring },
    cases h4 with c h4,
    rw [dvd_add_iff_right (dvd_mul_right m c), h4, ← h1],
    apply dvd_zero },
  apply calc v n
      = -(m * sgm v b as n) + (symmod b m) +
        (coeffs.val_except n v (as.map (λ x, symmod x m))) :
      begin
        rw h2, simp, rw ← coeffs.val_except_add_eq n,
        simp,
        have hn : n < as.length,
        { by_contra hc, rw not_lt at hc,
          rw (list.get_eq_zero_of_le n hc) at h0,
          cases h0 },
        rw list.get_map hn, simp [a_n, m],
        rw [add_comm, symmod_add_one h0], ring
      end
  ... = term.val (v⟨n↦sgm v b as n⟩) (rhs n b as) :
      begin
        simp only [rhs, term.val],
        rw [← coeffs.val_except_add_eq n, list.get_set, update_eq],
        have h2 : ∀ a b c : int, a + b + c = b + (c + a) := by {intros, ring},
        rw (h2 (- _)), apply fun_mono_2 rfl, apply fun_mono_2,
        { rw coeffs.val_except_update_set },
        { simp [m, a_n], ring }
      end
end

def sym_sym (m b : int) : int :=
symdiv b m + symmod b m

-- Requires : 0 < as[n]
def coeffs_reduce : nat → int → list int → term
| n b as :=
  let a := as.get n in
  let m := a + 1 in
  (sym_sym m b, (as.map (sym_sym m)){n ↦ -a})

lemma coeffs_reduce_correct {v b} {as : coeffs} {n} :
  0 < as.get n →
  0 = term.val v (b,as) →
  0 = term.val (v⟨n ↦ sgm v b as n⟩) (coeffs_reduce n b as) :=
begin
  intros h1 h2,
  let a_n := as.get n,
  let m := a_n + 1,
  have h3 : m ≠ 0 :=
       begin apply ne_of_gt, apply lt_trans h1, simp [m] end,
  have h4 : 0 = (term.val (v⟨n↦sgm v b as n⟩) (coeffs_reduce n b as)) * m :=
  calc 0
      = term.val v (b,as) : h2
  ... = b + coeffs.val_except n v as
          + a_n * ((rhs n b as).val (v⟨n ↦ sgm v b as n⟩)) :
        begin
          simp only [term.val],
          rw [← coeffs.val_except_add_eq n,
              rhs_correct n h1 h2],
          simp only [a_n, add_assoc],
        end
  ... = -(m * a_n * sgm v b as n) + (b + a_n * (symmod b m))
        + (coeffs.val_except n v as
        + a_n * coeffs.val_except n v (as.map (λ x, symmod x m))) :
          begin
            simp [term.val, rhs, mul_add, m, a_n],
           rw [← coeffs.val_except_add_eq n,
            list.get_set, update_eq, mul_add],
            apply fun_mono_2,
            { rw coeffs.val_except_eq_val_except
              update_eq_of_ne list.get_set_eq_of_ne },
            { simp only [m], ring },
          end
  ... = -(m * a_n * sgm v b as n) + (b + a_n * (symmod b m))
        + coeffs.val_except n v (as.map (λ a_i, a_i + a_n * (symmod a_i m))) :
        begin
          apply fun_mono_2 rfl,
          simp only [coeffs.val_except, mul_add],
          repeat {rw ← coeffs.val_btw_map_mul},
          have h4 : ∀ {a b c d : int},
            a + b + (c + d) = (a + c) + (b + d),
          { intros, ring }, rw h4,
          have h5 : list.add as (list.map (has_mul.mul a_n)
            (list.map (λ (x : ℤ), symmod x (list.get n as + 1)) as)) =
            list.map (λ (a_i : ℤ), a_i + a_n * symmod a_i m) as,
          { rw [list.map_map, list.map_add_map],
            apply fun_mono_2,
            { have h5 : (λ x : int, x) = id,
              { rw function.funext_iff, intro x, refl },
              rw [h5, list.map_id] },
            { apply fun_mono_2 _ rfl,
              rw function.funext_iff, intro x,
              simp only [m] } },
          simp only [list.length_map],
          repeat { rw [← coeffs.val_btw_add, h5] },
        end
  ... = -(m * a_n * sgm v b as n) + (m * sym_sym m b)
        + coeffs.val_except n v (as.map (λ a_i, m * sym_sym m a_i)) :
        begin
          repeat {rw add_assoc}, apply fun_mono_2, refl,
          rw ← add_assoc,
          have h4 : ∀ (x : ℤ), x + a_n * symmod x m = m * sym_sym m x,
          { intro x, have h5 : a_n = m - 1,
            { simp only [m], rw add_sub_cancel },
              rw [h5, sub_mul, one_mul, add_sub,
                add_comm, add_sub_assoc, ← mul_symdiv_eq],
             simp only [sym_sym, mul_add, add_comm] },
          apply fun_mono_2 (h4 _),
          apply coeffs.val_except_eq_val_except; intros x h5, refl,
          apply congr_arg, apply fun_mono_2 _ rfl,
          rw function.funext_iff, apply h4,
        end
  ... = (-(a_n * sgm v b as n) + (sym_sym m b)
        + coeffs.val_except n v (as.map (sym_sym m))) * m :
        begin
          simp only [add_mul _ _ m], apply fun_mono_2, ring,
          simp only [coeffs.val_except], simp only [add_mul _ _ m],
          apply fun_mono_2,
          { rw [mul_comm _ m, ← coeffs.val_btw_map_mul, list.map_map] },
          { simp only [list.length_map, mul_comm _ m],
            rw [← coeffs.val_btw_map_mul, list.map_map] }
        end
  ... = (term.val (v⟨n↦sgm v b as n⟩) (coeffs_reduce n b as)) * m :
        begin
          simp_omega [coeffs_reduce, add_mul],
          rw [add_comm _ (sym_sym m b * m), add_assoc],
          apply fun_mono_2, refl,
          rw [← coeffs.val_except_add_eq n, list.get_set,
            update_eq, add_comm, add_mul], apply fun_mono_2,
          { apply congr_arg (λ x, x * m),
            apply coeffs.val_except_eq_val_except; intros x h4,
            { rw update_eq_of_ne _ h4 },
            { rw list.get_set_eq_of_ne _ h4 } },
          { apply congr_arg (λ x, x * m),
            simp only [neg_mul_eq_neg_mul] }
        end,
  rw [← int.mul_div_cancel (term.val _ _) h3, ← h4, int.zero_div]
end

-- Requires : t1.coeffs[m] = 1
def cancel (m : nat) (t1 t2 : term) : term :=
term.add (t1.mul (-t2.snd.get m)) t2

def subst (n : nat) (t1 t2 : term) : term :=
term.add (t1.mul (t2.snd.get n)) (t2.fst,t2.snd{n ↦ 0})

lemma subst_correct {v t n b} {as : coeffs} :
  0 < as.get n → 0 = term.val v (b,as) →
  term.val v t = term.val (v⟨n↦sgm v b as n⟩) (subst n (rhs n b as) t) :=
begin
  intros h1 h2, simp_omega [subst],
  rw ← rhs_correct _ h1 h2, cases t with b' as',
  simp_omega,
  have h3 : coeffs.val (v⟨n↦sgm v b as n⟩) (as'{n↦0}) =
    coeffs.val_except n v as',
  { rw [← coeffs.val_except_add_eq n, list.get_set,
      zero_mul, add_zero, coeffs.val_except_update_set] },
  rw [h3, ← coeffs.val_except_add_eq n], ring
end

@[derive has_reflect]
inductive ee : Type
| drop   : ee
| nondiv : int → ee
| factor : int → ee
| neg    : ee
| reduce : nat → ee
| cancel : nat → ee

namespace ee

def repr : ee → string
| drop   := "↓"
| (nondiv i) := i.repr ++ "∤"
| (factor i) := "/" ++ i.repr
| neg    := "-"
| (reduce n) := "≻" ++ n.repr
| (cancel n) := "+" ++ n.repr

instance has_repr : has_repr ee := ⟨repr⟩

meta instance has_to_format : has_to_format ee := ⟨λ x, x.repr⟩

end ee

def conc : list ee → clause → clause
| []     ([], les)     := ([],les)
| []     ((_::_), les) := ([],[])
| (_::_) ([], les)     := ([],[])
| (ee.drop::es) ((eq::eqs), les) := conc es (eqs, les)
| (ee.neg::es)  ((eq::eqs), les) := conc es ((eq.neg::eqs), les)
| (ee.nondiv i::es) ((b,as)::eqs, les) :=
  if ¬(i ∣ b) ∧ (∀ x ∈ as, i ∣ x)
  then ([],[⟨-1,[]⟩])
  else ([],[])
| (ee.factor i::es) ((b,as)::eqs, les) :=
  if (i ∣ b) ∧ (∀ x ∈ as, i ∣ x)
  then conc es ((term.div i (b,as)::eqs), les)
  else ([],[])
| (ee.reduce n::es) ((b,as)::eqs, les) :=
  if 0 < as.get n
  then let eq' := coeffs_reduce n b as in
       let r := rhs n b as in
       let eqs' := eqs.map (subst n r) in
       let les' := les.map (subst n r) in
       conc es ((eq'::eqs'), les')
  else ([],[])
| (ee.cancel m::es) ((eq::eqs), les)  :=
  conc es ((eqs.map (_root_.cancel m eq)),
    (les.map (_root_.cancel m eq)))

open tactic

lemma sat_empty : clause.sat ([],[]) :=
⟨λ _,0, ⟨dec_trivial, dec_trivial⟩⟩

lemma sat_conc : ∀ {es} {c : clause}, c.sat → (conc es c).sat
| []     ([], les) h := h
| (e::_) ([], les) h :=
  begin cases e; simp only [conc]; apply sat_empty end
| [] ((_::_), les) h := sat_empty
| (ee.drop::es) ((eq::eqs), les) h  :=
  begin
    apply (@sat_conc es _ _),
    apply clause.sat_of_weaker _ h,
    constructor,
    apply list.subset_cons,
    apply list.subset.refl
  end
| (ee.neg::es) ((eq::eqs), les) h1 :=
  begin
    simp only [conc], apply sat_conc,
    cases h1 with v h1, existsi v,
    cases h1 with hl hr, apply and.intro _ hr,
    rw list.forall_mem_cons at *,
    apply and.intro _ hl.right,
    rw term.val_neg, rw ← hl.left, refl
  end
| (ee.nondiv i::es) ((b,as)::eqs, les) h1 :=
  begin
    simp only [conc],
    by_cases h2 : (¬i ∣ b ∧ ∀ (x : ℤ), x ∈ as → i ∣ x),
    { exfalso, cases h1 with v h1,
      have h3 : 0 = b + coeffs.val v as := h1.left _ (or.inl rfl),
      have h4 : i ∣ coeffs.val v as     := coeffs.dvd_val h2.right,
      have h5 : i ∣ b + coeffs.val v as := by { rw ← h3, apply dvd_zero },
      rw ← dvd_add_iff_left h4 at h5, apply h2.left h5 },
    { rw if_neg h2, apply sat_empty }
  end
| (ee.factor i::es) ((b,as)::eqs, les) h1 :=
  begin
    simp only [conc],
    by_cases h2 : (i ∣ b) ∧ (∀ x ∈ as, i ∣ x),
    { rw if_pos h2, apply sat_conc, cases h1 with v h1,
      existsi v, cases h1 with h3 h4, apply and.intro _ h4,
      rw list.forall_mem_cons at *, cases h3 with h5 h6,
      apply and.intro _ h6,
      rw [term.val_div h2.left h2.right, ← h5, int.zero_div] },
    { rw if_neg h2, apply sat_empty }
  end
| (ee.reduce n::es) ((b,as)::eqs, les) h1 :=
  begin
    simp only [conc], by_cases h2 : 0 < list.get n as,
    tactic.rotate 1, { rw if_neg h2, apply sat_empty },
    rw if_pos h2, apply sat_conc, cases h1 with v h1,
    existsi v⟨n ↦ sgm v b as n⟩, cases h1 with h1 h3,
    rw list.forall_mem_cons at h1, cases h1 with h4 h5,
    constructor, apply list.forall_mem_cons_of,
    apply coeffs_reduce_correct h2 h4,
    { intros x h6, rw list.mem_map at h6,
      cases h6 with t h6, cases h6 with h6 h7,
      rw [← h7, ← subst_correct h2 h4], apply h5 _ h6 },
    { intros x h6, rw list.mem_map at h6,
      cases h6 with t h6, cases h6 with h6 h7,
      rw [← h7, ← subst_correct h2 h4], apply h3 _ h6 }
   end
| (ee.cancel m::es) ((eq::eqs), les) h1 :=
  begin
    simp only [conc], apply sat_conc,
    cases h1 with v h1, existsi v, cases h1 with h1 h2,
    rw list.forall_mem_cons at h1, cases h1 with h1 h3,
    constructor; intros t h4; rw list.mem_map at h4;
    rcases h4 with ⟨s,h4,h5⟩; rw ← h5;
    simp only [term.val_add, term.val_mul, cancel];
    rw [← h1, mul_zero, zero_add],
    { apply h3 _ h4 }, { apply h2 _ h4}
  end

lemma unsat_of_unsat_conc (ee c) :
  (conc ee c).unsat → c.unsat :=
begin intros h1 h2, apply h1, apply sat_conc h2 end


import tactic

variables P Q : Prop

example (hP : P) : P ∨ Q :=
begin
  left,
  assumption,
end

example (hQ : Q) : P ∨ Q :=
begin
  right,
  assumption,
end

example (hP : P) (hQ : Q) : P ∧ Q :=
begin
  split;
  assumption,
end


/-
Copyright (c) 2023 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import linear_algebra.bilinear_form
import linear_algebra.tensor_product
import linear_algebra.contraction

/-!
# The bilinear form on a tensor product

## Main definitions

* `bilin_form.tensor_distrib (B₁ ⊗ₜ B₂)`: the bilinear form on `M₁ ⊗ M₂` constructed by applying
  `B₁` on `M₁` and `B₂` on `M₂`.
* `bilin_form.tensor_distrib_equiv`: `bilin_form.tensor_distrib` as an equivalence on finite free
  modules.

-/

universes u v w
variables {ι : Type*} {R : Type*} {M₁ M₂ : Type*}

open_locale tensor_product

namespace bilin_form

section comm_semiring
variables [comm_semiring R]
variables [add_comm_monoid M₁] [add_comm_monoid M₂]
variables [module R M₁] [module R M₂]

/-- The tensor product of two bilinear forms injects into bilinear forms on tensor products. -/
def tensor_distrib : bilin_form R M₁ ⊗[R] bilin_form R M₂ →ₗ[R] bilin_form R (M₁ ⊗[R] M₂) :=
((tensor_product.tensor_tensor_tensor_comm R _ _ _ _).dual_map
  ≪≫ₗ (tensor_product.lift.equiv R _ _ _).symm
  ≪≫ₗ linear_map.to_bilin).to_linear_map
  ∘ₗ tensor_product.dual_distrib R _ _
  ∘ₗ (tensor_product.congr
    (bilin_form.to_lin ≪≫ₗ tensor_product.lift.equiv R _ _ _)
    (bilin_form.to_lin ≪≫ₗ tensor_product.lift.equiv R _ _ _)).to_linear_map

@[simp] lemma tensor_distrib_tmul (B₁ : bilin_form R M₁) (B₂ : bilin_form R M₂)
  (m₁ : M₁) (m₂ : M₂) (m₁' : M₁) (m₂' : M₂) :
  tensor_distrib (B₁ ⊗ₜ B₂) (m₁ ⊗ₜ m₂) (m₁' ⊗ₜ m₂') = B₁ m₁ m₁' * B₂ m₂ m₂' :=
rfl

/-- The tensor product of two bilinear forms, a shorthand for dot notation. -/
@[reducible]
protected def tmul (B₁ : bilin_form R M₁) (B₂ : bilin_form R M₂) : bilin_form R (M₁ ⊗[R] M₂) :=
tensor_distrib (B₁ ⊗ₜ[R] B₂)

end comm_semiring

section comm_ring
variables [comm_ring R]
variables [add_comm_group M₁] [add_comm_group M₂]
variables [module R M₁] [module R M₂]
variables [module.free R M₁] [module.finite R M₁]
variables [module.free R M₂] [module.finite R M₂]
variables [nontrivial R]

/-- `tensor_distrib` as an equivalence. -/
noncomputable def tensor_distrib_equiv :
  bilin_form R M₁ ⊗[R] bilin_form R M₂ ≃ₗ[R] bilin_form R (M₁ ⊗[R] M₂) :=
-- the same `linear_equiv`s as from `tensor_distrib`, but with the inner linear map also as an
-- equiv
tensor_product.congr
    (bilin_form.to_lin ≪≫ₗ tensor_product.lift.equiv R _ _ _)
    (bilin_form.to_lin ≪≫ₗ tensor_product.lift.equiv R _ _ _)
  ≪≫ₗ tensor_product.dual_distrib_equiv R (M₁ ⊗ M₁) (M₂ ⊗ M₂)
  ≪≫ₗ (tensor_product.tensor_tensor_tensor_comm R _ _ _ _).dual_map
  ≪≫ₗ (tensor_product.lift.equiv R _ _ _).symm
  ≪≫ₗ linear_map.to_bilin

@[simp]
lemma tensor_distrib_equiv_apply (B : bilin_form R M₁ ⊗ bilin_form R M₂) :
  tensor_distrib_equiv B = tensor_distrib B := rfl

end comm_ring

end bilin_form


/- This file includes the proofs of the axioms for the alternative and sequential composition. -/
import .iff_lemmas

open mcrl2

variable {α : Type}
variable [comm_semigroup_with_zero α]

lemma transition.alt_comm (x y: mcrl2 α) (x' : option (mcrl2 α)) (a) :
  transition (x + y) a x' ↔ transition (y + x) a x' :=
begin
  simp only [transition.alt_iff, or_comm]
end

lemma transition.alt_idem (x : mcrl2 α) (y : option (mcrl2 α)) (a) :
transition (x + x) a y ↔ transition x a y :=
begin
  simp only [transition.alt_iff, or_self]
end

lemma transition.alt_assoc (x y z: mcrl2 α) (x' : option (mcrl2 α)) (a) :
transition (x + (y + z)) a x' ↔ transition ((x + y) + z) a x' :=
begin
  simp only [transition.alt_iff, or_assoc]
end

lemma transition.alt_dist (x y z : mcrl2 α) (x' : option (mcrl2 α)) (a) :
transition ((x + y) ⬝ z) a x' ↔ transition ((x⬝ z) + (y ⬝ z)) a x' :=
begin
  simp only [transition.alt_iff, transition.seq_iff, and_or_distrib_left, exists_or_distrib]
end

/- No seq_assoc here because we have to prove it via bisimulation. -/


example (P Q : Type) : (P → Q) → ((Q → empty) → (P → empty)) :=
begin
intros g f p,
exact f(g(p)),
end


/-
Copyright (c) 2018 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot, Johannes Hölzl
-/
import topology.algebra.group_completion
import topology.algebra.ring

/-!
# Completion of topological rings:

This files endows the completion of a topological ring with a ring structure.
More precisely the instance `uniform_space.completion.ring` builds a ring structure
on the completion of a ring endowed with a compatible uniform structure in the sense of
`uniform_add_group`. There is also a commutative version when the original ring is commutative.

The last part of the file builds a ring structure on the biggest separated quotient of a ring.

## Main declarations:

Beyond the instances explained above (that don't have to be explicitly invoked),
the main constructions deal with continuous ring morphisms.

* `uniform_space.completion.extension_hom`: extends a continuous ring morphism from `R`
  to a complete separated group `S` to `completion R`.
* `uniform_space.completion.map_ring_hom` : promotes a continuous ring morphism
  from `R` to `S` into a continuous ring morphism from `completion R` to `completion S`.
-/
open classical set filter topological_space add_comm_group
open_locale classical
noncomputable theory
universes u

namespace uniform_space.completion
open dense_inducing uniform_space function
variables (α : Type*) [ring α] [uniform_space α]

instance : has_one (completion α) := ⟨(1:α)⟩

instance : has_mul (completion α) :=
⟨curry $ (dense_inducing_coe.prod dense_inducing_coe).extend (coe ∘ uncurry (*))⟩

@[norm_cast] lemma coe_one : ((1 : α) : completion α) = 1 := rfl

variables {α} [topological_ring α]

@[norm_cast]
lemma coe_mul (a b : α) : ((a * b : α) : completion α) = a * b :=
((dense_inducing_coe.prod dense_inducing_coe).extend_eq
  ((continuous_coe α).comp (@continuous_mul α _ _ _)) (a, b)).symm

variables [uniform_add_group α]

lemma continuous_mul : continuous (λ p : completion α × completion α, p.1 * p.2) :=
begin
  let m := (add_monoid_hom.mul : α →+ α →+ α).compr₂ to_compl,
  have : continuous (λ p : α × α, m p.1 p.2),
  from (continuous_coe α).comp continuous_mul,
  have di : dense_inducing (to_compl : α → completion α),
  from dense_inducing_coe,
  convert di.extend_Z_bilin di this,
  ext ⟨x, y⟩,
  refl
end

lemma continuous.mul {β : Type*} [topological_space β] {f g : β → completion α}
  (hf : continuous f) (hg : continuous g) : continuous (λb, f b * g b) :=
continuous_mul.comp (hf.prod_mk hg : _)

instance : ring (completion α) :=
{ one_mul       := assume a, completion.induction_on a
    (is_closed_eq (continuous.mul continuous_const continuous_id) continuous_id)
    (assume a, by rw [← coe_one, ← coe_mul, one_mul]),
  mul_one       := assume a, completion.induction_on a
    (is_closed_eq (continuous.mul continuous_id continuous_const) continuous_id)
    (assume a, by rw [← coe_one, ← coe_mul, mul_one]),
  mul_assoc     := assume a b c, completion.induction_on₃ a b c
    (is_closed_eq
      (continuous.mul (continuous.mul continuous_fst (continuous_fst.comp continuous_snd))
        (continuous_snd.comp continuous_snd))
      (continuous.mul continuous_fst
        (continuous.mul (continuous_fst.comp continuous_snd) (continuous_snd.comp continuous_snd))))
    (assume a b c, by rw [← coe_mul, ← coe_mul, ← coe_mul, ← coe_mul, mul_assoc]),
  left_distrib  := assume a b c, completion.induction_on₃ a b c
    (is_closed_eq
      (continuous.mul continuous_fst (continuous.add
        (continuous_fst.comp continuous_snd)
        (continuous_snd.comp continuous_snd)))
      (continuous.add
        (continuous.mul continuous_fst (continuous_fst.comp continuous_snd))
        (continuous.mul continuous_fst (continuous_snd.comp continuous_snd))))
    (assume a b c, by rw [← coe_add, ← coe_mul, ← coe_mul, ← coe_mul, ←coe_add, mul_add]),
  right_distrib := assume a b c, completion.induction_on₃ a b c
    (is_closed_eq
      (continuous.mul (continuous.add continuous_fst
        (continuous_fst.comp continuous_snd)) (continuous_snd.comp continuous_snd))
      (continuous.add
        (continuous.mul continuous_fst (continuous_snd.comp continuous_snd))
        (continuous.mul (continuous_fst.comp continuous_snd) (continuous_snd.comp continuous_snd))))
    (assume a b c, by rw [← coe_add, ← coe_mul, ← coe_mul, ← coe_mul, ←coe_add, add_mul]),
  ..completion.add_comm_group, ..completion.has_mul α, ..completion.has_one α }

/-- The map from a uniform ring to its completion, as a ring homomorphism. -/
def coe_ring_hom : α →+* completion α :=
⟨coe, coe_one α, assume a b, coe_mul a b, coe_zero, assume a b, coe_add a b⟩

lemma continuous_coe_ring_hom : continuous (coe_ring_hom : α → completion α) :=
continuous_coe α

variables {β : Type u} [uniform_space β] [ring β] [uniform_add_group β] [topological_ring β]
          (f : α →+* β) (hf : continuous f)

/-- The completion extension as a ring morphism. -/
def extension_hom [complete_space β] [separated_space β] :
  completion α →+* β :=
have hf' : continuous (f : α →+ β), from hf, -- helping the elaborator
have hf : uniform_continuous f, from uniform_continuous_of_continuous hf',
{ to_fun := completion.extension f,
  map_zero' := by rw [← coe_zero, extension_coe hf, f.map_zero],
  map_add' := assume a b, completion.induction_on₂ a b
    (is_closed_eq
      (continuous_extension.comp continuous_add)
      ((continuous_extension.comp continuous_fst).add
                      (continuous_extension.comp continuous_snd)))
    (assume a b,
      by rw [← coe_add, extension_coe hf, extension_coe hf, extension_coe hf,
             f.map_add]),
  map_one' := by rw [← coe_one, extension_coe hf, f.map_one],
  map_mul' := assume a b, completion.induction_on₂ a b
    (is_closed_eq
      (continuous_extension.comp continuous_mul)
      ((continuous_extension.comp continuous_fst).mul (continuous_extension.comp continuous_snd)))
    (assume a b,
      by rw [← coe_mul, extension_coe hf, extension_coe hf, extension_coe hf, f.map_mul]) }

instance top_ring_compl : topological_ring (completion α) :=
{ continuous_add := continuous_add,
  continuous_mul := continuous_mul }

/-- The completion map as a ring morphism. -/
def map_ring_hom (hf : continuous f) : completion α →+* completion β :=
extension_hom (coe_ring_hom.comp f) (continuous_coe_ring_hom.comp  hf)

variables (R : Type*) [comm_ring R] [uniform_space R] [uniform_add_group R] [topological_ring R]

instance : comm_ring (completion R) :=
{ mul_comm := assume a b, completion.induction_on₂ a b
      (is_closed_eq (continuous_fst.mul continuous_snd)
                    (continuous_snd.mul continuous_fst))
      (assume a b, by rw [← coe_mul, ← coe_mul, mul_comm]),
 ..completion.ring }

end uniform_space.completion

namespace uniform_space
variables {α : Type*}
lemma ring_sep_rel (α) [comm_ring α] [uniform_space α] [uniform_add_group α] [topological_ring α] :
  separation_setoid α = submodule.quotient_rel (ideal.closure ⊥) :=
setoid.ext $ assume x y, group_separation_rel x y

lemma ring_sep_quot
  (α : Type u) [r : comm_ring α] [uniform_space α] [uniform_add_group α] [topological_ring α] :
  quotient (separation_setoid α) = (α ⧸ (⊥ : ideal α).closure) :=
by rw [@ring_sep_rel α r]; refl

/-- Given a topological ring `α` equipped with a uniform structure that makes subtraction uniformly
continuous, get an equivalence between the separated quotient of `α` and the quotient ring
corresponding to the closure of zero. -/
def sep_quot_equiv_ring_quot (α)
  [r : comm_ring α] [uniform_space α] [uniform_add_group α] [topological_ring α] :
  quotient (separation_setoid α) ≃ (α ⧸ (⊥ : ideal α).closure) :=
quotient.congr_right $ assume x y, group_separation_rel x y

/- TODO: use a form of transport a.k.a. lift definition a.k.a. transfer -/
instance comm_ring [comm_ring α] [uniform_space α] [uniform_add_group α] [topological_ring α] :
  comm_ring (quotient (separation_setoid α)) :=
by rw ring_sep_quot α; apply_instance

instance topological_ring
  [comm_ring α] [uniform_space α] [uniform_add_group α] [topological_ring α] :
  topological_ring (quotient (separation_setoid α)) :=
begin
  convert topological_ring_quotient (⊥ : ideal α).closure; try {apply ring_sep_rel},
  simp [uniform_space.comm_ring]
end

end uniform_space


/-
Author: <Redacted for anonymized submission>
-/
import data.mv_polynomial.basic
import data.polynomial.div
import data.polynomial.field_division
import algebra.polynomial.big_operators
-- import .general_lemmas.polynomial_mv_sv_cast
-- import .general_lemmas.mv_divisability
-- import .general_lemmas.single_antidiagonal
-- import .general_lemmas.polynomial_smul_eq_C_mul
import .vars

/-!
# Knowledge Soundness

This file formalizes the knowledge-soundness property of the first protocol described in the Pinocchio paper.

NOTE The implementation here is incomplete, since there seem to be a few problems with the definition that I could not resolve. In particular

How do we check that the proof value of β_v v(s) + β_w w(s) + β_y y(s) is correct using "three pairings" Are we meant to use β_y γ for example?

-/

open_locale big_operators

section

noncomputable theory

universes u

/-- The finite field parameter of our SNARK -/
parameter {F : Type u}
parameter [field F]

/-- The naturals representing:
  m - m from paper - The QAP size
  n_in - n from paper - the number of inputs
  n_out - n' from paper - the number of outputs
  n_mid - (m - N) from paper - the number of internal gates
  d - the degree of h -/
parameters {n_in n_out n_mid d : ℕ}

-- N from paper
def n_stmt := n_in + n_out

-- Alternative name
def n_wit := n_mid

def m := n_stmt + n_wit

/-- fin-indexed collections of polynomials from the quadratic arithmetic program -/
parameter {v_stmt : fin n_stmt → polynomial F }
parameter {w_stmt : fin n_stmt → polynomial F }
parameter {y_stmt : fin n_stmt → polynomial F }
parameter {v_wit : fin n_wit → polynomial F }
parameter {w_wit : fin n_wit → polynomial F }
parameter {y_wit : fin n_wit → polynomial F }
parameter {v_0 : polynomial F }
parameter {w_0 : polynomial F }
parameter {y_0 : polynomial F }

/-- The roots of the polynomial t -/
parameter {r : fin m → F}
/-- t is the polynomial divisibility by which is used to verify satisfaction of the QAP -/
def t : polynomial F := ∏ i in (finset.fin_range m), (polynomial.X - polynomial.C (r i))

/-- t has degree m -/
lemma nat_degree_t : t.nat_degree = m
:=
begin
  -- rw polynomial.nat_degree,
  rw t,
  rw polynomial.nat_degree_prod,
  simp,
  intros i hi,
  exact polynomial.X_sub_C_ne_zero (r i),
end

lemma monic_t : t.monic
:=
begin
  rw t,
  apply polynomial.monic_prod_of_monic,
  intros i hi,
  exact polynomial.monic_X_sub_C (r i),
end

lemma degree_t_pos : 0 < m → 0 < t.degree
:=
begin
  intro hm,
  suffices h : t.degree = some m,
    rw h,
    apply with_bot.some_lt_some.2,
    exact hm,

  have h := nat_degree_t,
  rw polynomial.nat_degree at h,

  induction h1 : t.degree,

  rw h1 at h,
  rw option.get_or_else at h,
  rw h at hm,
  have h2 := has_lt.lt.false hm,
  exfalso,
  exact h2,

  rw h1 at h,
  rw option.get_or_else at h,
  rw h,
end

-- -- Single variable form of V_wit
-- def V_wit_sv (a_wit : fin n_wit → F) : polynomial F
-- := ∑ i in finset.fin_range n_wit, a_wit i • u_wit i

-- /-- The statement polynomial that the verifier computes from the statement bits, as a single variable polynomial -/
-- def V_stmt_sv (a_stmt : fin n_stmt → F) : polynomial F
-- := ∑ i in (finset.fin_range n_stmt), a_stmt i • u_stmt i

/-- Definition 2 from Pinocchio -/
def satisfying (c_stmt : fin n_stmt → F) (c_wit : fin n_wit → F) :=
(
  (v_0
    + (∑ i in (finset.fin_range n_stmt), c_stmt i • v_stmt i)
    + ∑ i in (finset.fin_range n_wit), c_wit i • v_wit i)
 *
  (w_0
    + (∑ i in (finset.fin_range n_stmt), c_stmt i • w_stmt i)
    + (∑ i in (finset.fin_range n_wit), c_wit i • w_wit i))
 -
  (y_0
    + (∑ i in (finset.fin_range n_stmt), c_stmt i • y_stmt i)
    + (∑ i in (finset.fin_range n_wit), c_wit i • y_wit i)))
   %ₘ t = 0

/- Multivariable polynomial definititons and ultilities -/

/-- Helper for converting mv_polynomial to single -/
-- @[simp]
-- def singlify : vars -> polynomial F
-- | vars.X := polynomial.X
-- | vars.Alpha := 1
-- | vars.Beta := 1
-- | vars.Gamma := 1
-- | vars.Delta := 1

-- /-- Helpers for representing vars as polynomials -/
def s_poly : mv_polynomial vars F := mv_polynomial.X vars.s
def α_poly : mv_polynomial vars F := mv_polynomial.X vars.α
def β_v_poly : mv_polynomial vars F := mv_polynomial.X vars.β_v
def β_w_poly : mv_polynomial vars F := mv_polynomial.X vars.β_w
def β_y_poly : mv_polynomial vars F := mv_polynomial.X vars.β_y
def γ_poly : mv_polynomial vars F := mv_polynomial.X vars.γ

def poly (v : vars) : mv_polynomial vars F := mv_polynomial.X v

-- /-- Multivariable version of t -/
-- def t_mv : mv_polynomial vars F := t.eval₂ mv_polynomial.C X_poly

-- /-- V_stmt as a multivariable polynomial of vars.X -/
-- def V_stmt_mv (a_stmt : fin n_stmt → F) : mv_polynomial vars F
-- := (V_stmt_sv a_stmt).eval₂ mv_polynomial.C X_poly

/-- The crs elements as multivariate polynomials of the toxic waste samples: Evaluation key elements -/
def crs_v_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := (v_wit k).eval₂ mv_polynomial.C s_poly
def crs_v_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := (v_stmt k).eval₂ mv_polynomial.C s_poly -- Part of ver key
def crs_w_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := (w_wit k).eval₂ mv_polynomial.C s_poly
def crs_w_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := (w_stmt k).eval₂ mv_polynomial.C s_poly
def crs_y_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := (y_wit k).eval₂ mv_polynomial.C s_poly
def crs_y_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := (y_stmt k).eval₂ mv_polynomial.C s_poly

def crs_α_v_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := α_poly * (v_wit k).eval₂ mv_polynomial.C s_poly
def crs_α_w_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := α_poly * (w_wit k).eval₂ mv_polynomial.C s_poly
def crs_α_w_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := α_poly * (w_stmt k).eval₂ mv_polynomial.C s_poly
def crs_α_y_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := α_poly * (y_wit k).eval₂ mv_polynomial.C s_poly
def crs_α_y_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := α_poly * (y_stmt k).eval₂ mv_polynomial.C s_poly

def crs_β_v_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := β_v_poly * (v_wit k).eval₂ mv_polynomial.C s_poly
def crs_β_w_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := β_w_poly * (w_wit k).eval₂ mv_polynomial.C s_poly
def crs_β_w_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := β_w_poly * (w_stmt k).eval₂ mv_polynomial.C s_poly
def crs_β_y_wit_k_of_s (k : fin n_wit) : mv_polynomial vars F := β_y_poly * (y_wit k).eval₂ mv_polynomial.C s_poly
def crs_β_y_stmt_k_of_s (k : fin n_stmt) : mv_polynomial vars F := β_y_poly * (y_stmt k).eval₂ mv_polynomial.C s_poly

def crs_powers_of_s (i : fin d) : (mv_polynomial vars F) := s_poly^(i : ℕ)
def crs_α_powers_of_s (i : fin d) : (mv_polynomial vars F) := α_poly * s_poly^(i : ℕ)
-- TODO this is getting out of hand. Maybe we generalize around this bloat?

/-- The crs elements as multivariate polynomials of the toxic waste samples: Verification key elements -/

def crs_α : mv_polynomial vars F := α_poly
def crs_γ : mv_polynomial vars F := γ_poly
def crs_β_v_γ : mv_polynomial vars F := β_v_poly * γ_poly
def crs_β_w_γ : mv_polynomial vars F := β_w_poly * γ_poly
def crs_β_y_γ : mv_polynomial vars F := β_y_poly * γ_poly
def crs_t : mv_polynomial vars F :=
  (t).eval₂ mv_polynomial.C s_poly
def crs_v_0 : mv_polynomial vars F :=
  (v_0).eval₂ mv_polynomial.C s_poly
def crs_w_0 : mv_polynomial vars F :=
  (w_0).eval₂ mv_polynomial.C s_poly
def crs_y_0 : mv_polynomial vars F :=
  (y_0).eval₂ mv_polynomial.C s_poly

/-- The coefficients of the CRS elements in the algebraic adversary's representation -/
parameters {v_wit_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {v_wit_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {v_wit_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {v_wit_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {v_wit_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {v_wit_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {v_wit_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {v_wit_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {v_wit_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {v_wit_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {v_wit_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {v_wit_comp_crs_powers_of_s : fin n_wit →  F}
parameters {v_wit_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {v_wit_comp_crs_α : F}
parameters {v_wit_comp_crs_γ : F}
parameters {v_wit_comp_crs_β_v_γ : F}
parameters {v_wit_comp_crs_β_w_γ : F}
parameters {v_wit_comp_crs_β_y_γ : F}
parameters {v_wit_comp_crs_t : F}
parameters {v_wit_comp_crs_v_0 : F}
parameters {v_wit_comp_crs_w_0 : F}
parameters {v_wit_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_v_wit : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (v_wit_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (v_wit_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  v_wit_comp_crs_α • crs_α
  +
  v_wit_comp_crs_γ • crs_γ
  +
  v_wit_comp_crs_β_v_γ • crs_β_v_γ
  +
  v_wit_comp_crs_β_w_γ • crs_β_w_γ
  +
  v_wit_comp_crs_β_y_γ • crs_β_y_γ
  +
  v_wit_comp_crs_t • crs_t
  +
  v_wit_comp_crs_v_0 • crs_v_0
  +
  v_wit_comp_crs_w_0 • crs_w_0
  +
  v_wit_comp_crs_y_0 • crs_y_0

parameters {w_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {w_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {w_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {w_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {w_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {w_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {w_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {w_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {w_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {w_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {w_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {w_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {w_comp_crs_powers_of_s : fin n_wit →  F}
parameters {w_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {w_comp_crs_α : F}
parameters {w_comp_crs_γ : F}
parameters {w_comp_crs_β_v_γ : F}
parameters {w_comp_crs_β_w_γ : F}
parameters {w_comp_crs_β_y_γ : F}
parameters {w_comp_crs_t : F}
parameters {w_comp_crs_v_0 : F}
parameters {w_comp_crs_w_0 : F}
parameters {w_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_w : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (w_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (w_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  w_comp_crs_α • crs_α
  +
  w_comp_crs_γ • crs_γ
  +
  w_comp_crs_β_v_γ • crs_β_v_γ
  +
  w_comp_crs_β_w_γ • crs_β_w_γ
  +
  w_comp_crs_β_y_γ • crs_β_y_γ
  +
  w_comp_crs_t • crs_t
  +
  w_comp_crs_v_0 • crs_v_0
  +
  w_comp_crs_w_0 • crs_w_0
  +
  w_comp_crs_y_0 • crs_y_0

parameters {y_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {y_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {y_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {y_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {y_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {y_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {y_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {y_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {y_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {y_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {y_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {y_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {y_comp_crs_powers_of_s : fin n_wit →  F}
parameters {y_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {y_comp_crs_α : F}
parameters {y_comp_crs_γ : F}
parameters {y_comp_crs_β_v_γ : F}
parameters {y_comp_crs_β_w_γ : F}
parameters {y_comp_crs_β_y_γ : F}
parameters {y_comp_crs_t : F}
parameters {y_comp_crs_v_0 : F}
parameters {y_comp_crs_w_0 : F}
parameters {y_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_y : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (y_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (y_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  y_comp_crs_α • crs_α
  +
  y_comp_crs_γ • crs_γ
  +
  y_comp_crs_β_v_γ • crs_β_v_γ
  +
  y_comp_crs_β_w_γ • crs_β_w_γ
  +
  y_comp_crs_β_y_γ • crs_β_y_γ
  +
  y_comp_crs_t • crs_t
  +
  y_comp_crs_v_0 • crs_v_0
  +
  y_comp_crs_w_0 • crs_w_0
  +
  y_comp_crs_y_0 • crs_y_0

parameters {h_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {h_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {h_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {h_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {h_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {h_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {h_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {h_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {h_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {h_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {h_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {h_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {h_comp_crs_powers_of_s : fin n_wit →  F}
parameters {h_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {h_comp_crs_α : F}
parameters {h_comp_crs_γ : F}
parameters {h_comp_crs_β_v_γ : F}
parameters {h_comp_crs_β_w_γ : F}
parameters {h_comp_crs_β_y_γ : F}
parameters {h_comp_crs_t : F}
parameters {h_comp_crs_v_0 : F}
parameters {h_comp_crs_w_0 : F}
parameters {h_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_h : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (h_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (h_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  h_comp_crs_α • crs_α
  +
  h_comp_crs_γ • crs_γ
  +
  h_comp_crs_β_v_γ • crs_β_v_γ
  +
  h_comp_crs_β_w_γ • crs_β_w_γ
  +
  h_comp_crs_β_y_γ • crs_β_y_γ
  +
  h_comp_crs_t • crs_t
  +
  h_comp_crs_v_0 • crs_v_0
  +
  h_comp_crs_w_0 • crs_w_0
  +
  h_comp_crs_y_0 • crs_y_0

parameters {α_v_wit_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_v_wit_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_v_wit_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_v_wit_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_v_wit_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_v_wit_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_v_wit_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_v_wit_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_v_wit_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_v_wit_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_v_wit_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_v_wit_comp_crs_powers_of_s : fin n_wit →  F}
parameters {α_v_wit_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {α_v_wit_comp_crs_α : F}
parameters {α_v_wit_comp_crs_γ : F}
parameters {α_v_wit_comp_crs_β_v_γ : F}
parameters {α_v_wit_comp_crs_β_w_γ : F}
parameters {α_v_wit_comp_crs_β_y_γ : F}
parameters {α_v_wit_comp_crs_t : F}
parameters {α_v_wit_comp_crs_v_0 : F}
parameters {α_v_wit_comp_crs_w_0 : F}
parameters {α_v_wit_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_α_v_wit : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_v_wit_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_v_wit_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  α_v_wit_comp_crs_α • crs_α
  +
  α_v_wit_comp_crs_γ • crs_γ
  +
  α_v_wit_comp_crs_β_v_γ • crs_β_v_γ
  +
  α_v_wit_comp_crs_β_w_γ • crs_β_w_γ
  +
  α_v_wit_comp_crs_β_y_γ • crs_β_y_γ
  +
  α_v_wit_comp_crs_t • crs_t
  +
  α_v_wit_comp_crs_v_0 • crs_v_0
  +
  α_v_wit_comp_crs_w_0 • crs_w_0
  +
  α_v_wit_comp_crs_y_0 • crs_y_0

parameters {α_w_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_w_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_w_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_w_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_w_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_w_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_w_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_w_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_w_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_w_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_w_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_w_comp_crs_powers_of_s : fin n_wit →  F}
parameters {α_w_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {α_w_comp_crs_α : F}
parameters {α_w_comp_crs_γ : F}
parameters {α_w_comp_crs_β_v_γ : F}
parameters {α_w_comp_crs_β_w_γ : F}
parameters {α_w_comp_crs_β_y_γ : F}
parameters {α_w_comp_crs_t : F}
parameters {α_w_comp_crs_v_0 : F}
parameters {α_w_comp_crs_w_0 : F}
parameters {α_w_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_α_w : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_w_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_w_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  α_w_comp_crs_α • crs_α
  +
  α_w_comp_crs_γ • crs_γ
  +
  α_w_comp_crs_β_v_γ • crs_β_v_γ
  +
  α_w_comp_crs_β_w_γ • crs_β_w_γ
  +
  α_w_comp_crs_β_y_γ • crs_β_y_γ
  +
  α_w_comp_crs_t • crs_t
  +
  α_w_comp_crs_v_0 • crs_v_0
  +
  α_w_comp_crs_w_0 • crs_w_0
  +
  α_w_comp_crs_y_0 • crs_y_0

parameters {α_y_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_y_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_y_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_y_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_y_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_y_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_y_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_y_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_y_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_y_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_y_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_y_comp_crs_powers_of_s : fin n_wit →  F}
parameters {α_y_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {α_y_comp_crs_α : F}
parameters {α_y_comp_crs_γ : F}
parameters {α_y_comp_crs_β_v_γ : F}
parameters {α_y_comp_crs_β_w_γ : F}
parameters {α_y_comp_crs_β_y_γ : F}
parameters {α_y_comp_crs_t : F}
parameters {α_y_comp_crs_v_0 : F}
parameters {α_y_comp_crs_w_0 : F}
parameters {α_y_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_α_y : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_y_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_y_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  α_y_comp_crs_α • crs_α
  +
  α_y_comp_crs_γ • crs_γ
  +
  α_y_comp_crs_β_v_γ • crs_β_v_γ
  +
  α_y_comp_crs_β_w_γ • crs_β_w_γ
  +
  α_y_comp_crs_β_y_γ • crs_β_y_γ
  +
  α_y_comp_crs_t • crs_t
  +
  α_y_comp_crs_v_0 • crs_v_0
  +
  α_y_comp_crs_w_0 • crs_w_0
  +
  α_y_comp_crs_y_0 • crs_y_0

parameters {α_h_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_h_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_h_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_h_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_h_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_h_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_h_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_h_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {α_h_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {α_h_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {α_h_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {α_h_comp_crs_powers_of_s : fin n_wit →  F}
parameters {α_h_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {α_h_comp_crs_α : F}
parameters {α_h_comp_crs_γ : F}
parameters {α_h_comp_crs_β_v_γ : F}
parameters {α_h_comp_crs_β_w_γ : F}
parameters {α_h_comp_crs_β_y_γ : F}
parameters {α_h_comp_crs_t : F}
parameters {α_h_comp_crs_v_0 : F}
parameters {α_h_comp_crs_w_0 : F}
parameters {α_h_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_α_h : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (α_h_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (α_h_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  α_h_comp_crs_α • crs_α
  +
  α_h_comp_crs_γ • crs_γ
  +
  α_h_comp_crs_β_v_γ • crs_β_v_γ
  +
  α_h_comp_crs_β_w_γ • crs_β_w_γ
  +
  α_h_comp_crs_β_y_γ • crs_β_y_γ
  +
  α_h_comp_crs_t • crs_t
  +
  α_h_comp_crs_v_0 • crs_v_0
  +
  α_h_comp_crs_w_0 • crs_w_0
  +
  α_h_comp_crs_y_0 • crs_y_0

parameters {β_comp_crs_v_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_v_stmt_k_of_s : fin n_stmt →  F}
parameters {β_comp_crs_w_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_w_stmt_k_of_s : fin n_stmt →  F}
parameters {β_comp_crs_y_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_y_stmt_k_of_s : fin n_stmt →  F}

parameters {β_comp_crs_α_v_wit_k_of_s : fin n_wit →  F}
-- parameters {β_comp_crs_α_v_stmt_k_of_s : fin n_stmt →  F}
parameters {β_comp_crs_α_w_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_α_w_stmt_k_of_s : fin n_stmt →  F}
parameters {β_comp_crs_α_y_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_α_y_stmt_k_of_s : fin n_stmt →  F}

parameters {β_comp_crs_β_v_wit_k_of_s : fin n_wit →  F}
-- parameters {β_comp_crs_β_v_stmt_k_of_s : fin n_stmt →  F}
parameters {β_comp_crs_β_w_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_β_w_stmt_k_of_s : fin n_stmt →  F}
parameters {β_comp_crs_β_y_wit_k_of_s : fin n_wit →  F}
parameters {β_comp_crs_β_y_stmt_k_of_s : fin n_stmt →  F}

parameters {β_comp_crs_powers_of_s : fin n_wit →  F}
parameters {β_comp_crs_α_powers_of_s : fin n_stmt →  F}

parameters {β_comp_crs_α : F}
parameters {β_comp_crs_γ : F}
parameters {β_comp_crs_β_v_γ : F}
parameters {β_comp_crs_β_w_γ : F}
parameters {β_comp_crs_β_y_γ : F}
parameters {β_comp_crs_t : F}
parameters {β_comp_crs_v_0 : F}
parameters {β_comp_crs_w_0 : F}
parameters {β_comp_crs_y_0 : F}

/-- Polynomial forms of the adversary's proof representation -/
def proof_β : mv_polynomial vars F :=
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_v_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_α_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_α_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_α_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_α_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_α_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_β_v_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_β_y_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_β_y_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_β_w_wit_k_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_β_w_stmt_k_of_s i) • (crs_v_stmt_k_of_s i)
  +
  ∑ i in (finset.fin_range n_wit), (β_comp_crs_powers_of_s i) • (crs_v_wit_k_of_s i)
  +
  ∑ i in (finset.fin_range n_stmt), (β_comp_crs_α_powers_of_s i) • (crs_v_stmt_k_of_s i)
  +
  β_comp_crs_α • crs_α
  +
  β_comp_crs_γ • crs_γ
  +
  β_comp_crs_β_v_γ • crs_β_v_γ
  +
  β_comp_crs_β_w_γ • crs_β_w_γ
  +
  β_comp_crs_β_y_γ • crs_β_y_γ
  +
  β_comp_crs_t • crs_t
  +
  β_comp_crs_v_0 • crs_v_0
  +
  β_comp_crs_w_0 • crs_w_0
  +
  β_comp_crs_y_0 • crs_y_0

-- Single variable form of V_wit
def v_wit_sv (a_wit : fin n_wit → F) : polynomial F
:= ∑ i in finset.fin_range n_wit, a_wit i • v_wit i

/-- The statement polynomial that the verifier computes from the statement bits, as a single variable polynomial -/
def v_stmt_sv (a_stmt : fin n_stmt → F) : polynomial F
:= ∑ i in (finset.fin_range n_stmt), a_stmt i • v_stmt i

/-- V_stmt as a multivariable polynomial of vars.X -/
def v_stmt_mv (a_stmt : fin n_stmt → F) : mv_polynomial vars F
:= (v_stmt_sv a_stmt).eval₂ mv_polynomial.C s_poly

/-- V as a multivariable polynomial -/
def proof_v (a_stmt : fin n_stmt → F) : mv_polynomial vars F
:= v_stmt_mv a_stmt + proof_v_wit

/-- Multivariable version of t -/
def t_mv : mv_polynomial vars F := t.eval₂ mv_polynomial.C s_poly

/-- Show that if the adversary polynomials obey the equations,
then the coefficients give a satisfying witness.
-/
theorem soundness (a_stmt : fin n_stmt → F ) :
  (0 < m)
  -> proof_α_h = proof_h * crs_α
  -> proof_α_v_wit = proof_v_wit * crs_α
  -> proof_α_y = proof_y * crs_α
  -> proof_α_w = proof_w * crs_α
  -> proof_β * crs_γ = crs_β_v_γ * (proof_v a_stmt) + crs_β_y_γ * proof_y + crs_β_w_γ * proof_w
  -- NOTE: Why does this require 3 pairings according to
  -> proof_h * t_mv + crs_y_0 + proof_y = (crs_v_0 + v_stmt_mv a_stmt + proof_v a_stmt) * (crs_w_0 + proof_w)
  -- NOTE: This is what the paper says, but it seems wrong
  -> (satisfying a_stmt v_wit_comp_crs_v_wit_k_of_s)
:=
begin
  sorry
end

end


import ground_zero.types.equiv

namespace ground_zero.types.heq

universes u v
def from_homo {α : Sort u} {a b : α} (h : a = b) : a == b :=
begin induction h, reflexivity end

def inclusion {α : Sort u} {a b : α} : (a = b :> α) → a == b :=
from_homo ∘ ground_zero.support.truncation

def map {α : Sort u} {β : α → Sort v} {a b : α}
(f : Π (x : α), β x) (p : a = b) : f a == f b :=
begin induction p, reflexivity end

def only_refl {α : Sort u} {a b : α} (p : a = b) : p == (eq.refl a) :=
begin induction p, trivial end

lemma eq_subst_heq {α : Sort u} {π : α → Sort v}
  {a b : α} (p : a = b :> α) (x : π a) :
  x == ground_zero.types.equiv.subst p x :=
begin induction p, reflexivity end

def from_pathover {α : Sort u} {π : α → Sort v}
  {a b : α} (p : a = b :> α) {u : π a} {v : π b}
  (q : u =[p] v) : u == v :=
begin induction q, reflexivity end

end ground_zero.types.heq


/-
Copyright (c) 2022 Devon Tuma. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Devon Tuma
-/
import data.vector.zip
import crypto_foundations.primitives.signature
import crypto_foundations.hardness_assumptions.hard_homogeneous_space
import computational_monads.constructions.repeat
import computational_monads.constructions.forking_lemma

/-!
# Signature Scheme Based On Hard Homogenous Spaces

This file defines a signature scheme based on hard homogenous spaces,
using a generalized version of the basic Diffie-Helmann signature scheme.

We prove both completeness and unforgeability, providing an explicit reduction
from signature forgery to a vectorization forgery.
-/

noncomputable theory

open oracle_comp oracle_spec

section commits

variables {G X M : Type} [fintype G] [fintype X]
  [decidable_eq G] [decidable_eq X]
  [add_group G] [algorithmic_homogenous_space G X] {n : ℕ}

/-- Function used in signing to combine the random commitments with the resulting hash,
  using the provided secret key to prove that the secret key corresponds to the public key -/
@[reducible, inline]
def zip_commits_with_hash (cs : vector G n) (h : vector bool n) (sk : G) : vector (G × bool) n :=
vector.zip_with (λ c (b : bool), (if b then c else c + sk, b)) cs h

lemma zip_commits_with_hash_nil (sk : G) :
  zip_commits_with_hash vector.nil vector.nil sk = vector.nil :=
rfl

@[reducible, inline]
def retrieve_commits (x₀ pk: X) (σ : vector (G × bool) n) : vector X n :=
(σ.map (λ ⟨c, b⟩, if b then c +ᵥ pk else c +ᵥ x₀))

lemma retrieve_commits_nil (x₀ pk : X) :
  retrieve_commits x₀ pk (@vector.nil $ G × bool) = vector.nil :=
rfl

end commits

/-- Signature derived from a hard homogenous space, based on the diffie helmann case.
  `n` represents the number of commitments to make, more corresponding to more difficult forgery.
  `x₀` is some arbitrary public base point in `X`, used to compute public keys from secret keys -/
def hhs_signature (G X M : Type) (n : ℕ) [fintype G] [fintype X] [inhabited G] [inhabited X]
  [decidable_eq G] [decidable_eq X] [decidable_eq M] [add_group G]
  [algorithmic_homogenous_space G X] : signature :=
{ M := M, PK := X × X, SK := G, S := vector (G × bool) n,
  gen := λ _,
  do{ x₀ ←$ᵗ X, sk ←$ᵗ G,
      return ((x₀, sk +ᵥ x₀), sk) },
  sign := λ ⟨⟨x₀, pk⟩, sk, m⟩,
  do{ (cs : vector G n) ← repeat ($ᵗ G) n,
      (ys : vector X n) ← return (cs.map (λ c, c +ᵥ pk)),
      (h : vector bool n) ← query₂ () (ys, m),
      return (zip_commits_with_hash cs h sk) },
  verify := λ ⟨⟨x₀, pk⟩, m, σ⟩,
  do{ (h : vector bool n) ← query₂ () (retrieve_commits x₀ pk σ, m),
      return (h = σ.map prod.snd) },
  random_oracle_spec := ((vector X n × M) ↦ₒ vector bool n),
  decidable_eq_M := by apply_instance,
  decidable_eq_S := by apply_instance,
  inhabited_S := by apply_instance,
  fintype_S := by apply_instance }

namespace hhs_signature

variables {G X M : Type} [fintype G] [fintype X] [inhabited G] [inhabited X]
  [decidable_eq G] [decidable_eq X] [decidable_eq M]
  [add_group G] [algorithmic_homogenous_space G X] {n : ℕ}

@[simp]
lemma support_coe_uniform_selecting_oracles [inhabited G] {α : Type}
  (oa : oracle_comp uniform_selecting α) :
  support (↑oa : oracle_comp (hhs_signature G X M n).base_oracle_spec α) = oa.support :=
begin
  sorry
end

section gen

@[simp]
lemma gen_apply (u : unit) : ((hhs_signature G X M n).gen u) =
  do { x₀ ←$ᵗ X, sk ←$ᵗ G, return ((x₀, sk +ᵥ x₀), sk) } := rfl

lemma support_gen : ((hhs_signature G X M n).gen ()).support =
  ⋃ (x₀ : X) (sk : G), { ((x₀, sk +ᵥ x₀), sk) } :=
by simp only [gen_apply, support_bind, support_coe_uniform_selecting_oracles,
  support_uniform_select_fintype, support_return, set.Union_true]

end gen

section sign

@[simp]
lemma sign_apply (x₀ pk : X) (sk : G) (m : M) :
  ((hhs_signature G X M n).sign ((x₀, pk), sk, m)) =
  do{ (cs : vector G n) ← repeat ($ᵗ G) n,
      (ys : vector X n) ← return (cs.map (λ c, c +ᵥ pk)),
      (h : vector bool n) ← query₂ () (ys, m),
      return (zip_commits_with_hash cs h sk) } := rfl

@[simp]
lemma support_sign (x₀ pk : X) (sk : G) (m : M) :
  ((hhs_signature G X M n).sign ((x₀, pk), sk, m)).support =
    ⋃ (cs : vector G n) (h : vector bool n), { zip_commits_with_hash cs h sk } :=
sorry

end sign

section verify

@[simp]
lemma verify_apply {n : ℕ} (x₀ pk : X) (m : M) (σ : vector (G × bool) n) :
  ((hhs_signature G X M n).verify ((x₀, pk), m, σ)) =
  do{ (h : vector bool n) ← query₂ () (retrieve_commits x₀ pk σ, m),
      return (h = σ.map prod.snd) } := rfl

end verify

section is_valid_signature

/-- Check if a signature is valid, given an explicit internal state of the random oracle.
Note that we return `false` for things not-previously queried,
which differs from e.g. the behaviour of the actual `unforgeable` experiment. -/
def is_valid_signature (x₀ pk : X) (m : M) (σ : vector (G × bool) n)
  (cache : query_log ((vector X n × M) ↦ₒ vector bool n)) : Prop :=
match cache.lookup () (retrieve_commits x₀ pk σ, m) with
| none := true
| (some h) := h = σ.map prod.snd
end

/-- Sign always returns valid signatures, in terms of the final random oracle state. -/
lemma is_valid_signature_of_mem_support_sign (x₀ pk : X) (sk : G) (m : M)
  (σ : vector (G × bool) n) (cache : query_log (hhs_signature G X M n).random_oracle_spec)
  (h : (σ, (), cache) ∈ (default_simulate (idₛ ++ₛ random_oracle _) $
    (hhs_signature G X M n).sign ((x₀, pk), sk, m)).support) :
  is_valid_signature x₀ pk m σ cache :=
begin
  sorry
end

/-- Given a valid signature for some random oracle state, verify returns true
assuming the state is initialized with that cache. -/
lemma support_verify_of_is_valid_signature (x₀ pk : X) (m : M)
  (σ : vector (G × bool) n) (cache : query_log (hhs_signature G X M n).random_oracle_spec)
  (h : is_valid_signature x₀ pk m σ cache) : (simulate (idₛ ++ₛ random_oracle _)
    ((hhs_signature G X M n).verify ((x₀, pk), m, σ)) ((), cache)).support = {(tt, (), cache)} :=
begin
  sorry
end

end is_valid_signature

section complete

theorem hhs_signature_complete [inhabited G] :
  (hhs_signature G X M n).complete :=
begin
  rw signature.complete_iff_signatures_support_subset,
  rintros m ⟨x₀, pk⟩ sk σ hgen hsign,
  sorry
end

end complete

section unforgeable_reductions

variables (x₀ pk : X) (adversary : (hhs_signature G X M n).unforgeable_adversary)

section mock_signing_oracle

/-- Oracle to mock a signing oracle for messages in the vectorization reduction.
Predetermines the random oracle results to fake a valid signature,
keeping the results in a seperate internal mocked cache.
Queries to the random oracle are first filtered through this,
and are passed to the true random oracle only if the query is fresh. -/
def mock_signing_oracle : sim_oracle (hhs_signature G X M n).unforgeable_adversary_oracle_spec
  (hhs_signature G X M n).base_oracle_spec
  (query_log (hhs_signature G X M n).random_oracle_spec) :=
{ default_state := query_log.init _,
  o := λ i, match i with
    -- For uniform selection queries, simply forward them to the lower level.
    | (sum.inl (sum.inl i)) := λ ⟨t, mock_cache⟩,
      do{ u ← query (sum.inl i) t,
          return (u, mock_cache) }
    -- For random oracle queries, check if the query has been mocked.
    -- If so, return the mocked value, otherwise call the regular random oracle
    | (sum.inl (sum.inr i)) := λ ⟨t, mock_cache⟩, mock_cache.lookup_cached_or_run i t
      (@query (hhs_signature G X M n).base_oracle_spec (sum.inr i) t)
    | (sum.inr ()) := λ ⟨m, mock_cache⟩,
      do{ bs ← repeat ($ᵗ bool) n, -- pre-select what all the bool results will be.
          cs ← repeat ($ᵗ G) n,
          ys ← return (vector.zip_with (λ (b : bool) c, if b then c +ᵥ pk else c +ᵥ x₀) bs cs),
          mock_cache' ← return (mock_cache.log_query () (ys, m) bs),
          return (vector.zip_with prod.mk cs bs, mock_cache') }
  end }

end mock_signing_oracle

section mock_signing_reduction

/-- Fake the signing oracle, and force a query corresponding to adversary's result. -/
def mock_signing_reduction :
  oracle_comp (hhs_signature G X M n).base_oracle_spec (M × vector (G × bool) n) :=
do{ ⟨m, σ⟩ ← default_simulate' (mock_signing_oracle x₀ pk) (adversary.adv (x₀, pk)),
    _ ← query₂ () (retrieve_commits x₀ pk σ, m), -- force a query to the final output
    return (m, σ) }

/-- Further simulate the random oracle after mocking the signing oracle-/
def simulate_mock_signing_reduction : oracle_comp uniform_selecting (M × vector (G × bool) n ×
  query_log (hhs_signature G X M n).random_oracle_spec) :=
do{ ⟨⟨m, σ⟩, (), cache⟩ ← default_simulate (idₛ ++ₛ random_oracle _)
      (mock_signing_reduction x₀ pk adversary),
    return (m, σ, cache) }

theorem prob_event_is_valid_signature_ge_unforgeable_advantage  :
  ⁅λ ⟨m, σ, cache⟩, is_valid_signature x₀ pk m σ cache |
    simulate_mock_signing_reduction x₀ pk adversary⁆
      ≥ adversary.advantage :=
begin
  sorry
end

end mock_signing_reduction

section choose_fork

/- The choose_fork that will be passed to forking lemma. `q` will be the
max queries of the adversary (given by its `query_bound`).
Returns none if it hasn't been queried or if the signature isn't valid.

Note that if the message was queried to signing oracle (causing the experiment to fail
for unforgeability), then the result will only be in the higher level cache,
not the lower level one being used here (since it never leaves the "mock") -/
def choose_fork (m : M) (σ : vector (G × bool) n)
  (cache : query_log (hhs_signature G X M n).random_oracle_spec) (q : ℕ) : option (fin q) :=
match cache.lookup_with_index () (retrieve_commits x₀ pk σ, m) with
| none := none
| (some ⟨h, index⟩) := if hq : index < q ∧ h = σ.map prod.snd then some ⟨index, hq.1⟩ else none
end

end choose_fork

section fork_reduction

/-- Reduce an unforgeability adversery to a forking adversary that can be passed to
`oracle_comp.fork` to get two results, that can be used to construct a adversary for
vectorization in the hard homogenous space.
`q` is the maximum number of queries made by the adversary to consider. -/
def forking_adversary_reduction (adversary : (hhs_signature G X M n).unforgeable_adversary) :
  forking_adversary (vector X n × M) (vector bool n) (M × vector (G × bool) n) :=
{ adv := mock_signing_reduction x₀ pk adversary,
  q := adversary.query_bound,
  choose_fork := λ ⟨m, σ⟩ log, choose_fork x₀ pk m σ log (adversary.query_bound),
  cache_nonempty := sorry,
  no_overflow := sorry }

/-- The forked adversary reduction succeeds at least as often as the original -/
theorem forking_adversary_reduction_advantage :
  (forking_adversary_reduction x₀ pk adversary).advantage ≥ adversary.advantage :=
calc (forking_adversary_reduction x₀ pk adversary).advantage
  = ⁅λ ⟨m, σ, cache⟩, is_valid_signature x₀ pk m σ cache |
      simulate_mock_signing_reduction x₀ pk adversary⁆ :
    sorry
  ... ≥ adversary.advantage :
    prob_event_is_valid_signature_ge_unforgeable_advantage x₀ pk adversary

variables (i : option $ fin adversary.query_bound) (m m' : M)
  (cache cache' : query_log (hhs_signature G X M n).random_oracle_spec)
  (σ σ' : vector (G × bool) n)

section lookup

/-- `choose_fork` forces the resulting cache to have a query
matching the resulting `bool`s in the signature -/
theorem lookup_left_eq_signature_bools (hi : i.is_some)
  (h : (i, (m, σ), cache, (m', σ'), cache') ∈
    (fork (forking_adversary_reduction pk x₀ adversary)).support) :
  cache.lookup () (retrieve_commits x₀ pk σ, m) = some (σ.map prod.snd) :=
begin
  sorry
end

/-- `choose_fork` forces the resulting cache to have a query
matching the resulting `bool`s in the signature -/
theorem lookup_right_eq_signature_bools (hi : i.is_some)
  (h : (i, (m, σ), cache, (m', σ'), cache') ∈
    (fork (forking_adversary_reduction pk x₀ adversary)).support) :
  cache'.lookup () (retrieve_commits x₀ pk σ', m') = some (σ'.map prod.snd) :=
begin
  sorry
end

/-- If the fork succeeds at giving different outputs at the forked query,
then that must be true for the signature's commits, by definition of `choose_fork`. -/
theorem lookup_ne_lookup_of_cache_differs (hi : i.is_some)
  (h : (i, (m, σ), cache, (m', σ'), cache') ∈
    (fork (forking_adversary_reduction pk x₀ adversary)).support)
  (h' : forked_cache_differs (i, (m, σ), cache, (m', σ'), cache')) :
  cache'.lookup () (retrieve_commits x₀ pk σ', m') ≠
    cache.lookup () (retrieve_commits x₀ pk σ, m) :=
begin
  sorry
end

end lookup

/-- If fork returns a non-`none` index, then the inputs to the oracle must also match,
in particular the output of `retrieve_commits` must also match,
since `choose_fork` uses this to decide which oracle to fork on. -/
theorem retrieve_commits_eq_retrieve_commits (hi : i.is_some)
  (h : (i, (m, σ), cache, (m', σ'), cache') ∈
    (fork (forking_adversary_reduction pk x₀ adversary)).support) :
  retrieve_commits x₀ pk σ = retrieve_commits x₀ pk σ' :=
begin
  sorry
end

/-- If the forked caches differ, then so must the booleans in the resulting signature,
since they must be exactly the outputs of the oracle for signing to succeed -/
theorem oracle_output_ne_oracle_output (hi : i.is_some)
  (h : (i, (m, σ), cache, (m', σ'), cache') ∈
    (fork (forking_adversary_reduction pk x₀ adversary)).support)
  (h' : forked_cache_differs (i, (m, σ), cache, (m', σ'), cache')) :
  σ.map prod.snd ≠ σ'.map prod.snd :=
begin
  sorry
end

/-- If the fork succeeds, we know that there are two valid signatures
corresponding to a query with the same input and a different output.
This further implies that `retrieve_commits` agrees on both,
but the actual booleans are different, which will let us get a vectorization. -/
theorem vectorizable_of_fork_success
  (h : (i, (m, σ), cache, (m', σ'), cache') ∈
    (fork (forking_adversary_reduction pk x₀ adversary)).support)
  (h' : fork_success (i, (m, σ), cache, (m', σ'), cache')) :
  retrieve_commits x₀ pk σ = retrieve_commits x₀ pk σ'
    ∧ σ.map prod.snd ≠ σ'.map prod.snd :=
let ⟨hi, hcache⟩ := h' in
⟨retrieve_commits_eq_retrieve_commits x₀ pk _ i m m' cache cache' σ σ' hi h,
  oracle_output_ne_oracle_output x₀ pk _ i m m' cache cache' σ σ' hi h hcache⟩

/-- The probability of the fork succeeding is at least the square of
the original adversary's success probability, minus a small chance
of both oracle calls giving the same result. -/
theorem prob_event_fork_success_reduction (x₀ pk : X)
  (adversary : (hhs_signature G X M n).unforgeable_adversary) :
  ⁅fork_success | fork (forking_adversary_reduction pk x₀ adversary)⁆ ≥
    (adversary.advantage ^ 2 / (adversary.query_bound)) - (1 / 2 ^ n) :=
calc ⁅fork_success | fork (forking_adversary_reduction pk x₀ adversary)⁆
  ≥ ((forking_adversary_reduction pk x₀ adversary).advantage ^ 2 / (adversary.query_bound))
      - (1 / (fintype.card $ vector bool n)) :
    prob_event_fork_success (forking_adversary_reduction pk x₀ adversary)
  ... ≥ ((forking_adversary_reduction pk x₀ adversary).advantage ^ 2 /
          (adversary.query_bound)) - (1 / 2 ^ n) :
    begin
      rw [card_vector, fintype.card_bool, nat.cast_pow, nat.cast_two],
      exact le_rfl
    end
  ... ≥ (adversary.advantage ^ 2 / (adversary.query_bound)) - (1 / 2 ^ n) :
    sorry

end fork_reduction

section vectorization_reduction

open algorithmic_homogenous_space

def vectorization_of_signatures (σ σ' : vector (G × bool) n) : G :=
(σ.to_list.zip σ'.to_list).foldr (λ ⟨⟨g₀, b⟩, ⟨g₁, b'⟩⟩ g,
  if b then (if b' then g else g₁ - g₀) else (if b' then g₀ - g₁ else g)) (arbitrary G)

theorem vectorization_of_signatures_eq_of_exists_index (σ σ' : vector (G × bool) n)
  (h : retrieve_commits pk x₀ σ = retrieve_commits pk x₀ σ')
  (h' : σ.map prod.snd ≠ σ'.map prod.snd) :
  vectorization_of_signatures σ σ' = pk -ᵥ x₀ :=
begin
  sorry
end

/-- Reduce an unforgeability adversery to a hhs vectorization adversary,
by forking the adversary and using the two different results -/
def vectorization_adversary_reduction (adversary : (hhs_signature G X M n).unforgeable_adversary) :
  vectorization_adversary G X :=
{ adv := λ ⟨pk, x₀⟩,
    do{ ⟨i, ⟨m, σ⟩, cache, ⟨m', σ'⟩, cache'⟩ ← fork (forking_adversary_reduction pk x₀ adversary),
        return (vectorization_of_signatures σ σ') } }

theorem main_result (adversary : (hhs_signature G X M n).unforgeable_adversary) :
  ⁅(=) (pk -ᵥ x₀) | (vectorization_adversary_reduction adversary).adv (pk, x₀)⁆ ≥
    (adversary.advantage ^ 2 / adversary.query_bound) - (1 / 2 ^ n)  :=
calc ⁅(=) (pk -ᵥ x₀) | (vectorization_adversary_reduction adversary).adv (pk, x₀)⁆
  = ⁅λ o, (vectorization_of_signatures o.2.1.2 o.2.2.2.1.2) = pk -ᵥ x₀ |
      fork (forking_adversary_reduction pk x₀ adversary)⁆ :
    sorry
  ... ≥ ⁅fork_success | fork (forking_adversary_reduction pk x₀ adversary)⁆ :
    begin
      refine prob_event_mono _ _,
      intros o ho,
      -- have := vectorizable_of_fork_success x₀ pk _ _ _ _ _ _ _ _ _ ho,
      apply vectorization_of_signatures_eq_of_exists_index,
      refine (vectorizable_of_fork_success pk x₀ _ _ o.2.1.1 o.2.2.2.1.1 _ _ _ _ sorry ho.1).1,
      refine (vectorizable_of_fork_success pk x₀ _ _ o.2.1.1 o.2.2.2.1.1 _ _ _ _ sorry ho.1).2
    end
  ... ≥ (adversary.advantage ^ 2 / adversary.query_bound) - (1 / 2 ^ n) :
    prob_event_fork_success_reduction x₀ pk _

lemma vectorization_adversary_reduction_advantage
  (adversary : (hhs_signature G X M n).unforgeable_adversary) :
  (vectorization_adversary_reduction adversary).advantage ≥
    (adversary.advantage ^ 2 / adversary.query_bound) - (1 / 2 ^ n) :=
calc (vectorization_adversary_reduction adversary).advantage
  = (∑' pk x₀, ⁅(=) (pk -ᵥ x₀) |
      (vectorization_adversary_reduction adversary).adv (pk, x₀)⁆) / (fintype.card X) ^ 2 :
    vectorization_adversary.advantage_eq_tsum (vectorization_adversary_reduction adversary)
  ... ≥ (∑' (pk x₀ : X), (adversary.advantage ^ 2 / adversary.query_bound - (1 / 2 ^ n))) /
          (fintype.card X) ^ 2 :
    begin
      sorry,
      -- exact div_le_div_of_le_of_nonneg (tsum_le_tsum (λ pk, tsum_le_tsum
      --   (λ x₀, by apply main_result) sorry sorry) sorry sorry) zero_le'
    end
  ... = (adversary.advantage ^ 2 / adversary.query_bound) - (1 / 2 ^ n) :
    begin
      simp_rw [tsum_fintype, finset.sum_const, nsmul_eq_mul],
      rw [← mul_assoc, ← pow_two, div_eq_mul_inv, mul_comm, ← mul_assoc],
      have : (↑(fintype.card X) : nnreal) ^ 2 ≠ 0 := sorry,
      sorry,
      -- erw [inv_mul_cancel this, one_mul],
    end

end vectorization_reduction

end unforgeable_reductions

end hhs_signature

/-- Construct a signature scheme by incrementing both the hard homogenous space parameter,
and the number of commits made by the signature.
Both are controlled by the same parameter, but this could eventually be generalized -/
def hhs_signature_scheme (G X : ℕ → Type) (M : Type)
  [∀ n, fintype $ G n] [∀ n, fintype $ X n] [∀ n, inhabited $ G n] [∀ n, inhabited $ X n]
  [∀ n, decidable_eq $ G n] [∀ n, decidable_eq $ X n] [decidable_eq M]
  [∀ n, add_group $ G n] [∀ n, algorithmic_homogenous_space (G n) (X n)]
  [hard_homogenous_space G X] : signature_scheme :=
λ n, hhs_signature (G n) (X n) M n

namespace hhs_signature_scheme

section unforgeable

open algorithmic_homogenous_space

variables (G X : ℕ → Type) (M : Type)
  [∀ n, fintype $ G n] [∀ n, fintype $ X n] [∀ n, inhabited $ G n] [∀ n, inhabited $ X n]
  [∀ n, decidable_eq $ G n] [∀ n, decidable_eq $ X n] [decidable_eq M]
  [∀ n, add_group $ G n] [∀ n, algorithmic_homogenous_space (G n) (X n)]
  [hard_homogenous_space G X]

theorem hhs_signature_scheme_unforgeable : (hhs_signature_scheme G X M).unforgeable :=
begin
  intros adversary _,
  let reduction : Π (sp : ℕ), vectorization_adversary (G sp) (X sp) :=
    λ n, hhs_signature.vectorization_adversary_reduction (adversary n),
  have := hard_homogenous_space.vectorization_hard reduction,
  sorry,
end

end unforgeable

end hhs_signature_scheme