import combinatorics.simple_graph.coloring

--OUTPUT 1
theorem two_colorable_iff_bipartite {G : Type*} [graph G] : two_colorable G ↔ bipartite G :=
begin
  have h1 : two_colorable G → bipartite G, from sorry,
  have h2 : bipartite G → two_colorable G, from sorry,
  show two_colorable G ↔ bipartite G, from sorry,
end

--OUTPUT 2
theorem bipartite_graph_two_colorable (G : Type*) (EG : set (G × G)) :
  (∃ (A B : set G), ∀ (e : (G × G)), e ∈ EG → ((e.1 ∈ A) ∧ (e.2 ∈ B)) ∨ ((e.1 ∈ B) ∧ (e.2 ∈ A))) ↔
  (∃ (A B : set G), ∀ (v : G), ((v ∈ A) ∨ (v ∈ B)) ∧ (∀ (e : (G × G)), e ∈ EG → ((e.1 ∈ A) ∧ (e.2 ∈ B)) ∨ ((e.1 ∈ B) ∧ (e.2 ∈ A)))) :=
begin
  split,
  { assume h1 : ∃ (A B : set G), ∀ (e : (G × G)), e ∈ EG → ((e.1 ∈ A) ∧ (e.2 ∈ B)) ∨ ((e.1 ∈ B) ∧ (e.2 ∈ A)),
    cases h1 with A h2,
    cases h2 with B h3,
    use A,
    use B,
    assume v,
    split,
    { assume h4 : v ∉ A,
      sorry
    },
    { assume h5 : v ∉ B,
      sorry
    },
    { sorry },
  },
  { assume h1 : ∃ (A B : set G), ∀ (v : G), ((v ∈ A) ∨ (v ∈ B)) ∧ (∀ (e : (G × G)), e ∈ EG → ((e.1 ∈ A) ∧ (e.2 ∈ B)) ∨ ((e.1 ∈ B) ∧ (e.2 ∈ A))),
    cases h1 with A h2,
    cases h2 with B h3,
    use A,
    use B,
    assume e,
    assume h4 : e ∈ EG,
    have h5 : ((e.1 ∈ A) ∨ (e.2 ∈ A)) ∧ ((e.1 ∈ B) ∨ (e.2 ∈ B)), from sorry,
    cases h5 with h6 h7,
    cases h6 with h6a h6b,
    cases h7 with h7a h7b,
    sorry
  }
end

--OUTPUT 3
theorem bipartite_graph_is_two_colorable {G : Type*} [graph G] : ∃! c : G → bool, is_coloring c G 2 :=
begin
  have h1 : ∀ c : G → bool, is_coloring c G 2 → ∃ V1 V2 : set G, is_bipartite V1 V2 G, from sorry,
  have h2 : ∀ V1 V2 : set G, is_bipartite V1 V2 G → ∃! c : G → bool, is_coloring c G 2, from sorry,

  have h3 : ∃! c : G → bool, is_coloring c G 2 ↔ ∃ V1 V2 : set G, is_bipartite V1 V2 G, from sorry,
  show ∃! c : G → bool, is_coloring c G 2, from sorry,
end

--OUTPUT 4
theorem bipartite_graph_2_colorable (G : Type*) [graph G] : is_bipartite G ↔ is_2_colorable G :=
begin
  show is_bipartite G ↔ is_2_colorable G, from sorry,
end

--OUTPUT 5
theorem bipartite_graph_is_two_colorable {G : Type*} [graph G] : is_2_colorable G ↔ is_bipartite G :=
begin
  have h1 : is_2_colorable G → is_bipartite G, from sorry,
  have h2 : is_bipartite G → is_2_colorable G, from sorry,
  show is_2_colorable G ↔ is_bipartite G, from sorry,
end

--OUTPUT 6
theorem bipartite_graph_two_colorable {G : Type*} [graph G] : bipartite G ↔ two_colorable G := sorry

/--`theorem`
If two graphs are isomorphic, then they are bipartite if and only if they are two colorable
Let $G_{1}$ and $G_{2}$ be two isomorphic graphs. Then $G_{1}$ is 2-colorable if and only if $G_{2}$ is 2-colorable and $G_{1}$ is bipartite if and only if $G_{2}$ is bipartite.
`proof`
Let $f$ be an isomorphism from $G_{1}$ to $G_{2}$.

Suppose $G_{1}$ is 2-colorable. We can color every vertex either red or blue, and no edge will have both endpoints colored the same color. Let $A$ denote the subset of vertices colored red, and let $B$ denote the subset of vertices colored blue. Since all vertices of $A$ are red, there are no edges within $A$, and similarly for $B$. This implies that every edge has one endpoint in $A$ and the other in $B$, which means $G_{1}$ is bipartite.

Now suppose $G_{2}$ is 2-colorable. We can color every vertex either red or blue, and no edge will have both endpoints colored the same color. Let $A$ denote the subset of vertices colored red, and let $B$ denote the subset of vertices colored blue. Since all vertices of $A$ are red, there are no edges within $A$, and similarly for $B$. This implies that every edge has one endpoint in $A$ and the other in $B$, which means $G_{2}$ is bipartite.

By the definition of isomorphism, we know that $f(A) = A$ and $f(B) = B$. This means that the vertices of $G_{1}$ colored red are the same vertices of $G_{2}$ colored red, and the vertices of $G_{1}$ colored blue are the same vertices of $G_{2}$ colored blue. Since the vertices of $A$ and $B$ have no edges among themselves, and all edges have one endpoint in $A$ and the other in $B$, it follows that $G_{1}$ is bipartite.

Conversely, suppose $G_{1}$ is bipartite, that is, we can partition the vertices into two subsets $V_{1}, V_{2}$ every edge has one endpoint in $V_{1}$ and the other in $V_{2}$. Then coloring every vertex of $V_{1}$ red and every vertex of $V_{2}$ blue yields a valid coloring, so $G_{1}$ is 2-colorable.

Now suppose $G_{2}$ is bipartite, that is, we can partition the vertices into two subsets $V_{1}, V_{2}$ every edge has one endpoint in $V_{1}$ and the other in $V_{2}$. Then coloring every vertex of $V_{1}$ red and every vertex of $V_{2}$ blue yields a valid coloring, so $G_{2}$ is 2-colorable.

By the definition of isomorphism, we know that $f(V_{1}) = V_{1}$ and $f(V_{2}) = V_{2}$. This means that the vertices of $G_{1}$ colored red are the same vertices of $G_{2}$ colored red, and the vertices of $G_{1}$ colored blue are the same vertices of $G_{2}$ colored blue. Since the vertices of $V_{1}$ and $V_{2}$ have no edges among themselves, and all edges have one endpoint in $V_{1}$ and the other in $V_{2}$, it follows that $G_{1}$ is bipartite.

QED
-/
theorem isomorphic_graph_two_colorable_iff {G1 G2 : Type*} [graph G1] [graph G2] (f : G1 ≃ G2) : two_colorable G1 ↔ two_colorable G2 := sorry

/--`theorem`
If two graphs are isomorphic, then they are bipartite if and only if they are two colorable
Let $G_{1}$ and $G_{2}$ be two isomorphic graphs. Then $G_{1}$ is 2-colorable if and only if $G_{2}$ is 2-colorable and $G_{1}$ is bipartite if and only if $G_{2}$ is bipartite.
`proof`
Let $f$ be an isomorphism from $G_{1}$ to $G_{2}$.

Suppose $G_{1}$ is 2-colorable. We can color every vertex either red or blue, and no edge will have both endpoints colored the same color. Let $A$ denote the subset of vertices colored red, and let $B$ denote the subset of vertices colored blue. Since all vertices of $A$ are red, there are no edges within $A$, and similarly for $B$. This implies that every edge has one endpoint in $A$ and the other in $B$, which means $G_{1}$ is bipartite.

Now suppose $G_{2}$ is 2-colorable. We can color every vertex either red or blue, and no edge will have both endpoints colored the same color. Let $A$ denote the subset of vertices colored red, and let $B$ denote the subset of vertices colored blue. Since all vertices of $A$ are red, there are no edges within $A$, and similarly for $B$. This implies that every edge has one endpoint in $A$ and the other in $B$, which means $G_{2}$ is bipartite.

By the definition of isomorphism, we know that $f(A) = A$ and $f(B) = B$. This means that the vertices of $G_{1}$ colored red are the same vertices of $G_{2}$ colored red, and the vertices of $G_{1}$ colored blue are the same vertices of $G_{2}$ colored blue. Since the vertices of $A$ and $B$ have no edges among themselves, and all edges have one endpoint in $A$ and the other in $B$, it follows that $G_{1}$ is bipartite.

Conversely, suppose $G_{1}$ is bipartite, that is, we can partition the vertices into two subsets $V_{1}, V_{2}$ every edge has one endpoint in $V_{1}$ and the other in $V_{2}$. Then coloring every vertex of $V_{1}$ red and every vertex of $V_{2}$ blue yields a valid coloring, so $G_{1}$ is 2-colorable.

Now suppose $G_{2}$ is bipartite, that is, we can partition the vertices into two subsets $V_{1}, V_{2}$ every edge has one endpoint in $V_{1}$ and the other in $V_{2}$. Then coloring every vertex of $V_{1}$ red and every vertex of $V_{2}$ blue yields a valid coloring, so $G_{2}$ is 2-colorable.

By the definition of isomorphism, we know that $f(V_{1}) = V_{1}$ and $f(V_{2}) = V_{2}$. This means that the vertices of $G_{1}$ colored red are the same vertices of $G_{2}$ colored red, and the vertices of $G_{1}$ colored blue are the same vertices of $G_{2}$ colored blue. Since the vertices of $V_{1}$ and $V_{2}$ have no edges among themselves, and all edges have one endpoint in $V_{1}$ and the other in $V_{2}$, it follows that $G_{1}$ is bipartite.

QED
-/
theorem isomorphic_graph_bipartite_iff {G1 G2 : Type*} [graph G1] [graph G2] (f : G1 ≃ G2) : bipartite G1 ↔ bipartite G2 := sorry

/--`theorem`
If two graphs are isomorphic, then they are bipartite if and only if they are two colorable
Let $G_{1}$ and $G_{2}$ be two isomorphic graphs. Then $G_{1}$ is 2-colorable if and only if $G_{2}$ is 2
end --Needs more than 2000 tokens!

--OUTPUT 7
theorem bipartite_graph_two_colorable {α : Type*} (G : graph α) : two_colorable G ↔ bipartite G :=
begin
  have h1 : two_colorable G → bipartite G, from sorry,
  have h2 : bipartite G → two_colorable G, from sorry,
  show two_colorable G ↔ bipartite G, from sorry
end

--OUTPUT 8
theorem graph_is_two_colorable : ∀ G : Type*,
  graph G → (∃ (A B : set G), A ⊆ vertices G ∧ B ⊆ vertices G ∧ ∀ x ∈ A, ∀ y ∈ B, (x, y) ∈ edges G) ↔
  (∃ f : G → Prop, ∀ v : G, (f v ∨ ¬ f v) ∧ ∀ v₁ v₂ ∈ vertices G, (v₁, v₂) ∉ edges G → ¬ f v₁ ∧ ¬ f v₂) :=
begin
  assume G : Type*,
  assume hG : graph G,
  show (∃ (A B : set G), A ⊆ vertices G ∧ B ⊆ vertices G ∧ ∀ x ∈ A, ∀ y ∈ B, (x, y) ∈ edges G) ↔
    (∃ f : G → Prop, ∀ v : G, (f v ∨ ¬ f v) ∧ ∀ v₁ v₂ ∈ vertices G, (v₁, v₂) ∉ edges G → ¬ f v₁ ∧ ¬ f v₂), from sorry,
end

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from sorry,
  have h2 : (A ∩ B) ⊆ A, from sorry,
  have h3 : (A ∩ B) ⊆ S, from sorry,
  show (A ∩ B) ∈  𝒫 S, from sorry,
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by sorry
  ... = x*(x+y) + y*(x+y) : by sorry
  ... = x*x + x*y + y*x + y*y : by sorry
  ... = x^2 + 2*x*y + y^2 : by sorry,
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from sorry,
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from sorry,

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from sorry,
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from sorry,

  have h5 : ∀ a : G, classical.some (h3 a) = (1 : G), from sorry,
  have h6 : ∀ a : G, classical.some (h4 a) = (1 : G), from sorry,

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by {
    use (1 : G),
    have h7 : ∀ e : G, (∀ a : G, e * a = a ∧ a * e = a) → e = 1, from by {
      assume (e : G) (h7 : ∀ a : G, e * a = a ∧ a * e = a),
      have h8 : ∀ a : G, e = classical.some (h3 a), from sorry,
      have h9 : ∀ a : G, e = classical.some (h4 a), from sorry,
      show e = (1 : G), from sorry,
    },
    sorry,
  }
end

/--`theorem`
Bipartite Graph is two colorable
Let $G$ be a graph. Then $G$ is 2-colorable if and only if $G$ is bipartite.
`proof`
Let $G$ be a 2-colorable graph, which means we can color every vertex either red or blue, and no edge will have both endpoints colored the same color. Let $A$ denote the subset of vertices colored red, and let $B$ denote the subset of vertices colored blue. Since all vertices of $A$ are red, there are no edges within $A$, and similarly for $B$. This implies that every edge has one endpoint in $A$ and the other in $B$, which means $G$ is bipartite.

Conversely, suppose $G$ is bipartite, that is, we can partition the vertices into two subsets $V_{1}, V_{2}$ every edge has one endpoint in $V_{1}$ and the other in $V_{2}$. Then coloring every vertex of $V_{1}$ red and every vertex of $V_{2}$ blue yields a valid coloring, so $G$ is 2-colorable.

QED

-/
theorem
FEW SHOT PROMPTS TO CODEX(END)-/


/-
Author: <Redacted for anonymized submission>
-/
import data.mv_polynomial.comm_ring
import data.polynomial.div
import data.polynomial.field_division
import algebra.polynomial.big_operators
import .vars
import ...attributes
import ...integral_domain_tactic

/-!
# Knowledge Soundness

This file proves the knowledge-soundness property of the second, refined protocol described in the Pinocchio paper.

-/

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 -/

run_cmd mk_simp_attr `poly
run_cmd tactic.add_doc_string `simp_attr.poly "Attribute for defintions of poly elements"

-- Helpers for representing vars as polynomials

@[poly] def r_v_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.r_v
@[poly] def r_w_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.r_w
@[poly] def s_poly : mv_polynomial vars (polynomial F) := mv_polynomial.C (polynomial.X)
@[poly] def α_v_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.α_v
@[poly] def α_w_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.α_w
@[poly] def α_y_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.α_y
@[poly] def β_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.β
@[poly] def γ_poly : mv_polynomial vars (polynomial F) := mv_polynomial.X vars.γ

/-- Additional variable defined in terms of others -/
@[poly] def r_y_poly : mv_polynomial vars (polynomial F) := r_v_poly * r_w_poly

-- The crs elements as multivariate polynomials of the toxic waste samples: Evaluation key elements
@[poly] def crs_v_wit_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  r_v_poly * mv_polynomial.C (v_wit k)
@[poly] def crs_w_wit_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  r_w_poly * mv_polynomial.C (w_wit k)
@[poly] def crs_y_wit_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  r_y_poly * mv_polynomial.C (y_wit k)

@[poly] def crs_α_v_wit_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  α_v_poly * r_v_poly * mv_polynomial.C (v_wit k)
@[poly] def crs_α_w_wit_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  α_w_poly * r_w_poly * mv_polynomial.C (w_wit k)
@[poly] def crs_α_y_wit_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  α_y_poly * r_y_poly * mv_polynomial.C (y_wit k)

@[poly] def crs_powers (i : fin d) : (mv_polynomial vars (polynomial F)) := mv_polynomial.C (polynomial.X ^ (i : ℕ))

@[poly] def crs_β_v_w_y_k (k : fin n_wit) : mv_polynomial vars (polynomial F) :=
  β_poly * r_v_poly * mv_polynomial.C (v_wit k)
  + β_poly * r_w_poly * mv_polynomial.C (w_wit k)
  + β_poly * r_y_poly * mv_polynomial.C (y_wit k)

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

@[poly] def crs_α_v : mv_polynomial vars (polynomial F) := α_v_poly
@[poly] def crs_α_w : mv_polynomial vars (polynomial F) := α_w_poly
@[poly] def crs_α_y : mv_polynomial vars (polynomial F) := α_y_poly
@[poly] def crs_γ : mv_polynomial vars (polynomial F) := γ_poly
@[poly] def crs_βγ : mv_polynomial vars (polynomial F) := β_poly * γ_poly
@[poly] def crs_t : mv_polynomial vars (polynomial F) :=
  r_y_poly * mv_polynomial.C (t)
@[poly] def crs_v_0 : mv_polynomial vars (polynomial F) :=
  r_v_poly * mv_polynomial.C (v_0)
@[poly] def crs_w_0 : mv_polynomial vars (polynomial F) :=
  r_w_poly * mv_polynomial.C (w_0)
@[poly] def crs_y_0 : mv_polynomial vars (polynomial F) :=
  r_y_poly * mv_polynomial.C (y_0)
@[poly] def crs_v_stmt (i : fin n_stmt) : mv_polynomial vars (polynomial F) :=
  r_v_poly * mv_polynomial.C (v_stmt i)
@[poly] def crs_w_stmt (i : fin n_stmt) : mv_polynomial vars (polynomial F) :=
  r_w_poly * mv_polynomial.C (w_stmt i)
@[poly] def crs_y_stmt (i : fin n_stmt) : mv_polynomial vars (polynomial F) :=
  r_y_poly * mv_polynomial.C (y_stmt i)

-- Coefficients of the CRS elements in the representation of the v_wit proof element in the AGM
parameters {v_wit_comp_crs_v_wit_k : fin n_wit →  F}
parameters {v_wit_comp_crs_w_wit_k : fin n_wit →  F}
parameters {v_wit_comp_crs_y_wit_k : fin n_wit →  F}

parameters {v_wit_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {v_wit_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {v_wit_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {v_wit_comp_crs_powers : fin d →  F}

parameters {v_wit_comp_crs_β_v_w_y_k : fin n_wit →  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_γ : F}
parameters {v_wit_comp_crs_βγ : 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}
parameters {v_wit_comp_crs_v_stmt : fin n_stmt → F}
parameters {v_wit_comp_crs_w_stmt : fin n_stmt → F}
parameters {v_wit_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the v_wit proof element in the AGM -/
def proof_v_wit : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (v_wit_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (v_wit_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (v_wit_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (v_wit_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (v_wit_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (v_wit_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (v_wit_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (v_wit_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (v_wit_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (v_wit_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the w_wit proof element in the AGM
parameters {w_wit_comp_crs_v_wit_k : fin n_wit →  F}
parameters {w_wit_comp_crs_w_wit_k : fin n_wit →  F}
parameters {w_wit_comp_crs_y_wit_k : fin n_wit →  F}

parameters {w_wit_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {w_wit_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {w_wit_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {w_wit_comp_crs_powers : fin d →  F}

parameters {w_wit_comp_crs_β_v_w_y_k : fin n_wit →  F}

parameters {w_wit_comp_crs_α_v : F}
parameters {w_wit_comp_crs_α_w : F}
parameters {w_wit_comp_crs_α_y : F}
parameters {w_wit_comp_crs_γ : F}
parameters {w_wit_comp_crs_βγ : F}
parameters {w_wit_comp_crs_t : F}
parameters {w_wit_comp_crs_v_0 : F}
parameters {w_wit_comp_crs_w_0 : F}
parameters {w_wit_comp_crs_y_0 : F}
parameters {w_wit_comp_crs_v_stmt : fin n_stmt → F}
parameters {w_wit_comp_crs_w_stmt : fin n_stmt → F}
parameters {w_wit_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the w_wit proof element in the AGM -/
def proof_w_wit : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (w_wit_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (w_wit_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (w_wit_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (w_wit_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (w_wit_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (w_wit_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (w_wit_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (w_wit_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (w_wit_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (w_wit_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the y_wit proof element in the AGM
parameters {y_wit_comp_crs_v_wit_k : fin n_wit →  F}
parameters {y_wit_comp_crs_w_wit_k : fin n_wit →  F}
parameters {y_wit_comp_crs_y_wit_k : fin n_wit →  F}

parameters {y_wit_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {y_wit_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {y_wit_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {y_wit_comp_crs_powers : fin d →  F}

parameters {y_wit_comp_crs_β_v_w_y_k : fin n_wit →  F}

parameters {y_wit_comp_crs_α_v : F}
parameters {y_wit_comp_crs_α_w : F}
parameters {y_wit_comp_crs_α_y : F}
parameters {y_wit_comp_crs_γ : F}
parameters {y_wit_comp_crs_βγ : F}
parameters {y_wit_comp_crs_t : F}
parameters {y_wit_comp_crs_v_0 : F}
parameters {y_wit_comp_crs_w_0 : F}
parameters {y_wit_comp_crs_y_0 : F}
parameters {y_wit_comp_crs_v_stmt : fin n_stmt → F}
parameters {y_wit_comp_crs_w_stmt : fin n_stmt → F}
parameters {y_wit_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the w_wit proof element in the AGM -/
def proof_y_wit : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (y_wit_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (y_wit_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (y_wit_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (y_wit_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (y_wit_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (y_wit_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (y_wit_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (y_wit_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (y_wit_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (y_wit_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the h proof element in the AGM
parameters {h_comp_crs_v_wit_k : fin n_wit →  F}
parameters {h_comp_crs_w_wit_k : fin n_wit →  F}
parameters {h_comp_crs_y_wit_k : fin n_wit →  F}

parameters {h_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {h_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {h_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {h_comp_crs_powers : fin d →  F}

parameters {h_comp_crs_β_v_w_y_k : fin n_wit →  F}

parameters {h_comp_crs_α_v : F}
parameters {h_comp_crs_α_w : F}
parameters {h_comp_crs_α_y : F}
parameters {h_comp_crs_γ : F}
parameters {h_comp_crs_βγ : 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}
parameters {h_comp_crs_v_stmt : fin n_stmt → F}
parameters {h_comp_crs_w_stmt : fin n_stmt → F}
parameters {h_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the h proof element in the AGM -/
def proof_h : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (h_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (h_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (h_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (h_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (h_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (h_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (h_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (h_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (h_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (h_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (h_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (h_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (h_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (h_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the α_v_wit proof element in the AGM
parameters {v_wit'_comp_crs_v_wit_k : fin n_wit →  F}
parameters {v_wit'_comp_crs_w_wit_k : fin n_wit →  F}
parameters {v_wit'_comp_crs_y_wit_k : fin n_wit →  F}

parameters {v_wit'_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {v_wit'_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {v_wit'_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {v_wit'_comp_crs_powers : fin d →  F}

parameters {v_wit'_comp_crs_β_v_w_y_k : fin n_wit →  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_γ : F}
parameters {v_wit'_comp_crs_βγ : 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}
parameters {v_wit'_comp_crs_v_stmt : fin n_stmt → F}
parameters {v_wit'_comp_crs_w_stmt : fin n_stmt → F}
parameters {v_wit'_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the α_v_wit proof element in the AGM -/
def proof_v_wit' : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (v_wit'_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the α_w_wit proof element in the AGM
parameters {w_wit'_comp_crs_v_wit_k : fin n_wit →  F}
parameters {w_wit'_comp_crs_w_wit_k : fin n_wit →  F}
parameters {w_wit'_comp_crs_y_wit_k : fin n_wit →  F}

parameters {w_wit'_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {w_wit'_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {w_wit'_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {w_wit'_comp_crs_powers : fin d →  F}

parameters {w_wit'_comp_crs_β_v_w_y_k : fin n_wit →  F}

parameters {w_wit'_comp_crs_α_v : F}
parameters {w_wit'_comp_crs_α_w : F}
parameters {w_wit'_comp_crs_α_y : F}
parameters {w_wit'_comp_crs_γ : F}
parameters {w_wit'_comp_crs_βγ : F}
parameters {w_wit'_comp_crs_t : F}
parameters {w_wit'_comp_crs_v_0 : F}
parameters {w_wit'_comp_crs_w_0 : F}
parameters {w_wit'_comp_crs_y_0 : F}
parameters {w_wit'_comp_crs_v_stmt : fin n_stmt → F}
parameters {w_wit'_comp_crs_w_stmt : fin n_stmt → F}
parameters {w_wit'_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the α_w_wit proof element in the AGM -/
def proof_w_wit' : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (w_wit'_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the α_y_wit proof element in the AGM
parameters {y_wit'_comp_crs_v_wit_k : fin n_wit →  F}
parameters {y_wit'_comp_crs_w_wit_k : fin n_wit →  F}
parameters {y_wit'_comp_crs_y_wit_k : fin n_wit →  F}

parameters {y_wit'_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {y_wit'_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {y_wit'_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {y_wit'_comp_crs_powers : fin d →  F}

parameters {y_wit'_comp_crs_β_v_w_y_k : fin n_wit →  F}

parameters {y_wit'_comp_crs_α_v : F}
parameters {y_wit'_comp_crs_α_w : F}
parameters {y_wit'_comp_crs_α_y : F}
parameters {y_wit'_comp_crs_γ : F}
parameters {y_wit'_comp_crs_βγ : F}
parameters {y_wit'_comp_crs_t : F}
parameters {y_wit'_comp_crs_v_0 : F}
parameters {y_wit'_comp_crs_w_0 : F}
parameters {y_wit'_comp_crs_y_0 : F}
parameters {y_wit'_comp_crs_v_stmt : fin n_stmt → F}
parameters {y_wit'_comp_crs_w_stmt : fin n_stmt → F}
parameters {y_wit'_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the α_y_wit proof element in the AGM -/
def proof_y_wit' : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (y_wit'_comp_crs_w_stmt i))

-- Coefficients of the CRS elements in the representation of the Z proof element in the AGM
parameters {Z_comp_crs_v_wit_k : fin n_wit →  F}
parameters {Z_comp_crs_w_wit_k : fin n_wit →  F}
parameters {Z_comp_crs_y_wit_k : fin n_wit →  F}

parameters {Z_comp_crs_α_v_wit_k : fin n_wit →  F}
parameters {Z_comp_crs_α_w_wit_k : fin n_wit →  F}
parameters {Z_comp_crs_α_y_wit_k : fin n_wit →  F}

parameters {Z_comp_crs_powers : fin d →  F}

parameters {Z_comp_crs_β_v_w_y_k : fin n_wit →  F}

parameters {Z_comp_crs_α_v : F}
parameters {Z_comp_crs_α_w : F}
parameters {Z_comp_crs_α_y : F}
parameters {Z_comp_crs_γ : F}
parameters {Z_comp_crs_βγ : F}
parameters {Z_comp_crs_t : F}
parameters {Z_comp_crs_v_0 : F}
parameters {Z_comp_crs_w_0 : F}
parameters {Z_comp_crs_y_0 : F}
parameters {Z_comp_crs_v_stmt : fin n_stmt → F}
parameters {Z_comp_crs_w_stmt : fin n_stmt → F}
parameters {Z_comp_crs_y_stmt : fin n_stmt → F}

/-- Polynomial form of the representation of the Z proof element in the AGM -/
def proof_Z : mv_polynomial vars (polynomial F) :=
  ∑ i in (finset.fin_range n_wit), (crs_v_wit_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_y_wit_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_w_wit_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_w_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_v_wit_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_α_v_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_y_wit_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_α_y_wit_k i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_α_w_wit_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_α_w_wit_k i))
  +
  ∑ i in (finset.fin_range d), (crs_powers i) * mv_polynomial.C (polynomial.C (Z_comp_crs_powers i))
  +
  ∑ i in (finset.fin_range n_wit), (crs_β_v_w_y_k i) * mv_polynomial.C (polynomial.C (Z_comp_crs_β_v_w_y_k i))
  +
  crs_α_v * mv_polynomial.C (polynomial.C (Z_comp_crs_α_v))
  +
  crs_α_w * mv_polynomial.C (polynomial.C (Z_comp_crs_α_w))
  +
  crs_α_y * mv_polynomial.C (polynomial.C (Z_comp_crs_α_y))
  +
  crs_γ * mv_polynomial.C (polynomial.C (Z_comp_crs_γ))
  +
  crs_βγ * mv_polynomial.C (polynomial.C (Z_comp_crs_βγ))
  +
  crs_t * mv_polynomial.C (polynomial.C (Z_comp_crs_t))
  +
  crs_v_0 * mv_polynomial.C (polynomial.C (Z_comp_crs_v_0))
  +
  crs_w_0 * mv_polynomial.C (polynomial.C (Z_comp_crs_w_0))
  +
  crs_y_0 * mv_polynomial.C (polynomial.C (Z_comp_crs_y_0))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_v_stmt i) * mv_polynomial.C (polynomial.C (Z_comp_crs_v_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt), (crs_y_stmt i) * mv_polynomial.C (polynomial.C (Z_comp_crs_y_stmt i))
  +
  ∑ i in (finset.fin_range n_stmt),  (crs_w_stmt i) * mv_polynomial.C (polynomial.C (Z_comp_crs_w_stmt i))

-- 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 -/
@[poly] def v_stmt_mv (a_stmt : fin n_stmt → F) : mv_polynomial vars (polynomial F)
:= mv_polynomial.C (v_stmt_sv a_stmt)

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

/-- V_stmt as a multivariable polynomial of vars.X -/
@[poly] def y_stmt_mv (a_stmt : fin n_stmt → F) : mv_polynomial vars (polynomial F)
:= mv_polynomial.C (y_stmt_sv a_stmt)

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

/-- V_stmt as a multivariable polynomial of vars.X -/
@[poly] def w_stmt_mv (a_stmt : fin n_stmt → F) : mv_polynomial vars (polynomial F)
:= mv_polynomial.C (w_stmt_sv a_stmt)

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

/-- Multivariable version of t -/
@[poly] def t_mv : mv_polynomial vars (polynomial F) := mv_polynomial.C t

-- Equations

def eqnI (c_stmt : fin n_stmt → F ) : Prop :=
  (crs_v_0 + r_v_poly * v_stmt_mv c_stmt + proof_v_wit) * (crs_w_0 + r_w_poly * w_stmt_mv c_stmt + proof_w_wit) = ((r_y_poly * t_mv) * proof_h) + (crs_y_0 + r_y_poly * y_stmt_mv c_stmt + proof_y_wit)

def eqnII : Prop :=
  proof_v_wit' = proof_v_wit * crs_α_v

def eqnIII : Prop :=
  proof_w_wit' = proof_w_wit * crs_α_w

def eqnIV : Prop :=
  proof_y_wit' = proof_y_wit * crs_α_y

def eqnV : Prop :=
  proof_Z * crs_γ = (proof_v_wit + proof_w_wit + proof_y_wit) * crs_βγ

-- Coefficients
open mv_polynomial finsupp

-- lemma sadojfh (a b c : mv_polynomial vars (polynomial F)) : a + b - c = a -c + b :=
-- begin
--   ring,
-- end

lemma eqnIIresults (eqnII_verified : eqnII) :
  proof_v_wit
  = r_v_poly
    * mv_polynomial.C (
        ∑ i in (finset.fin_range n_wit),  (v_wit i) *  (polynomial.C (v_wit_comp_crs_v_wit_k i))
        + v_0 * (polynomial.C v_wit_comp_crs_v_0)
        + ∑ i in finset.fin_range n_stmt, (v_stmt i) * (polynomial.C (v_wit_comp_crs_v_stmt i))
     )
    + ∑ x in finset.fin_range d, mv_polynomial.C (polynomial.X ^ (x : ℕ)) * mv_polynomial.C ( polynomial.C (v_wit_comp_crs_powers x)) :=
begin

  rw eqnII at eqnII_verified,

  simp_rw [proof_v_wit, proof_v_wit'] at eqnII_verified ⊢,
  -- simp_rw [proof_v_wit, proof_w_wit, proof_y_wit, proof_h,
  --       proof_v_wit', proof_w_wit', proof_y_wit', proof_Z] at eqnII_verified,
  simp only [] with poly at eqnII_verified ⊢,
  -- simp only [] with polynomial_nf_3 at eqn',
  simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnII_verified ⊢,

  have h11eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.β 1 + single vars.γ 1)) eqnII_verified,
  have h19eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.γ 1)) eqnII_verified,
  have h21eqnII := congr_arg (coeff (single vars.r_w 1 + single vars.α_v 1 + single vars.β 1)) eqnII_verified,
  have h22eqnII := congr_arg (coeff (single vars.α_v 2)) eqnII_verified,
  have h32eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.α_w 1)) eqnII_verified,

  have h38eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_v 1 + single vars.β 1)) eqnII_verified,

  have h52eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 2)) eqnII_verified,

  have h54eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.α_y 1)) eqnII_verified,

  have h71eqnII := congr_arg (coeff (single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,

  have h74eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 1 + single vars.β 1)) eqnII_verified,

  have h93eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,

  have h94eqnII := congr_arg (coeff (single vars.α_w 1 + single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,
  have h95eqnII := congr_arg (coeff (single vars.α_v 1)) eqnII_verified,
  have h96eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.r_v 1)) eqnII_verified,

  have h101eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_v 1 + single vars.α_y 1)) eqnII_verified,

  clear eqnII_verified,

  simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
  simp only [] with finsupp_simp at *,

  simp only [h11eqnII, h19eqnII, h21eqnII, h22eqnII, h32eqnII, h38eqnII, h52eqnII, h54eqnII, h71eqnII, h74eqnII, h93eqnII, h94eqnII, h95eqnII, h96eqnII, h101eqnII],
  simp only [monomial_zero, add_zero, zero_add, <-monomial_add],

  rw <-sub_eq_zero,

  have h71eqnII' := congr_arg (monomial (single vars.r_w 1)) h71eqnII,
  have h93eqnII' := congr_arg (monomial (single vars.r_v 1 + single vars.r_w 1)) h93eqnII,
  simp only [monomial_zero, add_zero, zero_add, <-monomial_add] at h71eqnII' h93eqnII',

  rw <-h71eqnII',
  rw <-sub_eq_zero,

  rw <-h93eqnII',
  rw <-sub_eq_zero,

  abel,
  abel, -- Why two needed here?

end

-- lemma eqnIIresultsBetter (eqnII_verified : eqnII) (eqnV_verified : eqnV) :
--   proof_v_wit
--   = r_v_poly
--     * (
--         ∑ i in (finset.fin_range n_wit), mv_polynomial.C (v_wit i) * mv_polynomial.C (polynomial.C (Z_comp_crs_β_v_w_y_k i))
--      )
--     + ∑ x in finset.fin_range d, mv_polynomial.C (polynomial.X ^ (x : ℕ)) * mv_polynomial.C ( polynomial.C (v_wit_comp_crs_powers x)) :=
-- begin

--   rw eqnII at eqnII_verified,
--   rw eqnV at eqnV_verified,

--   simp_rw [proof_v_wit, proof_v_wit'] at eqnII_verified ⊢,
--   simp_rw [proof_v_wit, proof_w_wit, proof_y_wit, proof_Z] at eqnV_verified,

--   simp only [] with poly at eqnII_verified eqnV_verified ⊢,
--   -- simp only [] with polynomial_nf_3 at eqn',
--   simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnII_verified eqnV_verified ⊢,

--   have h11eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.β 1 + single vars.γ 1)) eqnII_verified,
--   have h19eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.γ 1)) eqnII_verified,
--   have h21eqnII := congr_arg (coeff (single vars.r_w 1 + single vars.α_v 1 + single vars.β 1)) eqnII_verified,
--   have h22eqnII := congr_arg (coeff (single vars.α_v 2)) eqnII_verified,
--   have h32eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.α_w 1)) eqnII_verified,

--   have h38eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_v 1 + single vars.β 1)) eqnII_verified,

--   have h52eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 2)) eqnII_verified,

--   have h54eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.α_y 1)) eqnII_verified,

--   have h71eqnII := congr_arg (coeff (single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,

--   have h74eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 1 + single vars.β 1)) eqnII_verified,

--   have h93eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,

--   have h94eqnII := congr_arg (coeff (single vars.α_w 1 + single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,
--   -- have h95eqnII := congr_arg (coeff (single vars.α_v 1)) eqnII_verified,
--   -- have h96eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.r_v 1)) eqnII_verified,

--   have h101eqnII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_v 1 + single vars.α_y 1)) eqnII_verified,

--   have h2eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,

--   clear eqnII_verified,
--   clear eqnV_verified,

--   simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
--   simp only [] with finsupp_simp at *,

--   rw [h11eqnII, h19eqnII, h21eqnII, h22eqnII, h32eqnII, h38eqnII, h52eqnII, h54eqnII, h74eqnII, h94eqnII, h101eqnII, h2eqnV], -- h95eqnII, h96eqnII,
--   simp only [monomial_zero, add_zero, zero_add, <-monomial_add],

--   rw <-sub_eq_zero,

--   have h71eqnII' := congr_arg (monomial (single vars.r_w 1)) h71eqnII,
--   have h93eqnII' := congr_arg (monomial (single vars.r_v 1 + single vars.r_w 1)) h93eqnII,
--   simp only [monomial_zero, add_zero, zero_add, <-monomial_add] at h71eqnII' h93eqnII',

--   rw <-h71eqnII',
--   rw <-sub_eq_zero,

--   rw <-h93eqnII',
--   rw <-sub_eq_zero,

--   abel,

-- end

lemma eqnIIIresults (eqnIII_verified : eqnIII) :
  proof_w_wit
  = r_w_poly
    * mv_polynomial.C (
        ∑ i in (finset.fin_range n_wit),  (w_wit i) * (polynomial.C (w_wit_comp_crs_w_wit_k i))
        + w_0 * (polynomial.C w_wit_comp_crs_w_0)
        + ∑ i in finset.fin_range n_stmt, (w_stmt i) * (polynomial.C (w_wit_comp_crs_w_stmt i))
     )
    + ∑ x in finset.fin_range d, mv_polynomial.C (polynomial.X ^ (x : ℕ)) * mv_polynomial.C ( polynomial.C (w_wit_comp_crs_powers x)) :=
begin

  rw eqnIII at eqnIII_verified,

  simp_rw [proof_w_wit, proof_w_wit'] at eqnIII_verified ⊢,
  -- simp_rw [proof_v_wit, proof_w_wit, proof_y_wit, proof_h,
  --       proof_v_wit', proof_w_wit', proof_y_wit', proof_Z] at eqnIII_verified,
  simp only [] with poly at eqnIII_verified ⊢,
  -- simp only [] with polynomial_nf_3 at eqn',
  simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnIII_verified ⊢,

  have h27eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_w 1)) eqnIII_verified,

  have h32eqnIII := congr_arg (coeff (single vars.α_v 1 + single vars.α_w 1)) eqnIII_verified,

  have h33eqnIII := congr_arg (coeff (single vars.r_w 1 + single vars.α_w 1)) eqnIII_verified,

  have h34eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.α_w 1 + single vars.β 1)) eqnIII_verified,

  have h35eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_w 1 + single vars.α_y 1)) eqnIII_verified,

  have h53eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.β 1 + single vars.γ 1)) eqnIII_verified,

  have h61eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.γ 1)) eqnIII_verified,

  have h75eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_w 1 + single vars.β 1)) eqnIII_verified,

  have h81eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.α_w 1)) eqnIII_verified,

  have h88eqnIII := congr_arg (coeff (single vars.r_w 1 + single vars.α_w 1 + single vars.β 1)) eqnIII_verified,

  have h89eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.α_y 1)) eqnIII_verified,

  have h96eqnIII := congr_arg (coeff (single vars.α_w 2)) eqnIII_verified,
  have h97eqnIII := congr_arg (coeff (single vars.α_w 2 + single vars.r_w 1)) eqnIII_verified,
  have h98eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.α_v 1 + single vars.r_v 1)) eqnIII_verified,

  clear eqnIII_verified,

  simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
  simp only [] with finsupp_simp at *,

  simp only [h27eqnIII, h32eqnIII, h33eqnIII, h34eqnIII, h35eqnIII, h53eqnIII, h61eqnIII, h75eqnIII, h81eqnIII, h88eqnIII, h89eqnIII, h96eqnIII, h97eqnIII, h98eqnIII],
  simp only [monomial_zero, add_zero, zero_add, <-monomial_add],

  rw <-sub_eq_zero,

  have h81eqnIII' := congr_arg (monomial (single vars.r_v 1)) h81eqnIII,
  have h27eqnIII' := congr_arg (monomial (single vars.r_v 1 + single vars.r_w 1)) h27eqnIII,
  simp only [monomial_zero, add_zero, zero_add, <-monomial_add] at h81eqnIII' h27eqnIII',

  rw <-h81eqnIII',
  rw <-sub_eq_zero,

  rw <-h27eqnIII',
  rw <-sub_eq_zero,

  abel,
  abel,
end

lemma eqnIVresults (eqnIV_verified : eqnIV) :
  proof_y_wit
  = r_y_poly
    * mv_polynomial.C (
        ∑ i in (finset.fin_range n_wit), (y_wit i) * (polynomial.C (y_wit_comp_crs_y_wit_k i))
        + y_0 * (polynomial.C y_wit_comp_crs_y_0)
        + ∑ i in finset.fin_range n_stmt, (y_stmt i) * (polynomial.C (y_wit_comp_crs_y_stmt i))
        + t * (polynomial.C y_wit_comp_crs_t)
     )
    + ∑ x in finset.fin_range d, mv_polynomial.C (polynomial.X ^ (x : ℕ)) * mv_polynomial.C ( polynomial.C (y_wit_comp_crs_powers x))  :=
begin
  rw eqnIV at eqnIV_verified,

  simp_rw [proof_y_wit, proof_y_wit'] at eqnIV_verified ⊢,
  -- simp_rw [proof_v_wit, proof_w_wit, proof_y_wit, proof_h,
  --       proof_v_wit', proof_w_wit', proof_y_wit', proof_Z] at eqnIV_verified,
  simp only [] with poly at eqnIV_verified ⊢,
  -- simp only [] with polynomial_nf_3 at eqn',
  simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnIV_verified ⊢,

  have h2eqnIV := congr_arg (coeff (single vars.r_w 1 + single vars.α_y 1 + single vars.β 1)) eqnIV_verified,
  have h4eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 1 + single vars.α_y 1)) eqnIV_verified,
  have h23eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 2)) eqnIV_verified,

  have h25eqnIV := congr_arg (coeff (single vars.r_w 1 + single vars.α_w 1 + single vars.α_y 1)) eqnIV_verified,

  have h30eqnIV := congr_arg (coeff (single vars.α_y 1 + single vars.β 1 + single vars.γ 1)) eqnIV_verified,

  have h37eqnIV := congr_arg (coeff (single vars.α_y 1 + single vars.γ 1)) eqnIV_verified,

  have h54eqnIV := congr_arg (coeff (single vars.α_v 1 + single vars.α_y 1)) eqnIV_verified,

  have h55eqnIV := congr_arg (coeff (single vars.r_w 1 + single vars.α_y 1)) eqnIV_verified,

  have h56eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 1 + single vars.β 1)) eqnIV_verified,

  have h57eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.α_y 1 + single vars.β 1)) eqnIV_verified,

  have h59eqnIV := congr_arg (coeff (single vars.α_y 2)) eqnIV_verified,

  have h89eqnIV := congr_arg (coeff (single vars.α_w 1 + single vars.α_y 1)) eqnIV_verified,

  have h102eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.α_y 1)) eqnIV_verified,

  clear eqnIV_verified,

  simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
  simp only [] with finsupp_simp at *,

  simp only [h2eqnIV, h4eqnIV, h23eqnIV, h25eqnIV, h30eqnIV, h37eqnIV, h54eqnIV, h55eqnIV, h56eqnIV, h57eqnIV, h59eqnIV, h89eqnIV, h102eqnIV],
  simp only [monomial_zero, add_zero, zero_add, <-monomial_add],

  rw <-sub_eq_zero,

  have h55eqnIV' := congr_arg (monomial (single vars.r_w 1)) h55eqnIV,
  have h102eqnIV' := congr_arg (monomial (single vars.r_v 1)) h102eqnIV,
  simp only [monomial_zero, add_zero, zero_add, <-monomial_add] at h55eqnIV' h102eqnIV',

  rw <-h55eqnIV',
  rw <-sub_eq_zero,

  rw <-h102eqnIV',
  rw <-sub_eq_zero,

  abel,
  abel,
end

lemma polynomial.mul_mod_by_monic (t p : polynomial F) (mt : t.monic) : (t * p) %ₘ t = 0 :=
begin
  rw polynomial.dvd_iff_mod_by_monic_eq_zero,
  apply dvd_mul_right,
  exact mt,
end

lemma move_C_left (p : polynomial F) (f : F) :
  p * polynomial.C f = polynomial.C f * p :=
begin
  ring,
end

lemma soundness (c_stmt : fin n_stmt → F ) (eqnI_verified : eqnI c_stmt)
  (eqnII_verified : eqnII) (eqnIII_verified : eqnIII) (eqnIV_verified : eqnIV)
  (eqnV_verified : eqnV) : (satisfying c_stmt Z_comp_crs_β_v_w_y_k) :=
begin
  rw eqnV at eqnV_verified,

  have eqnII' := eqnIIresults eqnII_verified,
  have eqnIII' := eqnIIIresults eqnIII_verified,
  have eqnIV' := eqnIVresults eqnIV_verified,

  simp_rw [eqnII', eqnIII', eqnIV', proof_Z] at eqnV_verified,
  -- simp_rw [proof_v_wit, proof_w_wit, proof_y_wit, proof_h,
  --       proof_v_wit', proof_w_wit', proof_y_wit', proof_Z] at eqnV_verified,
  simp only [] with poly at eqnV_verified,
  -- simp only [] with polynomial_nf_3 at eqn',
  simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnV_verified,

  have h2eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  have h3eqnV := congr_arg (coeff (single vars.r_w 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  have h4eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  -- have h4eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 1 + single vars.α_y 1)) eqnV_verified,
  -- have h23eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 2)) eqnV_verified,

  -- have h25eqnV := congr_arg (coeff (single vars.r_w 1 + single vars.α_w 1 + single vars.α_y 1)) eqnV_verified,

  -- have h30eqnV := congr_arg (coeff (single vars.α_y 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,

  -- have h37eqnV := congr_arg (coeff (single vars.α_y 1 + single vars.γ 1)) eqnV_verified,

  -- have h54eqnV := congr_arg (coeff (single vars.α_v 1 + single vars.α_y 1)) eqnV_verified,

  -- have h55eqnV := congr_arg (coeff (single vars.r_w 1 + single vars.α_y 1)) eqnV_verified,

  -- have h56eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 1 + single vars.β 1)) eqnV_verified,

  -- have h57eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.α_y 1 + single vars.β 1)) eqnV_verified,

  -- have h59eqnV := congr_arg (coeff (single vars.α_y 2)) eqnV_verified,

  -- have h89eqnV := congr_arg (coeff (single vars.α_w 1 + single vars.α_y 1)) eqnV_verified,

  -- have h102eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.α_y 1)) eqnV_verified,

  clear eqnV_verified,

  simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
  simp only [] with finsupp_simp at *,

  rw [<-h2eqnV] at eqnII',
  rw [<-h3eqnV] at eqnIII',
  rw [<-h4eqnV] at eqnIV',

  rw [eqnI] at eqnI_verified,
  rw satisfying,

  simp_rw [eqnII', eqnIII', eqnIV', proof_h] at eqnI_verified,

  simp only [] with poly at eqnI_verified,
  -- simp only [] with polynomial_nf_3 at eqn',
  simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnI_verified,

  have h1eqnI := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1)) eqnI_verified,

  clear eqnI_verified,

  simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at h1eqnI,
  simp only [] with finsupp_simp at h1eqnI,
  rw <-sub_eq_iff_eq_add at h1eqnI,
  -- rw <-sub_eq_zero at h1eqnI,
  -- rw polynomial.dvd_iff_mod_by_monic_eq_zero,

  have h2eqnI := congr_arg (%ₘ t) h1eqnI,
  simp only [polynomial.zero_mod_by_monic] at h2eqnI,
  rw polynomial.mul_mod_by_monic at h2eqnI,
  rw <- h2eqnI,
  congr' 1,
  simp_rw [v_stmt_sv, w_stmt_sv, y_stmt_sv, polynomial.smul_eq_C_mul],
  simp only [add_mul, mul_add],
  simp_rw [move_C_left],
  -- congr' 1,
  -- rw <-sub_eq_zero,

  abel,

  exact monic_t,

end

#exit

/-- Show that if the adversary polynomials obey the equations,
then the coefficients give a satisfying witness.
-/
theorem soundness (c_stmt : fin n_stmt → F ) :
  (0 < m)
  -> eqnI c_stmt
  -> eqnII
  -> eqnIII
  -> eqnIV
  -> eqnV
  -> (satisfying c_stmt Z_comp_crs_β_v_w_y_k) -- This shows that (a`+1, . . . , am) = (C`+1, . . . , Cm) is a witness for the statement (a1, . . . , a`)
:=
begin
  intros hm eqnI_verified eqnII_verified eqnIII_verified eqnIV_verified eqnV_verified,
  -- have foo := congr_arg (mv_polynomial.coeff (finsupp.single vars.r_v 1)) eqnII_verified,
  -- rw [proof_v_wit', proof_v_wit, crs_α_v] at foo,
  -- simp [crs_v_wit_k] at foo,
  -- simp only with coeff_simp at foo,

  rw eqnI at eqnI_verified,
  rw eqnII at eqnII_verified,
  rw eqnIII at eqnIII_verified,
  rw eqnIV at eqnIV_verified,
  rw eqnV at eqnV_verified,
  -- done,
  simp_rw [proof_v_wit, proof_w_wit, proof_y_wit, proof_h,
        proof_v_wit', proof_w_wit', proof_y_wit', proof_Z] at eqnI_verified eqnII_verified eqnIII_verified eqnIV_verified eqnV_verified,
  simp only [] with poly at eqnI_verified eqnII_verified eqnIII_verified eqnIV_verified eqnV_verified,
  -- simp only [] with polynomial_nf_3 at eqn',
  simp only [mv_polynomial.X, C_apply, mv_polynomial.monomial_mul, one_mul, mul_one, add_zero, zero_add, finset.sum_add_distrib, finset.sum_hom, mul_add, add_mul, sum_monomial_hom] at eqnI_verified eqnII_verified eqnIII_verified eqnIV_verified eqnV_verified,

  -- clear eqnI_verified,

  -- From II, II and IV, telling us that many coefficients of v_wit, w_wit, y_wit are 0.
  have h1eqnI    := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1)) eqnI_verified,
  have h11eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.β 1 + single vars.γ 1)) eqnII_verified,
  have h19eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.γ 1)) eqnII_verified,
  have h21eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.r_w 1 + single vars.β 1)) eqnII_verified,
  have h22eqnII  := congr_arg (coeff (single vars.α_v 2)) eqnII_verified,
  have h32eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.α_w 1)) eqnII_verified,
  have h38eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.r_v 1 + single vars.r_w 1 + single vars.β 1)) eqnII_verified,
  have h52eqnII  := congr_arg (coeff (single vars.α_v 2 + single vars.r_v 1)) eqnII_verified,
  have h54eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.α_y 1)) eqnII_verified,
  have h71eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.r_w 1)) eqnII_verified,
  have h74eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.r_v 1 + single vars.β 1)) eqnII_verified,
  have h93eqnII  := congr_arg (coeff (single vars.α_v 1 + single vars.r_v 1 + single vars.r_w 1)) eqnII_verified,
  have h94eqnII := congr_arg (coeff (single vars.α_w 1 + single vars.r_w 1 + single vars.α_v 1)) eqnII_verified,
  have h101eqnII := congr_arg (coeff (single vars.α_v 1 + single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 1)) eqnII_verified,
  have h27eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.r_v 1 + single vars.r_w 1)) eqnIII_verified,
  have h32eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.α_v 1)) eqnIII_verified,
  have h33eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.r_w 1)) eqnIII_verified,
  have h34eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.α_w 1 + single vars.β 1)) eqnIII_verified,
  have h35eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_w 1 + single vars.α_y 1)) eqnIII_verified,
  have h53eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.β 1 + single vars.γ 1)) eqnIII_verified,
  have h61eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.γ 1)) eqnIII_verified,
  have h75eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_w 1 + single vars.β 1)) eqnIII_verified,
  have h81eqnIII := congr_arg (coeff (single vars.r_v 1 + single vars.α_w 1)) eqnIII_verified,
  have h88eqnIII := congr_arg (coeff (single vars.r_w 1 + single vars.α_w 1 + single vars.β 1)) eqnIII_verified,
  have h89eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.α_y 1)) eqnIII_verified,
  have h96eqnIII := congr_arg (coeff (single vars.α_w 2)) eqnIII_verified,
  have h97eqnIII := congr_arg (coeff (single vars.α_w 2 + single vars.r_w 1)) eqnIII_verified,
  have h98eqnIII := congr_arg (coeff (single vars.α_w 1 + single vars.α_v 1 + single vars.r_v 1)) eqnIII_verified,
  have h2eqnIV := congr_arg (coeff (single vars.r_w 1 + single vars.α_y 1 + single vars.β 1)) eqnIV_verified,
  have h4eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.α_v 1 + single vars.α_y 1)) eqnIV_verified,
  have h23eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 2)) eqnIV_verified,
  have h25eqnIV := congr_arg (coeff (single vars.r_w 1 + single vars.α_w 1 + single vars.α_y 1)) eqnIV_verified,
  have h30eqnIV := congr_arg (coeff (single vars.α_y 1 + single vars.β 1 + single vars.γ 1)) eqnIV_verified,
  have h37eqnIV := congr_arg (coeff (single vars.α_y 1 + single vars.γ 1)) eqnIV_verified,
  have h54eqnIV := congr_arg (coeff (single vars.α_v 1 + single vars.α_y 1)) eqnIV_verified,
  have h55eqnIV := congr_arg (coeff (single vars.r_w 1 + single vars.α_y 1)) eqnIV_verified,
  have h56eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.α_y 1 + single vars.β 1)) eqnIV_verified,
  have h57eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.α_y 1 + single vars.β 1)) eqnIV_verified,
  have h59eqnIV := congr_arg (coeff (single vars.α_y 2)) eqnIV_verified,
  have h89eqnIV := congr_arg (coeff (single vars.α_w 1 + single vars.α_y 1)) eqnIV_verified,
  have h102eqnIV := congr_arg (coeff (single vars.r_v 1 + single vars.α_y 1)) eqnIV_verified,
  have h2eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  have h3eqnV := congr_arg (coeff (single vars.r_w 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  have h4eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,

  clear eqnI_verified,
  clear eqnII_verified,
  clear eqnIII_verified,
  clear eqnIV_verified,
  clear eqnV_verified,

  simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
  simp only [] with finsupp_simp at *,

  rw [h2eqnV, h3eqnV, h4eqnV] at h1eqnI,

  done,

  clear h2eqnV h3eqnV h4eqnV,

  simp only [h11eqnII, h19eqnII, h21eqnII, h22eqnII, h32eqnII, h38eqnII, h52eqnII, h54eqnII, h71eqnII, h74eqnII, h93eqnII, h94eqnII, h101eqnII, h27eqnIII, h32eqnIII, h33eqnIII, h34eqnIII, h35eqnIII, h53eqnIII, h61eqnIII, h75eqnIII, h81eqnIII, h88eqnIII, h89eqnIII, h96eqnIII, h97eqnIII, h98eqnIII, h2eqnIV, h4eqnIV, h23eqnIV, h25eqnIV, h30eqnIV, h37eqnIV, h54eqnIV, h55eqnIV, h56eqnIV, h57eqnIV, h59eqnIV, h89eqnIV, h102eqnIV] at h1eqnI, --TODO bring back eqnI

  -- have h12eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  -- have h13eqnV := congr_arg (coeff (single vars.r_w 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,
  -- have h14eqnV := congr_arg (coeff (single vars.r_v 1 + single vars.r_w 1 + single vars.β 1 + single vars.γ 1)) eqnV_verified,

  -- clear eqnV_verified,

  -- simp only [finsupp_vars_eq_ext] with coeff_simp finsupp_eq at *,
  -- simp only [] with finsupp_simp at *,

  -- simp only [*, zero_add, add_zero] at h12eqnV,

  clear h11eqnII h19eqnII h21eqnII h22eqnII h32eqnII h38eqnII h52eqnII h54eqnII h71eqnII h74eqnII h93eqnII h101eqnII h27eqnIII h32eqnIII h33eqnIII h34eqnIII h35eqnIII h53eqnIII h61eqnIII h75eqnIII h81eqnIII h88eqnIII h89eqnIII h96eqnIII h2eqnIV h4eqnIV h23eqnIV h25eqnIV h30eqnIV h37eqnIV h54eqnIV h55eqnIV h56eqnIV h57eqnIV h59eqnIV h89eqnIV h102eqnIV,

  done,

  -- Step 2: Recursively simplify and case-analyze the equations

  trace "Moving Cs right",
  simp only [simplifier1, simplifier2] at *,

  trace "Grouping distributivity",
  simp only [<-mul_add, <-add_mul, <-add_assoc, add_mul_distrib, add_mul_distrib'] at *,

  trace "Main simplification",
  simp only [*] with integral_domain_simp at *,
  tactic.integral_domain_tactic_v4,

  done,

  -- Solve remaining four cases by hand
  -- { rw [<-h1022, <-h0122, <-h0022],
  --   simp only [B_β_mul],
  --   simp only [<-mul_assoc],
  --   simp only [A_α_mul],
  --   simp only [<-mul_assoc],
  --   rw h1122,
  --   ring, },

end

end


import algebra.category.Group
import algebra.category.Module.epi_mono
import ring_theory.principal_ideal_domain
import category_theory.preadditive.injective
import enough_injectives.baer_criterion
import group_theory.subgroup.pointwise
import group_theory.quotient_group
import linear_algebra.basis
import lemmas.add_monoid_hom_mono

namespace add_comm_group

open quotient_group

open_locale pointwise

universe u

variables (A : Type u) [add_comm_group A]

class divisible : Prop :=
(div : ∀ (n : ℤ), n ≠ 0 → n • (⊤ : add_subgroup A) = ⊤)

instance : divisible ℚ :=
{ div := λ n hn, add_subgroup.ext $ λ q,
  { mp := λ _, trivial,
    mpr := λ _, ⟨q/n, trivial, by norm_num; rw [div_eq_mul_inv, mul_comm q, ←mul_assoc, mul_inv_cancel (by norm_cast; exact hn : (n : ℚ) ≠ 0), one_mul]⟩ } }

section product

variables {ι : Type*} (B : ι → Type*) [Π i, add_comm_group (B i)] [Π i, divisible (B i)]

lemma divisible_of_product_divisible : divisible (Π i, B i) :=
{ div := λ n hn, set_like.ext $ λ x,
  { mp := λ _, trivial,
    mpr := λ _, begin
      suffices : ∀ i, ∃ a, x i = n • a,
      { choose rep h_rep using this,
        refine ⟨rep, trivial, _⟩,
        ext i,
        rw h_rep,
        refl, },
      { intros i,
        have mem1 : x i ∈ n • ⊤,
        { rw divisible.div n hn,
          exact add_subgroup.mem_top _,
          apply_instance, },
        rcases mem1 with ⟨a, -, eq1⟩,
        exact ⟨a, eq1 ▸ rfl⟩, },
    end } }

end product

lemma injective_of_injective_as_module (h : category_theory.injective (⟨A⟩ : Module ℤ)) :
  category_theory.injective (AddCommGroup.of A) :=
{ factors := λ X Y g f m, begin
    have factors := h.factors,
    replace factors := @factors ⟨X⟩ ⟨Y⟩ _ _ _,
    work_on_goal 2
    { refine { to_fun := g, map_add' := _, map_smul' := _ },
      { intros x y, rw map_add },
      { intros z x,
        rw [ring_hom.id_apply],
        induction z using int.induction_on with n hn n hn,
        { rw [zero_smul, map_zero, zero_smul] },
        { rw [add_smul, one_smul, map_add, hn, add_smul, one_smul] },
        { rw [sub_smul, one_smul, map_sub, hn, sub_smul, one_smul] }, }, },
    work_on_goal 2
    { refine { to_fun := f, map_add' := _, map_smul' := _ },
      { intros x y, rw map_add },
      { intros z x,
        rw [ring_hom.id_apply],
        induction z using int.induction_on with n hn n hn,
        { rw [zero_smul, map_zero, zero_smul] },
        { rw [add_smul, one_smul, map_add, hn, add_smul, one_smul] },
        { rw [sub_smul, one_smul, map_sub, hn, sub_smul, one_smul] } } },
    work_on_goal 2
    { split,
      intros Z α β eq1,
      set α' : AddCommGroup.of Z ⟶ X := α.to_add_monoid_hom with α'_eq,
      set β' : AddCommGroup.of Z ⟶ X := β.to_add_monoid_hom with β'_eq,
      have eq2 : α' ≫ f = β' ≫ f,
      { ext,
        simp only [category_theory.comp_apply, linear_map.to_add_monoid_hom_coe, α'_eq, β'_eq],
        have := fun_like.congr_fun eq1 x,
        simpa only [Module.coe_comp, linear_map.coe_mk, function.comp_app] using this },
      resetI,
      rw category_theory.cancel_mono at eq2,
      ext,
      { have := fun_like.congr_fun eq2 x,
        simpa only using this }, },
    rcases factors with ⟨h, eq1⟩,
    refine ⟨h.to_add_monoid_hom, _⟩,
    ext,
    replace eq1 := fun_like.congr_fun eq1 x,
    simpa only using eq1,
  end }

lemma injective_as_module_of_injective_as_Ab (h : category_theory.injective (AddCommGroup.of A)) :
  category_theory.injective (⟨A⟩ : Module ℤ) :=
{ factors := λ X Y g f m, begin
    replace h := h.factors,
    replace h := @h ⟨X⟩ ⟨Y⟩ g.to_add_monoid_hom f.to_add_monoid_hom begin
      rw AddCommGroup.mono_iff_injective,
      rw Module.mono_iff_injective at m,
      intros x y eq1,
      change f x = f y at eq1,
      exact m eq1,
    end,
    rcases h with ⟨h, eq1⟩,
    refine ⟨{ to_fun := h, map_add' := λ x y, by rw [map_add], map_smul' := _ }, _⟩,
    { intros m x,
      rw [ring_hom.id_apply],
      induction m using int.induction_on with n hn n hn,
      { rw [zero_smul],
        convert map_zero _,
        convert zero_smul _ x, },
      { simp only [add_smul, map_add, hn, one_smul], },
      { simp only [sub_smul, map_sub, hn, one_smul] }, },
    { ext,
      replace eq1 := fun_like.congr_fun eq1 x,
      convert eq1, },
  end }

lemma injective_of_divisible (d : divisible A) :
  category_theory.injective (AddCommGroup.of A) :=
injective_of_injective_as_module _ $
@@module.injective_object_of_injective_module ℤ _ A _ _ $
Baer.criterion $ λ I g, begin
  rcases is_principal_ideal_ring.principal I with ⟨m, rfl⟩,
  by_cases m_eq_zero : m = 0,
  { subst m_eq_zero,
    refine ⟨{ to_fun := _, map_add' := _, map_smul' := _ }, λ n hn, _⟩,
    { intros n, exact g 0, },
    { intros n1 n2,
      simp only [map_zero, add_zero] },
    { intros n1 n2,
      simp only [map_zero, smul_zero], },
    { rw [submodule.span_singleton_eq_bot.mpr rfl, submodule.mem_bot] at hn,
      simp only [hn, map_zero],
      symmetry,
      convert map_zero _, }, },
  { have d : m • (⊤ : add_subgroup A) = _ := divisible.div m m_eq_zero,
    replace d : ∀ (a : A), ∃ (a' : A), a = m • a',
    { intros a,
      have mem1 : a ∈ m • ⊤,
      { rw d, trivial },
      { rcases mem1 with ⟨a', -, eq1⟩,
        refine ⟨a', _⟩,
        rw [←eq1],
        refl, }, },
    choose rep h_rep using d,
    set gₘ := g ⟨m, submodule.subset_span (set.mem_singleton _)⟩ with gm_eq,
    refine ⟨{ to_fun := _, map_add' := _, map_smul' := _ }, λ n hn, _⟩,
    { intros n,
      exact n • rep gₘ, },
    { intros n1 n2, simp only [add_smul], },
    { intros n1 n2,
      rw [ring_hom.id_apply, smul_eq_mul, mul_smul], },
    { rw submodule.mem_span_singleton at hn,
      rcases hn with ⟨n, rfl⟩,
      simp only [gm_eq, algebra.id.smul_eq_mul, linear_map.coe_mk],
      rw [mul_smul, ←h_rep (g ⟨m, _⟩), ←linear_map.map_smul],
      congr, }, },
end

def ℤ_as_ℚ_subgroup : add_subgroup ℚ :=
{ carrier := set.range (coe : ℤ → ℚ),
  add_mem' := begin
    rintros _ _ ⟨a, rfl⟩ ⟨b, rfl⟩,
    refine ⟨a + b, _⟩,
    norm_cast,
  end,
  zero_mem' := ⟨0, rfl⟩,
  neg_mem' := begin
    rintros _ ⟨a, rfl⟩,
    refine ⟨-a, _⟩,
    norm_cast,
  end }

instance divisible_ℚ_quotient_ℤ : divisible (ℚ ⧸ ℤ_as_ℚ_subgroup) :=
{ div := λ n hn, set_like.ext $ λ x,
  { mp := λ _, trivial,
    mpr := λ _, begin
      induction x using quotient.induction_on',
      refine ⟨quotient.mk' (x/n), trivial, _⟩,
      simp only [distrib_mul_action.to_add_monoid_End_apply, distrib_mul_action.to_add_monoid_hom_apply],
      change quotient.mk' (n • _) = _,
      congr,
      rw [div_eq_mul_inv, mul_comm],
      norm_num,
      rw [←mul_assoc, mul_inv_cancel, one_mul],
      norm_cast,
      exact hn,
    end } }

instance [divisible A] : divisible (ulift A) :=
{ div := λ n hn, set_like.ext $ λ x,
  { mp := λ _, trivial,
    mpr := λ _, begin
      rcases x with ⟨a⟩,
      have mem1 : a ∈ n • ⊤,
      { rw divisible.div n hn,
        exact add_subgroup.mem_top _,
        apply_instance },
      rcases mem1 with ⟨a, -, rfl⟩,
      refine ⟨⟨a⟩, trivial, _⟩,
      simp only [distrib_mul_action.to_add_monoid_End_apply, distrib_mul_action.to_add_monoid_hom_apply],
      refl,
    end } }

local notation `ℚ⧸ℤ` := (ulift.{u} (ℚ ⧸ ℤ_as_ℚ_subgroup))
local notation `I` := AddCommGroup.of A ⟶ AddCommGroup.of (ℚ⧸ℤ)
local notation `D` := AddCommGroup.of (Π (i : I), ℚ⧸ℤ)

instance : category_theory.injective (⟨ℚ⧸ℤ⟩ : Module ℤ) :=
injective_as_module_of_injective_as_Ab _ $ injective_of_divisible _ infer_instance

lemma ℚ_quotient_ℤ.Baer : module.Baer ℤ ℚ⧸ℤ :=
Baer.criterion' $ (module.injective_iff_injective_object _ _).mpr infer_instance

@[simps]
def embed_into_injective : AddCommGroup.of A ⟶ D :=
{ to_fun := λ a, begin
    intros i,
    exact i a,
  end,
  map_zero' := by simpa only [map_zero],
  map_add' := λ x y, by simpa only [map_add] }

section
variable {A}

noncomputable def rep {a : A} (x : (submodule.span ℤ {a} : submodule ℤ A)) : ℤ :=
(submodule.mem_span_singleton.mp x.2).some

lemma rep_eq {a : A} (x : (submodule.span ℤ {a} : submodule ℤ A)) :
  (rep x) • a = x :=
(submodule.mem_span_singleton.mp x.2).some_spec

end

namespace infinite_order

variable {A}

variables {a : A}

noncomputable def to_fun : (submodule.span ℤ {a} : submodule ℤ A) → ℚ⧸ℤ :=
λ x, ⟨quotient.mk' (rat.mk (rep x) 2)⟩

lemma to_fun_wd.aux (infinite_order : ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0)
  {m n : ℤ} (eq1 : m • a = n • a) :
  m = n :=
begin
  rw [←sub_eq_zero, ←sub_smul] at eq1,
  have eq2 : (m - n).nat_abs • a = 0,
  { suffices : ((m - n).nat_abs : ℤ) • a = 0,
    { rw ←this,
      simp only [coe_nat_zsmul], },
    rw [←int.abs_eq_nat_abs],
    refine abs_by_cases (λ (m : ℤ), m • a = 0) eq1 _,
    rw [neg_smul, eq1, neg_zero], },
  specialize infinite_order (int.nat_abs (m - n)),
  suffices : (m - n).nat_abs = 0,
  { rwa [int.nat_abs_eq_zero, sub_eq_zero] at this, },
  contrapose! infinite_order,
  refine ⟨infinite_order, eq2⟩,
end

lemma to_fun_wd
  (infinite_order : ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0)
  (x : (submodule.span ℤ {a} : submodule ℤ A))
  {m : ℤ} (hm : m • a = x) :
  to_fun x = ⟨quotient.mk' (rat.mk m 2)⟩ :=
begin
  dunfold to_fun,
  have eq1 : m = rep x,
  { have := rep_eq x,
    rw ←hm at this,
    rw to_fun_wd.aux infinite_order this, },
  subst eq1,
end

lemma map_add'
  (infinite_order : ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0)
  (x y : (submodule.span ℤ {a} : submodule ℤ A)) :
  to_fun (x + y) = to_fun x + to_fun y :=
begin
  have : (rep x + rep y) • a = rep (x + y) • a,
  { rw [add_smul, rep_eq, rep_eq, rep_eq, submodule.coe_add], },
  have := to_fun_wd.aux infinite_order this,
  dunfold to_fun,
  ext1,
  change _ = _ + _,
  dsimp only,
  change _ = quotient.mk' (_ + _),
  rw [quotient_add_group.eq', rat.neg_def, rat.add_def, rat.add_def];
  norm_num,
  simp only [add_mul, mul_assoc],
  norm_num,
  rw [←add_mul, ←neg_mul, ←add_mul, this, ←sub_eq_neg_add, sub_self, zero_mul],
  norm_num,
  exact add_subgroup.zero_mem _,
end

lemma map_smul'
  (infinite_order : ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0)
  (m : ℤ) (x : (submodule.span ℤ {a} : submodule ℤ A)) :
  to_fun (m • x) = m • to_fun x :=
begin
  have : (m * rep x) • a = rep (m • x) • a,
  { rw [mul_smul, rep_eq x, rep_eq (m • x), submodule.coe_smul], },
  have := to_fun_wd.aux infinite_order this,
  dunfold to_fun,
  ext1,
  change _ = _ • _,
  change _ = quotient.mk' _,
  dsimp only,
  rw [quotient_add_group.eq', ←this, rat.neg_def],
  norm_num,
  rw [show (m : ℚ) = rat.mk m 1, from rat.coe_int_eq_mk _, rat.mul_def, rat.add_def];
  norm_num,
  refine ⟨0, by norm_num⟩,
end

noncomputable def to_quotient
  (infinite_order : ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0) : (⟨(submodule.span ℤ {a} : submodule ℤ A)⟩ : Module ℤ) ⟶ ⟨ℚ⧸ℤ⟩ :=
{ to_fun := to_fun,
  map_add' := map_add' infinite_order,
  map_smul' := map_smul' infinite_order }

lemma to_quotient.apply_ne_zero
  (infinite_order : ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0) :
  to_quotient infinite_order ⟨a, submodule.mem_span_singleton_self _⟩ ≠ 0 :=
have H : ∀ (m : ℤ), - m * 2 ≠ 1,
begin
  intros m r,
  have : (2 : ℤ) ∣ -m * 2,
  { exact dvd_mul_left 2 (-m), },
  rw r at this,
  have not_dvd : ¬ (2 : ℤ) ∣ 1,
  { rw ←int.exists_lt_and_lt_iff_not_dvd,
    refine ⟨0, by linarith, by linarith⟩,
    linarith, },
  exact not_dvd this,
end,
begin
  dunfold to_quotient,
  change to_fun _ ≠ 0,
  dunfold to_fun,
  suffices : (quotient.mk' (rat.mk (rep ⟨a, _⟩) 2) : ℚ ⧸ ℤ_as_ℚ_subgroup) ≠ 0,
  { contrapose! this,
    rw ulift.ext_iff at this,
    exact this, },
  intro r,
  rw show (0 : ℚ ⧸ ℤ_as_ℚ_subgroup) = quotient.mk' 0, from rfl at r,
  rw [quotient_add_group.eq', add_zero, rat.neg_def] at r,
  rcases r with ⟨m, eq1⟩,
  rw show (m : ℚ) = rat.mk m 1, from rat.coe_int_eq_mk _ at eq1,
  rw [rat.mk_eq, mul_one] at eq1,
  replace eq1 : - m * 2 = rep ⟨a, _⟩,
  { rw [neg_mul, eq1, neg_neg], },
  have eq2 : (- m * 2) • a = rep ⟨a, _⟩ • a,
  { rw [eq1], },
  rw rep_eq at eq2,
  rw ←subtype.val_eq_coe at eq2,
  dsimp only at eq2,
  have eq3 : (-m * 2 - 1) • a = 0,
  { rw [sub_smul, one_smul, sub_eq_zero, eq2], },
  have eq4 : (-m * 2 - 1).nat_abs • a = 0,
  { suffices : ((-m * 2 - 1).nat_abs : ℤ) • a = 0,
    { rw ←this,
      simp only [coe_nat_zsmul], },
    rw [←int.abs_eq_nat_abs],
    refine abs_by_cases (λ (m : ℤ), m • a = 0) eq3 _,
    rw [neg_smul, eq3, neg_zero], },
  have eq5 : (-m * 2 - 1).nat_abs = 0,
  { contrapose! infinite_order,
    exact ⟨_, infinite_order, eq4⟩, },
  rw int.nat_abs_eq_zero at eq5,
  rw [sub_eq_zero] at eq5,
  exact H _ eq5,
  norm_num,
  norm_num,
end

end infinite_order

namespace finite_order

variables {A} {a : A}

noncomputable def order
  (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) : ℕ :=
@nat.find _ (classical.dec_pred _) fo

lemma order_ne_zero
  (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) :
  (order fo) ≠ 0 :=
(@nat.find_spec _ (classical.dec_pred _) fo).1

lemma order_smul (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) :
  (order fo) • a = 0 :=
(@nat.find_spec _ (classical.dec_pred _) fo).2

lemma order_is_min
  (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0)
  {m : ℕ} (m_ne_zero : m ≠ 0) (hm : m • a = 0) :
  order fo ≤ m :=
@nat.find_min' _ (classical.dec_pred _) fo _ ⟨m_ne_zero, hm⟩

lemma dvd_of_smul_eq_zero
  (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0)
  {m : ℕ} (m_ne_zero : m ≠ 0) (hm : m • a = 0) :
  order fo ∣ m :=
begin
  have eq1 := order_smul fo,
  have ineq1 := order_is_min fo m_ne_zero hm,
  set r := m % order fo with r_eq,
  set m' := m / order fo with m'_eq,
  have eq2 : m = order fo * m' + r,
  { rw [r_eq, m'_eq, nat.div_add_mod m (order fo)] },
  have eq3 : r • a = 0,
  { rwa [eq2, add_smul, mul_comm, mul_smul, order_smul, smul_zero, zero_add] at hm, },
  by_cases ineq2 : r = 0,
  { rw ineq2 at eq2 eq3,
    rw [add_zero] at eq2,
    exact ⟨_, eq2⟩ },
  { have ineq3 : r < order fo,
    { rw [r_eq],
      apply nat.mod_lt,
      exact nat.pos_of_ne_zero (order_ne_zero fo), },
    have ineq4 : order fo ≤ r := order_is_min fo ineq2 eq3,
    linarith },
end

noncomputable def to_fun (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) : (submodule.span ℤ {a} : submodule ℤ A) → ℚ⧸ℤ :=
λ x, ⟨quotient.mk' $ rat.mk (rep x) (order fo)⟩

lemma to_fun_wd (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) (x : (submodule.span ℤ {a} : submodule ℤ A))
  {m : ℤ} (hm : m • a = x) :
  to_fun fo x = ⟨quotient.mk' $ rat.mk m (order fo)⟩ :=
begin
  have INEQ : ((order fo) : ℤ) ≠ 0,
  { norm_cast, apply order_ne_zero, },
  dunfold to_fun,
  ext,
  dsimp only,
  rw [quotient_add_group.eq', rat.neg_def, rat.add_def, ←add_mul, ←sub_eq_neg_add,
    show rat.mk ((m - rep x) * (order fo)) ((order fo) * (order fo)) = rat.mk (m - rep x) (order fo),
    { rw [rat.mk_eq],
      ring,
      rw mul_ne_zero_iff,
      exact ⟨INEQ, INEQ⟩,
      exact INEQ }],
  all_goals { try { exact INEQ } },
  have eq1 := congr_arg2 has_sub.sub hm (rep_eq x),
  rw [sub_self, ←sub_smul] at eq1,
  have eq2 : (m - rep x).nat_abs • a = 0,
  { suffices : ((m - rep x).nat_abs : ℤ) • a = 0,
    { rwa coe_nat_zsmul at this, },
    rw ←int.abs_eq_nat_abs,
    refine abs_by_cases (λ (m : ℤ), m • a = 0) eq1 _,
    rw [neg_smul, eq1, neg_zero], },
  by_cases ineq1 : (m - rep x).nat_abs = 0,
  { rw int.nat_abs_eq_zero at ineq1,
    rw ineq1,
    exact ⟨0, by norm_num⟩, },
  have dvd1 := dvd_of_smul_eq_zero fo ineq1 eq2,
  have dvd2 : (order fo : ℤ) ∣ (m - rep x).nat_abs,
  { rcases dvd1 with ⟨k, hk⟩,
    refine ⟨k, _⟩,
    exact_mod_cast hk, },
  rw int.dvd_nat_abs at dvd2,
  rcases dvd2 with ⟨k, hk⟩,
  refine ⟨k, _⟩,
  rw [hk, show (k : ℚ) = rat.mk k 1, from rat.coe_int_eq_mk _,
    rat.mk_eq, mul_one, mul_comm],
  linarith,
  exact INEQ,
end

lemma map_add' (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0)
  (x y : (submodule.span ℤ {a} : submodule ℤ A)) :
  to_fun fo (x + y) = to_fun fo x + to_fun fo y :=
begin
  have INEQ : ((order fo) : ℤ) ≠ 0,
  { norm_cast, apply order_ne_zero, },

  have eq1 : rep (x + y) • a = (rep x + rep y) • a,
  { rw [rep_eq, add_smul, rep_eq, rep_eq],
    refl },
  have eq2 : ↑(x + y) = (rep x + rep y) • a := by rwa [rep_eq] at eq1,
  rw to_fun_wd fo (x + y) eq2.symm,
  ext,
  dunfold to_fun,
  change quotient.mk' _ = quotient.mk' (_ + _),
  rw [quotient_add_group.eq', rat.neg_def, rat.add_def,
    rat.add_def, ←add_mul, mul_assoc, ←add_mul, ←sub_eq_neg_add,
    sub_self, zero_mul],
  suffices : (0 : ℚ) ∈ ℤ_as_ℚ_subgroup,
  { convert this,
    norm_num, },
  exact ⟨0, rfl⟩,
  exact INEQ,
  rw mul_ne_zero_iff,
  exact ⟨INEQ, INEQ⟩,
  exact INEQ,
  exact INEQ,
end

lemma map_smul' (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) (m : ℤ)
  (x : (submodule.span ℤ {a} : submodule ℤ A)) :
  to_fun fo (m • x) = m • to_fun fo x :=
begin
  have INEQ : ((order fo) : ℤ) ≠ 0,
  { norm_cast, apply order_ne_zero, },
  have eq1 : (m * rep x) • a = rep (m • x) • a,
  { rw [mul_smul, rep_eq x, rep_eq (m • x)],
    refl, },
  have eq2 : (m * rep x) • a = (m • x),
  { rw rep_eq at eq1,
    exact eq1 },
  rw to_fun_wd fo (m • x) eq2,
  dunfold to_fun,
  ext,
  change quotient.mk' _ = quotient.mk' (_ • _),
  rw [quotient_add_group.eq', rat.neg_def],
  norm_num,
  rw [show (m : ℚ) = rat.mk m 1, from rat.coe_int_eq_mk _,
    rat.mul_def, rat.add_def, one_mul, ←add_mul, ←sub_eq_neg_add,
    sub_self, zero_mul],
  suffices : (0 : ℚ) ∈ ℤ_as_ℚ_subgroup,
  { convert this,
    norm_num },
  exact ⟨0, rfl⟩,
  exact INEQ,
  rw one_mul,
  exact INEQ,
  linarith,
  exact INEQ,
end

noncomputable def to_quotient (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0) :
  (⟨(submodule.span ℤ {a} : submodule ℤ A)⟩ : Module ℤ) ⟶ ⟨ℚ⧸ℤ⟩ :=
{ to_fun := to_fun fo,
  map_add' := map_add' fo,
  map_smul' := map_smul' fo }

lemma to_quotient.apply_ne_zero (fo : ∃ (n : ℕ), n ≠ 0 ∧ n • a = 0)
  (ha : a ≠ 0) :
  to_quotient fo ⟨a, submodule.mem_span_singleton_self _⟩ ≠ 0 :=
begin
  have INEQ : ((order fo) : ℤ) ≠ 0,
  { norm_cast, apply order_ne_zero, },

  change to_fun fo _ ≠ 0,
  rw to_fun_wd fo ⟨a, _⟩ (rep_eq ⟨a, _⟩),
  intro r,
  rw ulift.ext_iff at r,
  change quotient.mk' _ = 0 at r,
  have r2 : quotient.mk' (rat.mk (rep ⟨a, _⟩) (order fo)) = quotient.mk' 0,
  { rw r, refl, },
  rw [quotient_add_group.eq', add_zero, rat.neg_def] at r2,
  rcases r2 with ⟨m, hm⟩,
  rw [show (m : ℚ) = rat.mk m 1, from rat.coe_int_eq_mk _, rat.mk_eq,
    mul_one] at hm,
  have eq1 : (m * order fo) • a =
    (- rep ⟨a, _⟩) • a,
  { rw hm, },
  rw [neg_smul, rep_eq ⟨a, submodule.mem_span_singleton_self _⟩, ←subtype.val_eq_coe] at eq1,
  dsimp only at eq1,
  have eq2 : (m * order fo + 1) • a = 0,
  { rw [add_smul, one_smul, eq1, ←sub_eq_neg_add, sub_self], },
  have eq3 : (m * order fo) • a = 0,
  { rw [mul_smul, coe_nat_zsmul, order_smul fo, smul_zero], },
  have eq4 := congr_arg2 has_sub.sub eq2 eq3,
  rw [←sub_smul, add_sub_cancel', one_smul, sub_self] at eq4,
  exact ha eq4,
  linarith,
  exact INEQ,
end

end finite_order

lemma embed_into_injective_injective :
  function.injective (embed_into_injective A) :=
suffices h : ∀ (a : A) (ha : a ≠ 0), ∃ (f : (AddCommGroup.of A) ⟶ (AddCommGroup.of ℚ⧸ℤ)), (f : A ⟶ ℚ⧸ℤ) a ≠ 0,
begin
  contrapose! h,
  simp only [function.injective, not_forall] at h,
  rcases h with ⟨a, b, h, ineq1⟩,
  refine ⟨a-b, _, _⟩,
  { intro r,
    rw sub_eq_zero at r,
    exact ineq1 r, },
  { intros f,
    have eq2 : f a = f b,
    { unfold embed_into_injective at h,
      simp only [id.def, add_monoid_hom.coe_mk] at h,
      convert congr_fun h f, },
    rwa [map_sub, sub_eq_zero], },
end,
λ a ha,
suffices ∃ (f : (⟨submodule.span ℤ {a}⟩ : Module ℤ) ⟶ ⟨ℚ⧸ℤ⟩), f ⟨a, submodule.subset_span (set.mem_singleton _)⟩ ≠ 0,
begin
  rcases this with ⟨f, eq1⟩,
  haveI injQZ : category_theory.injective (⟨ℚ⧸ℤ⟩ : Module ℤ) := infer_instance,
  replace injQZ := injQZ.factors,
  replace injQZ := @injQZ ⟨submodule.span ℤ {a}⟩ ⟨A⟩ f
  { to_fun := λ x, x.1,
    map_add' := λ x y, rfl,
    map_smul' := λ x y, rfl } begin
    rw Module.mono_iff_injective,
    intros x y eq1,
    rw subtype.ext_iff_val,
    exact eq1,
  end,
  rcases injQZ with ⟨h, eq2⟩,
  refine ⟨h.to_add_monoid_hom, _⟩,
  replace eq2 := fun_like.congr_fun eq2 ⟨a, submodule.mem_span_singleton_self _⟩,
  erw ←eq2 at eq1,
  convert eq1,
end,
begin
  by_cases finite_order_or_not :
    ∀ (n : ℕ), n ≠ 0 → n • a ≠ 0,
  { rename finite_order_or_not io,
    exact ⟨infinite_order.to_quotient io, infinite_order.to_quotient.apply_ne_zero io⟩ },
  { rename finite_order_or_not fo,
    simp only [ne.def, not_forall, not_not, exists_prop] at fo,
    exact ⟨finite_order.to_quotient fo, finite_order.to_quotient.apply_ne_zero fo ha⟩, }
end

def has_injective_presentation : category_theory.injective_presentation (AddCommGroup.of.{u} A) :=
{ J := D,
  injective := injective_of_divisible _ $ divisible_of_product_divisible _,
  f := embed_into_injective A,
  mono := (AddCommGroup.mono_iff_injective _).mpr $ embed_into_injective_injective _ }

instance : category_theory.enough_injectives (Ab.{u}) :=
{ presentation := λ A, nonempty.intro $ begin
    rcases A with ⟨A, α⟩,
    resetI,
    convert has_injective_presentation A,
  end }

end add_comm_group


import data.nat.prime
import data.rat.basic
import data.real.basic
import analysis.special_functions.pow
import data.real.cau_seq
import number_theory.padics.padic_norm

import valuations.basic

import for_mathlib.hom
import for_mathlib.rat

open is_absolute_value
noncomputable theory

def trivial_abs : ℚ → ℝ := λ a,
    if a = 0 then 0
    else 1

instance : is_absolute_value trivial_abs :=
{ abv_nonneg := λ x, by { rw [trivial_abs], dsimp, split_ifs, exact le_refl 0, exact zero_le_one, },
  abv_eq_zero := λ x, by { rw [trivial_abs], dsimp, split, contrapose!, intro, rw if_neg ᾰ, norm_num, intro, rw if_pos ᾰ, },
  abv_add := λ x y, by { rw [trivial_abs], dsimp, split_ifs, any_goals { linarith,}, exfalso, rw [h_1,h_2] at h, simpa using h, },
  abv_mul := λ x y, by { rw [trivial_abs], dsimp, split_ifs, any_goals {norm_num}, finish, finish, finish, finish, } }

def padic_norm_ℝ (p: ℕ) := λ r: ℚ, (padic_norm p r: ℝ)

instance abv_lift (abv: ℚ → ℚ) [habv: is_absolute_value abv]:
  is_absolute_value (λ r: ℚ, (abv r: ℝ)) :=
{ abv_nonneg := by { norm_cast, exact abv_nonneg abv, },
  abv_eq_zero := by { norm_cast, intro x, exact abv_eq_zero abv, },
  abv_add := by { norm_cast, exact abv_add abv, },
  abv_mul := by { norm_cast, exact abv_mul abv, } }

instance padic_is_abv {p: ℕ} [hp: fact (nat.prime p)] : is_absolute_value (padic_norm_ℝ p) :=
  @abv_lift (padic_norm p) (@padic_norm.is_absolute_value p _)

lemma trivial_abs_is_one_iff_nonzero_arg (a: ℚ) : (a ≠ 0) ↔ (trivial_abs a = 1) :=
begin
    split,
    intro h₁,
    unfold trivial_abs,
    by_cases (a = 0),
    exfalso,
    exact h₁ h,
    rw if_neg h,
    contrapose!,
    intro h,
    unfold trivial_abs,
    rw if_pos h,
    finish
end

lemma abs_val_equiv_of_equiv_on_primes (abv abv': ℚ → ℝ)
  [habv: is_absolute_value abv] [habv': is_absolute_value abv']
  (α: ℝ) (α_pos: 0 < α)
  (h: ∀ p: ℕ, prime p → (abv p) ^ α = abv' p): (λ r: ℚ, (abv r) ^ α) = abv' :=
begin
  have: ∀ n: ℕ, (abv n) ^ α = abv' n,
  {
    have inductive_step: ∀ q a: ℕ, nat.prime q → (abv a) ^ α = abv' a
      → (abv (q * a: ℕ)) ^ α = abv' ((q * a): ℕ),
    {
      intros q a q_prime a_norms_eq,
      calc (abv (q * a: ℕ)) ^ α
        = abv (q*a: ℕ) ^ α
          : rfl
        ... = ((abv q) * (abv a)) ^ α
          : by { push_cast, rw abv_mul abv, }
        ... = (abv q) ^ α * (abv a) ^ α
          : by rw real.mul_rpow (abv_nonneg abv q) (abv_nonneg abv a)
        ... = (abv' q) * (abv' a)
          : by rw [h q (nat.prime_iff.1 q_prime), a_norms_eq]
        ... = abv' ((q * a): ℕ)
            : by { rw ← abv_mul abv', norm_cast, }
    },
    apply induction_on_primes,
    {
      norm_cast,
      rw [abv_zero abv, abv_zero abv'],
      rw real.zero_rpow,
      exact (ne_of_lt α_pos).symm,
    },
    {
      norm_cast,
      rw [abv_one abv, abv_one abv'],
      rw real.one_rpow,
    },
    exact inductive_step,
  },

  set equiv_hom := hom_of_equiv (hom_of_abv abv) α α_pos
    (by { rw ← show abv = hom_of_abv abv, from rfl, exact abv_nonneg abv, }) with equiv_hom_def,
  set abv'_hom := abv_hom abv' with abv'_hom_def,
  have abv_eq_hom_fn: abv' = abv'_hom := rfl,
  have equiv_abs_eq_hom_fn: (λ r: ℚ, abv r ^ α) = equiv_hom := rfl,
  have same_on_neg_one: equiv_hom (-1) = abv'_hom (-1),
  {
    rw [← abv_eq_hom_fn, ← equiv_abs_eq_hom_fn],
    simp,
    rw [abv_neg abv, abv_neg abv'],
    rw [abv_one abv, abv_one abv'],
    rw real.one_rpow,
  },
  apply ext_hom_pnat equiv_hom abv'_hom same_on_neg_one,

  exact this,
end