l3lab/ntp-mathlib huggingface dataset:
/-- The Jacobi symbol takes only the values `0`, `1` and `-1`. -/
theorem trichotomy (a : ℤ) (b : ℕ) : J(a | b) = 0 ∨ J(a | b) = 1 ∨ J(a | b) = -1 :=
  ((@SignType.castHom ℤ _ _).toMonoidHom.mrange.copy {0, 1, -1} <| by
    rw [Set.pair_comm];
    exact (SignType.range_eq SignType.castHom).symm).list_prod_mem
      (by
        intro _ ha'
        rcases List.mem_pmap.mp ha' with ⟨p, hp, rfl⟩
        

Proof state:
case intro.intro
a : ℤ
b p : ℕ
hp : p ∈ factors b
ha' : legendreSym p a ∈ List.pmap (fun p pp => legendreSym p a) (factors b) (_ : ∀ x ∈ factors b, Nat.Prime x)
⊢ legendreSym p a ∈
    Submonoid.copy (MonoidHom.mrange ↑SignType.castHom) {0, 1, -1}
      (_ : {0, 1, -1} = ↑(MonoidHom.mrange ↑SignType.castHom))

Next tactic:
haveI : Fact p.Prime := ⟨prime_of_mem_factors hp⟩
/-- The Jacobi symbol takes only the values `0`, `1` and `-1`. -/
theorem trichotomy (a : ℤ) (b : ℕ) : J(a | b) = 0 ∨ J(a | b) = 1 ∨ J(a | b) = -1 :=
  ((@SignType.castHom ℤ _ _).toMonoidHom.mrange.copy {0, 1, -1} <| by
    rw [Set.pair_comm];
    exact (SignType.range_eq SignType.castHom).symm).list_prod_mem
      (by
        intro _ ha'
        rcases List.mem_pmap.mp ha' with ⟨p, hp, rfl⟩
        haveI : Fact p.Prime := ⟨prime_of_mem_factors hp⟩
        

Proof state:
case intro.intro
a : ℤ
b p : ℕ
hp : p ∈ factors b
ha' : legendreSym p a ∈ List.pmap (fun p pp => legendreSym p a) (factors b) (_ : ∀ x ∈ factors b, Nat.Prime x)
this : Fact (Nat.Prime p)
⊢ legendreSym p a ∈
    Submonoid.copy (MonoidHom.mrange ↑SignType.castHom) {0, 1, -1}
      (_ : {0, 1, -1} = ↑(MonoidHom.mrange ↑SignType.castHom))

Next tactic:
exact quadraticChar_isQuadratic (ZMod p) a
/-- The symbol `J(1 | b)` has the value `1`. -/
@[simp]
theorem one_left (b : ℕ) : J(1 | b) = 1 :=
  List.prod_eq_one fun z hz => by
    

Proof state:
b : ℕ
z : ℤ
hz : z ∈ List.pmap (fun p pp => legendreSym p 1) (factors b) (_ : ∀ x ∈ factors b, Nat.Prime x)
⊢ z = 1

Next tactic:
let ⟨p, hp, he⟩ := List.mem_pmap.1 hz
/-- The symbol `J(1 | b)` has the value `1`. -/
@[simp]
theorem one_left (b : ℕ) : J(1 | b) = 1 :=
  List.prod_eq_one fun z hz => by
    let ⟨p, hp, he⟩ := List.mem_pmap.1 hz
    -- porting note: The line 150 was added because Lean does not synthesize the instance
    -- `[Fact (Nat.Prime p)]` automatically (it is needed for `legendreSym.at_one`)
    

Proof state:
b : ℕ
z : ℤ
hz : z ∈ List.pmap (fun p pp => legendreSym p 1) (factors b) (_ : ∀ x ∈ factors b, Nat.Prime x)
p : ℕ
hp : p ∈ factors b
he : legendreSym p 1 = z
⊢ z = 1

Next tactic:
letI : Fact p.Prime := ⟨prime_of_mem_factors hp⟩
/-- The symbol `J(1 | b)` has the value `1`. -/
@[simp]
theorem one_left (b : ℕ) : J(1 | b) = 1 :=
  List.prod_eq_one fun z hz => by
    let ⟨p, hp, he⟩ := List.mem_pmap.1 hz
    -- porting note: The line 150 was added because Lean does not synthesize the instance
    -- `[Fact (Nat.Prime p)]` automatically (it is needed for `legendreSym.at_one`)
    letI : Fact p.Prime := ⟨prime_of_mem_factors hp⟩
    

Proof state:
b : ℕ
z : ℤ
hz : z ∈ List.pmap (fun p pp => legendreSym p 1) (factors b) (_ : ∀ x ∈ factors b, Nat.Prime x)
p : ℕ
hp : p ∈ factors b
he : legendreSym p 1 = z
this : Fact (Nat.Prime p) := { out := prime_of_mem_factors hp }
⊢ z = 1

Next tactic:
rw [← he, legendreSym.at_one]
/-- The Jacobi symbol is multiplicative in its first argument. -/
theorem mul_left (a₁ a₂ : ℤ) (b : ℕ) : J(a₁ * a₂ | b) = J(a₁ | b) * J(a₂ | b) := by
  

Proof state:
a₁ a₂ : ℤ
b : ℕ
⊢ J(a₁ * a₂ | b) = J(a₁ | b) * J(a₂ | b)

Next tactic:
simp_rw [jacobiSym, List.pmap_eq_map_attach, legendreSym.mul _ _ _]
/-- The Jacobi symbol is multiplicative in its first argument. -/
theorem mul_left (a₁ a₂ : ℤ) (b : ℕ) : J(a₁ * a₂ | b) = J(a₁ | b) * J(a₂ | b) := by
  simp_rw [jacobiSym, List.pmap_eq_map_attach, legendreSym.mul _ _ _];
  

Proof state:
a₁ a₂ : ℤ
b : ℕ
⊢ List.prod (List.map (fun x => legendreSym (↑x) a₁ * legendreSym (↑x) a₂) (List.attach (factors b))) =
    List.prod (List.map (fun x => legendreSym (↑x) a₁) (List.attach (factors b))) *
      List.prod (List.map (fun x => legendreSym (↑x) a₂) (List.attach (factors b)))

Next tactic:
exact List.prod_map_mul (α := ℤ) (l := (factors b).attach)
    (f := fun x ↦ @legendreSym x {out := prime_of_mem_factors x.2} a₁)
    (g := fun x ↦ @legendreSym x {out := prime_of_mem_factors x.2} a₂)
/-- The symbol `J(a | b)` vanishes iff `a` and `b` are not coprime (assuming `b ≠ 0`). -/
theorem eq_zero_iff_not_coprime {a : ℤ} {b : ℕ} [NeZero b] : J(a | b) = 0 ↔ a.gcd b ≠ 1 :=
  List.prod_eq_zero_iff.trans
    (by
      

Proof state:
a : ℤ
b : ℕ
inst✝ : NeZero b
⊢ 0 ∈ List.pmap (fun p pp => legendreSym p a) (factors b) (_ : ∀ x ∈ factors b, Nat.Prime x) ↔ Int.gcd a ↑b ≠ 1

Next tactic:
rw [List.mem_pmap, Int.gcd_eq_natAbs, Ne, Prime.not_coprime_iff_dvd]
/-- The symbol `J(a | b)` vanishes iff `a` and `b` are not coprime (assuming `b ≠ 0`). -/
theorem eq_zero_iff_not_coprime {a : ℤ} {b : ℕ} [NeZero b] : J(a | b) = 0 ↔ a.gcd b ≠ 1 :=
  List.prod_eq_zero_iff.trans
    (by
      rw [List.mem_pmap, Int.gcd_eq_natAbs, Ne, Prime.not_coprime_iff_dvd]
      -- porting note: Initially, `and_assoc'` and `and_comm'` were used on line 164 but they have
      -- been deprecated so we replace them with `and_assoc` and `and_comm`
      

Proof state:
a : ℤ
b : ℕ
inst✝ : NeZero b
⊢ (∃ a_1, ∃ (h : a_1 ∈ factors b), legendreSym a_1 a = 0) ↔ ∃ p, Nat.Prime p ∧ p ∣ Int.natAbs a ∧ p ∣ Int.natAbs ↑b

Next tactic:
simp_rw [legendreSym.eq_zero_iff _ _, int_cast_zmod_eq_zero_iff_dvd,
        mem_factors (NeZero.ne b), ← Int.coe_nat_dvd_left, Int.coe_nat_dvd, exists_prop, and_assoc,
        and_comm]
/-- The symbol `J(a | b)` is nonzero when `a` and `b` are coprime. -/
protected theorem ne_zero {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ≠ 0 := by
  

Proof state:
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
⊢ J(a | b) ≠ 0

Next tactic:
cases' eq_zero_or_neZero b with hb
/-- The symbol `J(a | b)` is nonzero when `a` and `b` are coprime. -/
protected theorem ne_zero {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ≠ 0 := by
  cases' eq_zero_or_neZero b with hb
  · 

Proof state:
case inl
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
hb : b = 0
⊢ J(a | b) ≠ 0

Next tactic:
rw [hb, zero_right]
/-- The symbol `J(a | b)` is nonzero when `a` and `b` are coprime. -/
protected theorem ne_zero {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ≠ 0 := by
  cases' eq_zero_or_neZero b with hb
  · rw [hb, zero_right]
    

Proof state:
case inl
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
hb : b = 0
⊢ 1 ≠ 0

Next tactic:
exact one_ne_zero
/-- The symbol `J(a | b)` is nonzero when `a` and `b` are coprime. -/
protected theorem ne_zero {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ≠ 0 := by
  cases' eq_zero_or_neZero b with hb
  · rw [hb, zero_right]
    exact one_ne_zero
  · 

Proof state:
case inr
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
h✝ : NeZero b
⊢ J(a | b) ≠ 0

Next tactic:
contrapose! h
/-- The symbol `J(a | b)` is nonzero when `a` and `b` are coprime. -/
protected theorem ne_zero {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ≠ 0 := by
  cases' eq_zero_or_neZero b with hb
  · rw [hb, zero_right]
    exact one_ne_zero
  · contrapose! h; 

Proof state:
case inr
a : ℤ
b : ℕ
h✝ : NeZero b
h : J(a | b) = 0
⊢ Int.gcd a ↑b ≠ 1

Next tactic:
exact eq_zero_iff_not_coprime.1 h
/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
theorem eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
  ⟨fun h => by
    

Proof state:
a : ℤ
b : ℕ
h : J(a | b) = 0
⊢ b ≠ 0 ∧ Int.gcd a ↑b ≠ 1

Next tactic:
rcases eq_or_ne b 0 with hb | hb
/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
theorem eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
  ⟨fun h => by
    rcases eq_or_ne b 0 with hb | hb
    · 

Proof state:
case inl
a : ℤ
b : ℕ
h : J(a | b) = 0
hb : b = 0
⊢ b ≠ 0 ∧ Int.gcd a ↑b ≠ 1

Next tactic:
rw [hb, zero_right] at h
/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
theorem eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
  ⟨fun h => by
    rcases eq_or_ne b 0 with hb | hb
    · rw [hb, zero_right] at h; 

Proof state:
case inl
a : ℤ
b : ℕ
h : 1 = 0
hb : b = 0
⊢ b ≠ 0 ∧ Int.gcd a ↑b ≠ 1

Next tactic:
cases h
/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
theorem eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
  ⟨fun h => by
    rcases eq_or_ne b 0 with hb | hb
    · rw [hb, zero_right] at h; cases h
    

Proof state:
case inr
a : ℤ
b : ℕ
h : J(a | b) = 0
hb : b ≠ 0
⊢ b ≠ 0 ∧ Int.gcd a ↑b ≠ 1

Next tactic:
exact ⟨hb, mt jacobiSym.ne_zero <| Classical.not_not.2 h⟩
/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
theorem eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
  ⟨fun h => by
    rcases eq_or_ne b 0 with hb | hb
    · rw [hb, zero_right] at h; cases h
    exact ⟨hb, mt jacobiSym.ne_zero <| Classical.not_not.2 h⟩, fun ⟨hb, h⟩ => by
    

Proof state:
a : ℤ
b : ℕ
x✝ : b ≠ 0 ∧ Int.gcd a ↑b ≠ 1
hb : b ≠ 0
h : Int.gcd a ↑b ≠ 1
⊢ J(a | b) = 0

Next tactic:
rw [← neZero_iff] at hb
/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
theorem eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
  ⟨fun h => by
    rcases eq_or_ne b 0 with hb | hb
    · rw [hb, zero_right] at h; cases h
    exact ⟨hb, mt jacobiSym.ne_zero <| Classical.not_not.2 h⟩, fun ⟨hb, h⟩ => by
    rw [← neZero_iff] at hb; 

Proof state:
a : ℤ
b : ℕ
x✝ : b ≠ 0 ∧ Int.gcd a ↑b ≠ 1
hb : NeZero b
h : Int.gcd a ↑b ≠ 1
⊢ J(a | b) = 0

Next tactic:
exact eq_zero_iff_not_coprime.2 h
/-- The symbol `J(0 | b)` vanishes when `b > 1`. -/
theorem zero_left {b : ℕ} (hb : 1 < b) : J(0 | b) = 0 :=
  (@eq_zero_iff_not_coprime 0 b ⟨ne_zero_of_lt hb⟩).mpr <| by
    

Proof state:
b : ℕ
hb : 1 < b
⊢ Int.gcd 0 ↑b ≠ 1

Next tactic:
rw [Int.gcd_zero_left, Int.natAbs_ofNat]
/-- The symbol `J(0 | b)` vanishes when `b > 1`. -/
theorem zero_left {b : ℕ} (hb : 1 < b) : J(0 | b) = 0 :=
  (@eq_zero_iff_not_coprime 0 b ⟨ne_zero_of_lt hb⟩).mpr <| by
    rw [Int.gcd_zero_left, Int.natAbs_ofNat]; 

Proof state:
b : ℕ
hb : 1 < b
⊢ b ≠ 1

Next tactic:
exact hb.ne'
/-- We have that `J(a^e | b) = J(a | b)^e`. -/
theorem pow_left (a : ℤ) (e b : ℕ) : J(a ^ e | b) = J(a | b) ^ e :=
  Nat.recOn e (by 

Proof state:
a : ℤ
e b : ℕ
⊢ J(a ^ zero | b) = J(a | b) ^ zero

Next tactic:
rw [_root_.pow_zero, _root_.pow_zero, one_left]
/-- We have that `J(a^e | b) = J(a | b)^e`. -/
theorem pow_left (a : ℤ) (e b : ℕ) : J(a ^ e | b) = J(a | b) ^ e :=
  Nat.recOn e (by rw [_root_.pow_zero, _root_.pow_zero, one_left]) fun _ ih => by
    

Proof state:
a : ℤ
e b x✝ : ℕ
ih : J(a ^ x✝ | b) = J(a | b) ^ x✝
⊢ J(a ^ succ x✝ | b) = J(a | b) ^ succ x✝

Next tactic:
rw [_root_.pow_succ, _root_.pow_succ, mul_left, ih]
/-- We have that `J(a | b^e) = J(a | b)^e`. -/
theorem pow_right (a : ℤ) (b e : ℕ) : J(a | b ^ e) = J(a | b) ^ e := by
  

Proof state:
a : ℤ
b e : ℕ
⊢ J(a | b ^ e) = J(a | b) ^ e

Next tactic:
induction' e with e ih
/-- We have that `J(a | b^e) = J(a | b)^e`. -/
theorem pow_right (a : ℤ) (b e : ℕ) : J(a | b ^ e) = J(a | b) ^ e := by
  induction' e with e ih
  · 

Proof state:
case zero
a : ℤ
b : ℕ
⊢ J(a | b ^ zero) = J(a | b) ^ zero

Next tactic:
rw [Nat.pow_zero, _root_.pow_zero, one_right]
/-- We have that `J(a | b^e) = J(a | b)^e`. -/
theorem pow_right (a : ℤ) (b e : ℕ) : J(a | b ^ e) = J(a | b) ^ e := by
  induction' e with e ih
  · rw [Nat.pow_zero, _root_.pow_zero, one_right]
  · 

Proof state:
case succ
a : ℤ
b e : ℕ
ih : J(a | b ^ e) = J(a | b) ^ e
⊢ J(a | b ^ succ e) = J(a | b) ^ succ e

Next tactic:
cases' eq_zero_or_neZero b with hb
/-- We have that `J(a | b^e) = J(a | b)^e`. -/
theorem pow_right (a : ℤ) (b e : ℕ) : J(a | b ^ e) = J(a | b) ^ e := by
  induction' e with e ih
  · rw [Nat.pow_zero, _root_.pow_zero, one_right]
  · cases' eq_zero_or_neZero b with hb
    · 

Proof state:
case succ.inl
a : ℤ
b e : ℕ
ih : J(a | b ^ e) = J(a | b) ^ e
hb : b = 0
⊢ J(a | b ^ succ e) = J(a | b) ^ succ e

Next tactic:
rw [hb, zero_pow (succ_pos e), zero_right, one_pow]
/-- We have that `J(a | b^e) = J(a | b)^e`. -/
theorem pow_right (a : ℤ) (b e : ℕ) : J(a | b ^ e) = J(a | b) ^ e := by
  induction' e with e ih
  · rw [Nat.pow_zero, _root_.pow_zero, one_right]
  · cases' eq_zero_or_neZero b with hb
    · rw [hb, zero_pow (succ_pos e), zero_right, one_pow]
    · 

Proof state:
case succ.inr
a : ℤ
b e : ℕ
ih : J(a | b ^ e) = J(a | b) ^ e
h✝ : NeZero b
⊢ J(a | b ^ succ e) = J(a | b) ^ succ e

Next tactic:
rw [_root_.pow_succ, _root_.pow_succ, mul_right, ih]
/-- The square of `J(a | b)` is `1` when `a` and `b` are coprime. -/
theorem sq_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ^ 2 = 1 := by
  

Proof state:
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
⊢ J(a | b) ^ 2 = 1

Next tactic:
cases' eq_one_or_neg_one h with h₁ h₁
/-- The square of `J(a | b)` is `1` when `a` and `b` are coprime. -/
theorem sq_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ^ 2 = 1 := by
  cases' eq_one_or_neg_one h with h₁ h₁ <;> 

Proof state:
case inl
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
h₁ : J(a | b) = 1
⊢ J(a | b) ^ 2 = 1

Next tactic:
rw [h₁]
/-- The square of `J(a | b)` is `1` when `a` and `b` are coprime. -/
theorem sq_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ^ 2 = 1 := by
  cases' eq_one_or_neg_one h with h₁ h₁ <;> 

Proof state:
case inr
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
h₁ : J(a | b) = -1
⊢ J(a | b) ^ 2 = 1

Next tactic:
rw [h₁]
/-- The square of `J(a | b)` is `1` when `a` and `b` are coprime. -/
theorem sq_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ^ 2 = 1 := by
  cases' eq_one_or_neg_one h with h₁ h₁ <;> rw [h₁] <;> 

Proof state:
case inl
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
h₁ : J(a | b) = 1
⊢ 1 ^ 2 = 1

Next tactic:
rfl
/-- The square of `J(a | b)` is `1` when `a` and `b` are coprime. -/
theorem sq_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ^ 2 = 1 := by
  cases' eq_one_or_neg_one h with h₁ h₁ <;> rw [h₁] <;> 

Proof state:
case inr
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
h₁ : J(a | b) = -1
⊢ (-1) ^ 2 = 1

Next tactic:
rfl
/-- The symbol `J(a^2 | b)` is `1` when `a` and `b` are coprime. -/
theorem sq_one' {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a ^ 2 | b) = 1 := by 

Proof state:
a : ℤ
b : ℕ
h : Int.gcd a ↑b = 1
⊢ J(a ^ 2 | b) = 1

Next tactic:
rw [pow_left, sq_one h]
/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
theorem mod_left (a : ℤ) (b : ℕ) : J(a | b) = J(a % b | b) :=
  congr_arg List.prod <|
    List.pmap_congr _
      (by
        -- porting note: Lean does not synthesize the instance [Fact (Nat.Prime p)] automatically
        -- (it is needed for `legendreSym.mod` on line 227). Thus, we name the hypothesis
        -- `Nat.Prime p` explicitly on line 224 and prove `Fact (Nat.Prime p)` on line 225.
        

Proof state:
a : ℤ
b : ℕ
⊢ ∀ a_1 ∈ factors b, ∀ (h₁ h₂ : Nat.Prime a_1), legendreSym a_1 a = legendreSym a_1 (a % ↑b)

Next tactic:
rintro p hp _ h₂
/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
theorem mod_left (a : ℤ) (b : ℕ) : J(a | b) = J(a % b | b) :=
  congr_arg List.prod <|
    List.pmap_congr _
      (by
        -- porting note: Lean does not synthesize the instance [Fact (Nat.Prime p)] automatically
        -- (it is needed for `legendreSym.mod` on line 227). Thus, we name the hypothesis
        -- `Nat.Prime p` explicitly on line 224 and prove `Fact (Nat.Prime p)` on line 225.
        rintro p hp _ h₂
        

Proof state:
a : ℤ
b p : ℕ
hp : p ∈ factors b
h₁✝ h₂ : Nat.Prime p
⊢ legendreSym p a = legendreSym p (a % ↑b)

Next tactic:
letI : Fact p.Prime := ⟨h₂⟩
/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
theorem mod_left (a : ℤ) (b : ℕ) : J(a | b) = J(a % b | b) :=
  congr_arg List.prod <|
    List.pmap_congr _
      (by
        -- porting note: Lean does not synthesize the instance [Fact (Nat.Prime p)] automatically
        -- (it is needed for `legendreSym.mod` on line 227). Thus, we name the hypothesis
        -- `Nat.Prime p` explicitly on line 224 and prove `Fact (Nat.Prime p)` on line 225.
        rintro p hp _ h₂
        letI : Fact p.Prime := ⟨h₂⟩
        

Proof state:
a : ℤ
b p : ℕ
hp : p ∈ factors b
h₁✝ h₂ : Nat.Prime p
this : Fact (Nat.Prime p) := { out := h₂ }
⊢ legendreSym p a = legendreSym p (a % ↑b)

Next tactic:
conv_rhs =>
          rw [legendreSym.mod, Int.emod_emod_of_dvd _ (Int.coe_nat_dvd.2 <| dvd_of_mem_factors hp),
            ← legendreSym.mod]
/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
theorem mod_left (a : ℤ) (b : ℕ) : J(a | b) = J(a % b | b) :=
  congr_arg List.prod <|
    List.pmap_congr _
      (by
        -- porting note: Lean does not synthesize the instance [Fact (Nat.Prime p)] automatically
        -- (it is needed for `legendreSym.mod` on line 227). Thus, we name the hypothesis
        -- `Nat.Prime p` explicitly on line 224 and prove `Fact (Nat.Prime p)` on line 225.
        rintro p hp _ h₂
        letI : Fact p.Prime := ⟨h₂⟩
        conv_rhs =>
          

Proof state:
a : ℤ
b p : ℕ
hp : p ∈ factors b
h₁✝ h₂ : Nat.Prime p
this : Fact (Nat.Prime p) := { out := h₂ }
| legendreSym p (a % ↑b)

Next tactic:
rw [legendreSym.mod, Int.emod_emod_of_dvd _ (Int.coe_nat_dvd.2 <| dvd_of_mem_factors hp),
            ← legendreSym.mod]
/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
theorem mod_left' {a₁ a₂ : ℤ} {b : ℕ} (h : a₁ % b = a₂ % b) : J(a₁ | b) = J(a₂ | b) := by
  

Proof state:
a₁ a₂ : ℤ
b : ℕ
h : a₁ % ↑b = a₂ % ↑b
⊢ J(a₁ | b) = J(a₂ | b)

Next tactic:
rw [mod_left, h, ← mod_left]
/-- If `p` is prime, `J(a | p) = -1` and `p` divides `x^2 - a*y^2`, then `p` must divide
`x` and `y`. -/
theorem prime_dvd_of_eq_neg_one {p : ℕ} [Fact p.Prime] {a : ℤ} (h : J(a | p) = -1) {x y : ℤ}
    (hxy : ↑p ∣ (x ^ 2 - a * y ^ 2 : ℤ)) : ↑p ∣ x ∧ ↑p ∣ y := by
  

Proof state:
p : ℕ
inst✝ : Fact (Nat.Prime p)
a : ℤ
h : J(a | p) = -1
x y : ℤ
hxy : ↑p ∣ x ^ 2 - a * y ^ 2
⊢ ↑p ∣ x ∧ ↑p ∣ y

Next tactic:
rw [← legendreSym.to_jacobiSym] at h
/-- If `p` is prime, `J(a | p) = -1` and `p` divides `x^2 - a*y^2`, then `p` must divide
`x` and `y`. -/
theorem prime_dvd_of_eq_neg_one {p : ℕ} [Fact p.Prime] {a : ℤ} (h : J(a | p) = -1) {x y : ℤ}
    (hxy : ↑p ∣ (x ^ 2 - a * y ^ 2 : ℤ)) : ↑p ∣ x ∧ ↑p ∣ y := by
  rw [← legendreSym.to_jacobiSym] at h
  

Proof state:
p : ℕ
inst✝ : Fact (Nat.Prime p)
a : ℤ
h : legendreSym p a = -1
x y : ℤ
hxy : ↑p ∣ x ^ 2 - a * y ^ 2
⊢ ↑p ∣ x ∧ ↑p ∣ y

Next tactic:
exact legendreSym.prime_dvd_of_eq_neg_one h hxy
/-- We can pull out a product over a list in the first argument of the Jacobi symbol. -/
theorem list_prod_left {l : List ℤ} {n : ℕ} : J(l.prod | n) = (l.map fun a => J(a | n)).prod := by
  

Proof state:
l : List ℤ
n : ℕ
⊢ J(List.prod l | n) = List.prod (List.map (fun a => J(a | n)) l)

Next tactic:
induction' l with n l' ih
/-- We can pull out a product over a list in the first argument of the Jacobi symbol. -/
theorem list_prod_left {l : List ℤ} {n : ℕ} : J(l.prod | n) = (l.map fun a => J(a | n)).prod := by
  induction' l with n l' ih
  · 

Proof state:
case nil
n : ℕ
⊢ J(List.prod [] | n) = List.prod (List.map (fun a => J(a | n)) [])

Next tactic:
simp only [List.prod_nil, List.map_nil, one_left]
/-- We can pull out a product over a list in the first argument of the Jacobi symbol. -/
theorem list_prod_left {l : List ℤ} {n : ℕ} : J(l.prod | n) = (l.map fun a => J(a | n)).prod := by
  induction' l with n l' ih
  · simp only [List.prod_nil, List.map_nil, one_left]
  · 

Proof state:
case cons
n✝ : ℕ
n : ℤ
l' : List ℤ
ih : J(List.prod l' | n✝) = List.prod (List.map (fun a => J(a | n✝)) l')
⊢ J(List.prod (n :: l') | n✝) = List.prod (List.map (fun a => J(a | n✝)) (n :: l'))

Next tactic:
rw [List.map, List.prod_cons, List.prod_cons, mul_left, ih]
/-- We can pull out a product over a list in the second argument of the Jacobi symbol. -/
theorem list_prod_right {a : ℤ} {l : List ℕ} (hl : ∀ n ∈ l, n ≠ 0) :
    J(a | l.prod) = (l.map fun n => J(a | n)).prod := by
  

Proof state:
a : ℤ
l : List ℕ
hl : ∀ n ∈ l, n ≠ 0
⊢ J(a | List.prod l) = List.prod (List.map (fun n => J(a | n)) l)

Next tactic:
induction' l with n l' ih
/-- We can pull out a product over a list in the second argument of the Jacobi symbol. -/
theorem list_prod_right {a : ℤ} {l : List ℕ} (hl : ∀ n ∈ l, n ≠ 0) :
    J(a | l.prod) = (l.map fun n => J(a | n)).prod := by
  induction' l with n l' ih
  · 

Proof state:
case nil
a : ℤ
hl : ∀ n ∈ [], n ≠ 0
⊢ J(a | List.prod []) = List.prod (List.map (fun n => J(a | n)) [])

Next tactic:
simp only [List.prod_nil, one_right, List.map_nil]
/-- We can pull out a product over a list in the second argument of the Jacobi symbol. -/
theorem list_prod_right {a : ℤ} {l : List ℕ} (hl : ∀ n ∈ l, n ≠ 0) :
    J(a | l.prod) = (l.map fun n => J(a | n)).prod := by
  induction' l with n l' ih
  · simp only [List.prod_nil, one_right, List.map_nil]
  · 

Proof state:
case cons
a : ℤ
n : ℕ
l' : List ℕ
ih : (∀ n ∈ l', n ≠ 0) → J(a | List.prod l') = List.prod (List.map (fun n => J(a | n)) l')
hl : ∀ n_1 ∈ n :: l', n_1 ≠ 0
⊢ J(a | List.prod (n :: l')) = List.prod (List.map (fun n => J(a | n)) (n :: l'))

Next tactic:
have hn := hl n (List.mem_cons_self n l')
/-- We can pull out a product over a list in the second argument of the Jacobi symbol. -/
theorem list_prod_right {a : ℤ} {l : List ℕ} (hl : ∀ n ∈ l, n ≠ 0) :
    J(a | l.prod) = (l.map fun n => J(a | n)).prod := by
  induction' l with n l' ih
  · simp only [List.prod_nil, one_right, List.map_nil]
  · have hn := hl n (List.mem_cons_self n l')
    -- `n ≠ 0`
    

Proof state:
case cons
a : ℤ
n : ℕ
l' : List ℕ
ih : (∀ n ∈ l', n ≠ 0) → J(a | List.prod l') = List.prod (List.map (fun n => J(a | n)) l')
hl : ∀ n_1 ∈ n :: l', n_1 ≠ 0
hn : n ≠ 0
⊢ J(a | List.prod (n :: l')) = List.prod (List.map (fun n => J(a | n)) (n :: l'))

Next tactic:
have hl' := List.prod_ne_zero fun hf => hl 0 (List.mem_cons_of_mem _ hf) rfl
/-- We can pull out a product over a list in the second argument of the Jacobi symbol. -/
theorem list_prod_right {a : ℤ} {l : List ℕ} (hl : ∀ n ∈ l, n ≠ 0) :
    J(a | l.prod) = (l.map fun n => J(a | n)).prod := by
  induction' l with n l' ih
  · simp only [List.prod_nil, one_right, List.map_nil]
  · have hn := hl n (List.mem_cons_self n l')
    -- `n ≠ 0`
    have hl' := List.prod_ne_zero fun hf => hl 0 (List.mem_cons_of_mem _ hf) rfl
    -- `l'.prod ≠ 0`
    

Proof state:
case cons
a : ℤ
n : ℕ
l' : List ℕ
ih : (∀ n ∈ l', n ≠ 0) → J(a | List.prod l') = List.prod (List.map (fun n => J(a | n)) l')
hl : ∀ n_1 ∈ n :: l', n_1 ≠ 0
hn : n ≠ 0
hl' : List.prod l' ≠ 0
⊢ J(a | List.prod (n :: l')) = List.prod (List.map (fun n => J(a | n)) (n :: l'))

Next tactic:
have h := fun m hm => hl m (List.mem_cons_of_mem _ hm)
/-- We can pull out a product over a list in the second argument of the Jacobi symbol. -/
theorem list_prod_right {a : ℤ} {l : List ℕ} (hl : ∀ n ∈ l, n ≠ 0) :
    J(a | l.prod) = (l.map fun n => J(a | n)).prod := by
  induction' l with n l' ih
  · simp only [List.prod_nil, one_right, List.map_nil]
  · have hn := hl n (List.mem_cons_self n l')
    -- `n ≠ 0`
    have hl' := List.prod_ne_zero fun hf => hl 0 (List.mem_cons_of_mem _ hf) rfl
    -- `l'.prod ≠ 0`
    have h := fun m hm => hl m (List.mem_cons_of_mem _ hm)
    -- `∀ (m : ℕ), m ∈ l' → m ≠ 0`
    

Proof state:
case cons
a : ℤ
n : ℕ
l' : List ℕ
ih : (∀ n ∈ l', n ≠ 0) → J(a | List.prod l') = List.prod (List.map (fun n => J(a | n)) l')
hl : ∀ n_1 ∈ n :: l', n_1 ≠ 0
hn : n ≠ 0
hl' : List.prod l' ≠ 0
h : ∀ m ∈ l', m ≠ 0
⊢ J(a | List.prod (n :: l')) = List.prod (List.map (fun n => J(a | n)) (n :: l'))

Next tactic:
rw [List.map, List.prod_cons, List.prod_cons, mul_right' a hn hl', ih h]
/-- If `J(a | n) = -1`, then `n` has a prime divisor `p` such that `J(a | p) = -1`. -/
theorem eq_neg_one_at_prime_divisor_of_eq_neg_one {a : ℤ} {n : ℕ} (h : J(a | n) = -1) :
    ∃ (p : ℕ) (_ : p.Prime), p ∣ n ∧ J(a | p) = -1 := by
  

Proof state:
a : ℤ
n : ℕ
h : J(a | n) = -1
⊢ ∃ p, ∃ (_ : Nat.Prime p), p ∣ n ∧ J(a | p) = -1

Next tactic:
have hn₀ : n ≠ 0 := by
    rintro rfl
    rw [zero_right, eq_neg_self_iff] at h
    exact one_ne_zero h