(* a dependent induction principle for le *)
  
  Scheme le_indd := Induction for le Sort Prop.Fact le_pirr : forall x y (H1 H2 : x <= y), H1 = H2.Fact lt_pirr x y (H1 H2 : x < y) : H1 = H2.Fact Zring : ring_theory 0%Z 1%Z Zplus Zmult Zminus Z.opp eq.Definition Z_pos_or_neg u : { 0 <= u } + { u < 0 }.Fact Z_coprime u v : (exists a b, a * Z.of_nat u + b*Z.of_nat v = 1) -> is_gcd u v 1%nat.Definition Z_Zp := { x | x < p }.Implicit Type x y : Z_Zp.Fact Zp_inj : forall x y, proj1_sig x = proj1_sig y -> x = y.Definition Zp_plus : Z_Zp -> Z_Zp -> Z_Zp.Notation "〚 x 〛" := (nat2Zp x).Definition Zp_zero : Z_Zp.Definition Zp_opp : Z_Zp -> Z_Zp.Definition Zp_mult : Z_Zp -> Z_Zp -> Z_Zp.Definition Zp_one : Z_Zp.(*

  Infix "+p" := Zp_plus (at level 50, left associativity).
  Infix "*p" := Zp_mult (at level 40, left associativity).
  Notation "-p" := Zp_opp.

*)

  Notation Zp := Zp_zero.Notation Op := Zp_one.Fact Zp_plus_zero : forall x, Zp ⊕ x = x.Fact Zp_plus_comm : forall x y,  x ⊕ y = y ⊕ x.Fact Zp_plus_assoc : forall x y z, x ⊕ (y ⊕ z) = x ⊕ y ⊕ z.Fact Zp_minus : forall x, x ⊕ ∸ x = Zp.Fact Zp_mult_one : forall x, Op ⊗ x = x.Fact Zp_mult_comm : forall x y, x ⊗ y = y ⊗ x.Fact Zp_mult_one_r x : x ⊗ Op = x.Fact Zp_mult_assoc : forall x y z, x ⊗ (y ⊗ z) = x ⊗ y ⊗ z.Fact Zp_mult_plus_distr_l : forall x y z, x ⊗ (y ⊕ z) = x ⊗ y ⊕ x ⊗ z.Fact Zp_mult_plus_distr_r x y z : (y ⊕ z) ⊗ x = y ⊗ x ⊕ z ⊗ x.(* Define a ring structureon Zp *)

  Definition Zp_is_ring : @ring_theory _ Zp Op Zp_plus Zp_mult (fun x y => x ⊕ ∸ y) Zp_opp eq.(* Invertible elements are some which are relatively prime to x *)

  Fact Zp_mult_invert : forall x, is_gcd p (proj1_sig x) 1 -> { i | i ⊗ x = Op }.Fact Zp_mult_revert : forall x i, i ⊗ x = Op -> is_gcd p (proj1_sig x) 1.Definition Zp_invertible x := exists i, i ⊗ x = Op.Fact Zp_invertible_spec x : Zp_invertible x <-> is_gcd p (proj1_sig x) 1.Fact Zp_prime_invert : prime p -> forall x, x <> Zp -> { i | i ⊗ x = Op }.Fact Zp_plus_monoid : monoid_theory Zp_plus Zp_zero.Fact Zp_mult_monoid : monoid_theory Zp_mult Zp_one.Fact Zp_one_invertible : Zp_invertible Op.Fact Zp_opp_invertible x : Zp_invertible x <-> Zp_invertible (∸ x).Fact Zp_mult_invertible x y : Zp_invertible x -> Zp_invertible y -> Zp_invertible (x ⊗ y).Fact Zp_expo_invertible n x : Zp_invertible x -> Zp_invertible (mscal Zp_mult Op n x).Fact Zp_invertible_cancel_l x y z : Zp_invertible x -> x⊗y = x⊗z -> y = z.(* ⊕  ⊗  ∸ *)

  Fact Zp_opp_zero : ∸ Zp = Zp.Fact Zp_plus_inj_l x y z : x ⊕ y = x ⊕ z -> y = z.Fact Zp_plus_zero_r x : x ⊕ Zp = x.Fact Zp_opp_inv x : ∸ (∸ x) = x.Fact Zp_opp_inj x y : ∸ x = ∸ y -> x = y.Fact Zp_opp_plus x y : ∸ (x ⊕ y) = ∸ x ⊕ ∸ y.Fact Zp_opp_plus_eq x y z : x ⊕ y = z <-> x = z ⊕ ∸ y.Fact Zp_mult_zero x : Zp ⊗ x = Zp.Fact Zp_mult_minus_one x : (∸ Op) ⊗ x = ∸ x.Fact Zp_opp_mult x y : (∸ x) ⊗ y = ∸ (x ⊗ y).Fact Zp_invertible_eq_zero x y : Zp_invertible x -> x ⊗ y = Zp -> y = Zp.Fact Zp_zero_is_one : Zp = Op <-> p = 1.Fact Zp_eq_dec : forall x y : Z_Zp, { x = y } + { x <> y }.Definition nat2Zp (u : nat) : Z_Zp.Fact nat2Zp_zero : 〚0〛= Zp.Fact nat2Zp_one : 〚1〛= Op.Fact nat2Zp_invertible n : Zp_invertible 〚n〛 <-> is_gcd p n 1.Fact nat2Zp_p :〚p〛= Zp.Fact nat2Zp_inj u v : 〚u〛=〚v〛 <-> rem u p = rem v p.Fact nat2Zp_surj u : { x | x < p /\ u = 〚x〛 }.Fact nat2Zp_plus u v : 〚u+v〛=〚u〛⊕〚v〛.Fact nat2Zp_minus u v : v <= u -> 〚u-v〛=〚u〛⊕ ∸〚v〛.Fact nat2Zp_minus_one : 〚p-1〛= ∸Op.Fact nat2Zp_mult u v :〚u*v〛=〚u〛⊗〚v〛.Fact nat2Zp_expo n u : 〚mscal mult 1%nat n u〛 = mscal Zp_mult Zp_one n 〚u〛.Fact nat2Zp_divides u v :〚u〛=〚v〛-> divides p u -> divides p v.Fact divides_nat2Zp u : divides p u <-> 〚u〛= Zp.Definition Zp_list := map nat2Zp (list_an 0 p).Fact Zp_list_spec x : In x Zp_list.Fact Zp_list_length : length Zp_list = p.Fact Zp_invertible_prime x : prime p -> Zp_invertible x <-> x <> Zp.Fact Zp_prime_integral x y : prime p -> x ⊗ y = Zp -> x = Zp \/ y = Zp.Fact Zp_prime_square_eq_square x y : prime p -> x⊗x = y⊗y <-> x = y \/ x = ∸y.Fact Zp_prime_square_one x : prime p -> x⊗x = Op <-> x = Op \/ x = ∸Op.Implicit Types u v w : Z.Definition Z2Zp u : Z_Zp.Definition Zp_invert x := proj1_sig (Zp_invert_full x).Fact Zp_invert_spec1 : Zp_invert Zp = Zp.Fact Zp_invert_spec2 x : x <> Zp -> Zp_invert x ⊗ x = Op.Notation inv := Zp_invert.Fact Zp_invert_eq_not_zero x y : x <> Zp -> inv x = y <-> y ⊗ x = Op.Fact Zp_invert_opp x : inv (∸x) = ∸(inv x).Fact Zp_invert_one : inv Op = Op.Fact Zp_invert_minus_one : inv (∸ Op) = ∸ Op.Fact Zp_invert_fix x : inv x = x <-> x = Zp \/ x = Op \/ x = ∸ Op.Fact Zp_invert_involutive x : inv (inv x) = x.Definition Zp_lprod := fold_right Zp_mult Zp_one.Fact Zp_lprod_nil : Zp_lprod nil = Op.Fact Zp_lprod_cons x l : Zp_lprod (x::l) = x ⊗ Zp_lprod l.Fact Zp_lprod_app l m : Zp_lprod (l++m) = Zp_lprod l ⊗  Zp_lprod m.

(* Specifications: *)
Theorem Zp_mult_autoinv l : ~ list_has_dup l -> (forall x, In x l -> x <> Zp /\ inv x <> x /\ In (inv x) l) -> Zp_lprod l = Op.