text: from unittest import TestCase
  
  
  class FieldElement:
      def __init__(self, num, prime):
          if num >= prime or num < 0:
              error = f"Num {num} not in field range 0 to {prime-1}"
              raise ValueError(error)
          self.num = num
          self.prime = prime
  
      def __eq__(self, other):
          if other is None:
              return False
          return self.num == other.num and self.prime == other.prime
  
      def __ne__(self, other):
          # this should be the inverse of the == operator
          return not (self == other)
  
      def __repr__(self):
          return f"FieldElement_{self.prime}({self.num})"
  
      def __add__(self, other):
          if self.prime != other.prime:
              raise TypeError("Cannot add two numbers in different Fields")
          # self.num and other.num are the actual values
          num = (self.num + other.num) % self.prime
          # self.prime is what you'll need to mod against
          prime = self.prime
          # You need to return an element of the same class
          # use: self.__class__(num, prime)
          return self.__class__(num, prime)
  
      def __sub__(self, other):
          if self.prime != other.prime:
              raise TypeError("Cannot add two numbers in different Fields")
          # self.num and other.num are the actual values
          num = (self.num - other.num) % self.prime
          # self.prime is what you'll need to mod against
          prime = self.prime
          # You need to return an element of the same class
          # use: self.__class__(num, prime)
          return self.__class__(num, prime)
  
      def __mul__(self, other):
          if self.prime != other.prime:
              raise TypeError("Cannot add two numbers in different Fields")
          # self.num and other.num are the actual values
          num = (self.num * other.num) % self.prime
          # self.prime is what you'll need to mod against
          prime = self.prime
          # You need to return an element of the same class
          # use: self.__class__(num, prime)
          return self.__class__(num, prime)
  
      def __pow__(self, n):
          # self.num is the base, n is the exponent
          # self.prime is what you'll need to mod against
          prime = self.prime
          # use pow(base, exponent, prime) to calculate the number
          num = pow(self.num, n, prime)
          # use: self.__class__(num, prime)
          return self.__class__(num, prime)
  
      def __truediv__(self, other):
          if self.prime != other.prime:
              raise TypeError("Cannot add two numbers in different Fields")
          # self.num and other.num are the actual values
          num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime
          # self.prime is what you'll need to mod against
          prime = self.prime
          # use fermat's little theorem:
          # self.num**(p-1) % p == 1
          # this means:
          # 1/n == pow(n, p-2, p)
          # You need to return an element of the same class
          # use: self.__class__(num, prime)
          return self.__class__(num, prime)
  
  
  class FieldElementTest(TestCase):
      def test_ne(self):
          a = FieldElement(2, 31)
          b = FieldElement(2, 31)
          c = FieldElement(15, 31)
          self.assertEqual(a, b)
          self.assertTrue(a != c)
          self.assertFalse(a != b)
  
      def test_add(self):
          a = FieldElement(2, 31)
          b = FieldElement(15, 31)
          self.assertEqual(a + b, FieldElement(17, 31))
          a = FieldElement(17, 31)
          b = FieldElement(21, 31)
          self.assertEqual(a + b, FieldElement(7, 31))
  
      def test_sub(self):
          a = FieldElement(29, 31)
          b = FieldElement(4, 31)
          self.assertEqual(a - b, FieldElement(25, 31))
          a = FieldElement(15, 31)
          b = FieldElement(30, 31)
          self.assertEqual(a - b, FieldElement(16, 31))
  
      def test_mul(self):
          a = FieldElement(24, 31)
          b = FieldElement(19, 31)
          self.assertEqual(a * b, FieldElement(22, 31))
  
      def test_pow(self):
          a = FieldElement(17, 31)
          self.assertEqual(a**3, FieldElement(15, 31))
          a = FieldElement(5, 31)
          b = FieldElement(18, 31)
          self.assertEqual(a**5 * b, FieldElement(16, 31))
  
      def test_div(self):
          a = FieldElement(3, 31)
          b = FieldElement(24, 31)
          self.assertEqual(a / b, FieldElement(4, 31))
          a = FieldElement(17, 31)
          self.assertEqual(a**-3, FieldElement(29, 31))
          a = FieldElement(4, 31)
          b = FieldElement(11, 31)
          self.assertEqual(a**-4 * b, FieldElement(13, 31))
  
  
  class Point:
      def __init__(self, x, y, a, b):
          self.a = a
          self.b = b
          self.x = x
          self.y = y
          # Exercise 13: x being None and y being None represents the point at infinity
          # Exercise 13: Check for that here since the equation below won't make sense
          # Exercise 13: with None values for both.
          if self.x is None and self.y is None:
              return
          # Exercise 12: make sure that the elliptic curve equation is satisfied
          # y**2 == x**3 + a*x + b
          if self.y**2 != self.x**3 + a * x + b:
              # if not, raise a ValueError
              raise ValueError(f"({self.x}, {self.y}) is not on the curve")
  
      def __eq__(self, other):
          return (
              self.x == other.x
              and self.y == other.y
              and self.a == other.a
              and self.b == other.b
          )
  
      def __ne__(self, other):
          # this should be the inverse of the == operator
          return not (self == other)
  
      def __repr__(self):
          if self.x is None:
              return "Point(infinity)"
          else:
              return f"Point({self.x.num},{self.y.num})_{self.x.prime}"
  
      def __add__(self, other):
          if self.a != other.a or self.b != other.b:
              raise TypeError(f"Points {self}, {other} are not on the same curve")
          # Case 0.0: self is the point at infinity, return other
          if self.x is None:
              return other
          # Case 0.1: other is the point at infinity, return self
          if other.x is None:
              return self
  
          # Case 1: self.x == other.x, self.y != other.y
          # Result is point at infinity
          if self.x == other.x and self.y != other.y:
              # Remember to return an instance of this class:
              # self.__class__(x, y, a, b)
              return self.__class__(None, None, self.a, self.b)
  
          # Case 2: self.x != other.x
          if self.x != other.x:
              # Formula (x3,y3)==(x1,y1)+(x2,y2)
              # s=(y2-y1)/(x2-x1)
              s = (other.y - self.y) / (other.x - self.x)
              # x3=s**2-x1-x2
              x = s**2 - self.x - other.x
              # y3=s*(x1-x3)-y1
              y = s * (self.x - x) - self.y
              # use: self.__class__(x, y, self.a, self.b)
              return self.__class__(x, y, self.a, self.b)
  
          # Case 3: self == other
          else:
              # Formula (x3,y3)=(x1,y1)+(x1,y1)
              # s=(3*x1**2+a)/(2*y1)
              s = (3 * self.x**2 + self.a) / (2 * self.y)
              # x3=s**2-2*x1
              x = s**2 - 2 * self.x
              # y3=s*(x1-x3)-y1
              y = s * (self.x - x) - self.y
              # use: self.__class__(x, y, self.a, self.b)
              return self.__class__(x, y, self.a, self.b)
  
  
  class PointTest(TestCase):
      def test_ne(self):
          a = Point(x=3, y=-7, a=5, b=7)
          b = Point(x=18, y=77, a=5, b=7)
          self.assertTrue(a != b)
          self.assertFalse(a != a)
  
      def test_on_curve(self):
          with self.assertRaises(ValueError):
              Point(x=-2, y=4, a=5, b=7)
          # these should not raise an error
          Point(x=3, y=-7, a=5, b=7)
          Point(x=18, y=77, a=5, b=7)
  
      def test_add0(self):
          a = Point(x=None, y=None, a=5, b=7)
          b = Point(x=2, y=5, a=5, b=7)
          c = Point(x=2, y=-5, a=5, b=7)
          self.assertEqual(a + b, b)
          self.assertEqual(b + a, b)
          self.assertEqual(b + c, a)
  
      def test_add1(self):
          a = Point(x=3, y=7, a=5, b=7)
          b = Point(x=-1, y=-1, a=5, b=7)
          self.assertEqual(a + b, Point(x=2, y=-5, a=5, b=7))
  
      def test_add2(self):
          a = Point(x=-1, y=1, a=5, b=7)
          self.assertEqual(a + a, Point(x=18, y=-77, a=5, b=7))
  