code: def generate_public_key(self):
          
          private_key = int(self.private_key, 16)
          if private_key >= self.N:
              raise Exception('Invalid private key.')
  
          G = JacobianPoint(self.Gx, self.Gy, 1)
          public_key = G * private_key
  
          x_hex = '{0:0{1}x}'.format(public_key.X, 64)
          y_hex = '{0:0{1}x}'.format(public_key.Y, 64)
          return '04' + x_hex + y_hex
docs: Generates a public key from the hex-encoded private key using elliptic
          curve cryptography. The private key is multiplied by a predetermined point
          on the elliptic curve called the generator point, G, resulting in the
          corresponding private key. The generator point is always the same for all
          Bitcoin users.
  
          Jacobian coordinates are used to represent the elliptic curve point G.
          https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
  
          The exponentiating by squaring (also known by double-and-add) method is
          used for the elliptic curve multiplication that results in the public key.
          https://en.wikipedia.org/wiki/Exponentiation_by_squaring
  
          Bitcoin public keys are 65 bytes. The first byte is 0x04, next 32
          bytes correspond to the X coordinate, and last 32 bytes correspond
          to the Y coordinate. They are typically encoded as 130-length hex
          characters.
  
          Args:
              private_key (bytes): UTF-8 encoded hexadecimal
          Returns:
              str: The public key in hexadecimal representation.