l3lab/ntp-mathlib huggingface dataset:
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  

Proof state:
case intro
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
⊢ ∃ f,
    ∀ (x y : 𝕎 k),
      f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
        peval (polyOfInterest p n) ![fun i => coeff x i, fun i => coeff y i]

Next tactic:
use f
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  

Proof state:
case h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
⊢ ∀ (x y : 𝕎 k),
    f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      peval (polyOfInterest p n) ![fun i => coeff x i, fun i => coeff y i]

Next tactic:
intro x y
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  

Proof state:
case h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
⊢ f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
    peval (polyOfInterest p n) ![fun i => coeff x i, fun i => coeff y i]

Next tactic:
dsimp [peval]
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  

Proof state:
case h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
⊢ (f₀ fun a =>
      Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y] (↑a).1
        { val := (↑a).2, isLt := (_ : (↑a).2 < n + 1) }) =
    (aeval (Function.uncurry ![fun i => coeff x i, fun i => coeff y i])) (polyOfInterest p n)

Next tactic:
rw [← hf₀]
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  

Proof state:
case h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
⊢ (f₀ fun a =>
      Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y] (↑a).1
        { val := (↑a).2, isLt := (_ : (↑a).2 < n + 1) }) =
    f₀ (Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val)

Next tactic:
congr
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  congr
  

Proof state:
case h.e_a
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
⊢ (fun a =>
      Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y] (↑a).1
        { val := (↑a).2, isLt := (_ : (↑a).2 < n + 1) }) =
    Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val

Next tactic:
ext a
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  congr
  ext a
  

Proof state:
case h.e_a.h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
a : ↑fun a => a ∈ univ.val ×ˢ Multiset.range (n + 1)
⊢ Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y] (↑a).1
      { val := (↑a).2, isLt := (_ : (↑a).2 < n + 1) } =
    (Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val) a

Next tactic:
cases' a with a ha
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  congr
  ext a
  cases' a with a ha
  

Proof state:
case h.e_a.h.mk
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
a : Fin 2 × ℕ
ha : a ∈ fun a => a ∈ univ.val ×ˢ Multiset.range (n + 1)
⊢ Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y] (↑{ val := a, property := ha }).1
      { val := (↑{ val := a, property := ha }).2, isLt := (_ : (↑{ val := a, property := ha }).2 < n + 1) } =
    (Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val) { val := a, property := ha }

Next tactic:
cases' a with i m
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  congr
  ext a
  cases' a with a ha
  cases' a with i m
  

Proof state:
case h.e_a.h.mk.mk
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
i : Fin 2
m : ℕ
ha : (i, m) ∈ fun a => a ∈ univ.val ×ˢ Multiset.range (n + 1)
⊢ Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y] (↑{ val := (i, m), property := ha }).1
      { val := (↑{ val := (i, m), property := ha }).2, isLt := (_ : (↑{ val := (i, m), property := ha }).2 < n + 1) } =
    (Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val) { val := (i, m), property := ha }

Next tactic:
fin_cases i
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  congr
  ext a
  cases' a with a ha
  cases' a with i m
  fin_cases i <;> 

Proof state:
case h.e_a.h.mk.mk.head
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
m : ℕ
ha : ({ val := 0, isLt := (_ : 0 < 2) }, m) ∈ fun a => a ∈ univ.val ×ˢ Multiset.range (n + 1)
⊢ Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y]
      (↑{ val := ({ val := 0, isLt := (_ : 0 < 2) }, m), property := ha }).1
      { val := (↑{ val := ({ val := 0, isLt := (_ : 0 < 2) }, m), property := ha }).2,
        isLt := (_ : (↑{ val := ({ val := 0, isLt := (_ : 0 < 2) }, m), property := ha }).2 < n + 1) } =
    (Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val)
      { val := ({ val := 0, isLt := (_ : 0 < 2) }, m), property := ha }

Next tactic:
rfl
theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) := by
  simp only [← peval_polyOfInterest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (polyOfInterest_vars p n)
  have : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = a.product b := fun a b => rfl
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k := by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp_rw [product_val, this, Multiset.mem_product, mem_univ_val, true_and_iff, range_val,
      Multiset.range_succ, Multiset.mem_cons, Multiset.mem_range] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  congr
  ext a
  cases' a with a ha
  cases' a with i m
  fin_cases i <;> 

Proof state:
case h.e_a.h.mk.mk.tail.head
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f₀ : ((↑fun a => a ∈ (univ ×ˢ range (n + 1)).val) → k) → k
hf₀ : ∀ (x : Fin 2 × ℕ → k), f₀ (x ∘ Subtype.val) = (aeval x) (polyOfInterest p n)
this : ∀ (a : Multiset (Fin 2)) (b : Multiset ℕ), a ×ˢ b = Multiset.product a b
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
  fun x y =>
    f₀ fun a => Subtype.casesOn a fun a ha => Function.uncurry ![x, y] (a.1, { val := a.2, isLt := (_ : a.2 < n + 1) })
x y : 𝕎 k
m : ℕ
ha : ({ val := 1, isLt := (_ : (fun a => a < 2) 1) }, m) ∈ fun a => a ∈ univ.val ×ˢ Multiset.range (n + 1)
⊢ Matrix.vecCons (truncateFun (n + 1) x) ![truncateFun (n + 1) y]
      (↑{ val := ({ val := 1, isLt := (_ : (fun a => a < 2) 1) }, m), property := ha }).1
      { val := (↑{ val := ({ val := 1, isLt := (_ : (fun a => a < 2) 1) }, m), property := ha }).2,
        isLt := (_ : (↑{ val := ({ val := 1, isLt := (_ : (fun a => a < 2) 1) }, m), property := ha }).2 < n + 1) } =
    (Function.uncurry ![fun i => coeff x i, fun i => coeff y i] ∘ Subtype.val)
      { val := ({ val := 1, isLt := (_ : (fun a => a < 2) 1) }, m), property := ha }

Next tactic:
rfl
theorem nth_mul_coeff (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, (x * y).coeff (n + 1) =
      x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) + y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) +
      f (truncateFun (n + 1) x) (truncateFun (n + 1) y) := by
  

Proof state:
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
⊢ ∃ f,
    ∀ (x y : 𝕎 k),
      coeff (x * y) (n + 1) =
        coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1) + coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) +
          f (truncateFun (n + 1) x) (truncateFun (n + 1) y)

Next tactic:
obtain ⟨f, hf⟩ := nth_mul_coeff' p k n
theorem nth_mul_coeff (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, (x * y).coeff (n + 1) =
      x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) + y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) +
      f (truncateFun (n + 1) x) (truncateFun (n + 1) y) := by
  obtain ⟨f, hf⟩ := nth_mul_coeff' p k n
  

Proof state:
case intro
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k
hf :
  ∀ (x y : 𝕎 k),
    f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      coeff (x * y) (n + 1) - coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) - coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1)
⊢ ∃ f,
    ∀ (x y : 𝕎 k),
      coeff (x * y) (n + 1) =
        coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1) + coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) +
          f (truncateFun (n + 1) x) (truncateFun (n + 1) y)

Next tactic:
use f
theorem nth_mul_coeff (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, (x * y).coeff (n + 1) =
      x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) + y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) +
      f (truncateFun (n + 1) x) (truncateFun (n + 1) y) := by
  obtain ⟨f, hf⟩ := nth_mul_coeff' p k n
  use f
  

Proof state:
case h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k
hf :
  ∀ (x y : 𝕎 k),
    f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      coeff (x * y) (n + 1) - coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) - coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1)
⊢ ∀ (x y : 𝕎 k),
    coeff (x * y) (n + 1) =
      coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1) + coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) +
        f (truncateFun (n + 1) x) (truncateFun (n + 1) y)

Next tactic:
intro x y
theorem nth_mul_coeff (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
    ∀ x y : 𝕎 k, (x * y).coeff (n + 1) =
      x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) + y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) +
      f (truncateFun (n + 1) x) (truncateFun (n + 1) y) := by
  obtain ⟨f, hf⟩ := nth_mul_coeff' p k n
  use f
  intro x y
  

Proof state:
case h
p : ℕ
hp : Fact (Nat.Prime p)
k : Type u_1
inst✝¹ : CommRing k
inst✝ : CharP k p
n : ℕ
f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k
hf :
  ∀ (x y : 𝕎 k),
    f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
      coeff (x * y) (n + 1) - coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) - coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1)
x y : 𝕎 k
⊢ coeff (x * y) (n + 1) =
    coeff x (n + 1) * coeff y 0 ^ p ^ (n + 1) + coeff y (n + 1) * coeff x 0 ^ p ^ (n + 1) +
      f (truncateFun (n + 1) x) (truncateFun (n + 1) y)

Next tactic:
rw [hf x y]