elliptic_curve.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. from sympy.core.numbers import oo
  2. from sympy.core.relational import Eq
  3. from sympy.core.symbol import symbols
  4. from sympy.polys.domains import FiniteField, QQ, RationalField, FF
  5. from sympy.solvers.solvers import solve
  6. from sympy.utilities.iterables import is_sequence
  7. from sympy.utilities.misc import as_int
  8. from .factor_ import divisors
  9. from .residue_ntheory import polynomial_congruence
  10. class EllipticCurve:
  11. """
  12. Create the following Elliptic Curve over domain.
  13. `y^{2} + a_{1} x y + a_{3} y = x^{3} + a_{2} x^{2} + a_{4} x + a_{6}`
  14. The default domain is ``QQ``. If no coefficient ``a1``, ``a2``, ``a3``,
  15. it create curve as following form.
  16. `y^{2} = x^{3} + a_{4} x + a_{6}`
  17. Examples
  18. ========
  19. References
  20. ==========
  21. .. [1] J. Silverman "A Friendly Introduction to Number Theory" Third Edition
  22. .. [2] https://mathworld.wolfram.com/EllipticDiscriminant.html
  23. .. [3] G. Hardy, E. Wright "An Introduction to the Theory of Numbers" Sixth Edition
  24. """
  25. def __init__(self, a4, a6, a1=0, a2=0, a3=0, modulus = 0):
  26. if modulus == 0:
  27. domain = QQ
  28. else:
  29. domain = FF(modulus)
  30. a1, a2, a3, a4, a6 = map(domain.convert, (a1, a2, a3, a4, a6))
  31. self._domain = domain
  32. self.modulus = modulus
  33. # Calculate discriminant
  34. b2 = a1**2 + 4 * a2
  35. b4 = 2 * a4 + a1 * a3
  36. b6 = a3**2 + 4 * a6
  37. b8 = a1**2 * a6 + 4 * a2 * a6 - a1 * a3 * a4 + a2 * a3**2 - a4**2
  38. self._b2, self._b4, self._b6, self._b8 = b2, b4, b6, b8
  39. self._discrim = -b2**2 * b8 - 8 * b4**3 - 27 * b6**2 + 9 * b2 * b4 * b6
  40. self._a1 = a1
  41. self._a2 = a2
  42. self._a3 = a3
  43. self._a4 = a4
  44. self._a6 = a6
  45. x, y, z = symbols('x y z')
  46. self.x, self.y, self.z = x, y, z
  47. self._eq = Eq(y**2*z + a1*x*y*z + a3*y*z**2, x**3 + a2*x**2*z + a4*x*z**2 + a6*z**3)
  48. if isinstance(self._domain, FiniteField):
  49. self._rank = 0
  50. elif isinstance(self._domain, RationalField):
  51. self._rank = None
  52. def __call__(self, x, y, z=1):
  53. return EllipticCurvePoint(x, y, z, self)
  54. def __contains__(self, point):
  55. if is_sequence(point):
  56. if len(point) == 2:
  57. z1 = 1
  58. else:
  59. z1 = point[2]
  60. x1, y1 = point[:2]
  61. elif isinstance(point, EllipticCurvePoint):
  62. x1, y1, z1 = point.x, point.y, point.z
  63. else:
  64. raise ValueError('Invalid point.')
  65. if self.characteristic == 0 and z1 == 0:
  66. return True
  67. return self._eq.subs({self.x: x1, self.y: y1, self.z: z1})
  68. def __repr__(self):
  69. return 'E({}): {}'.format(self._domain, self._eq)
  70. def minimal(self):
  71. """
  72. Return minimal Weierstrass equation.
  73. Examples
  74. ========
  75. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  76. >>> e1 = EllipticCurve(-10, -20, 0, -1, 1)
  77. >>> e1.minimal()
  78. E(QQ): Eq(y**2*z, x**3 - 13392*x*z**2 - 1080432*z**3)
  79. """
  80. char = self.characteristic
  81. if char == 2:
  82. return self
  83. if char == 3:
  84. return EllipticCurve(self._b4/2, self._b6/4, a2=self._b2/4, modulus=self.modulus)
  85. c4 = self._b2**2 - 24*self._b4
  86. c6 = -self._b2**3 + 36*self._b2*self._b4 - 216*self._b6
  87. return EllipticCurve(-27*c4, -54*c6, modulus=self.modulus)
  88. def points(self):
  89. """
  90. Return points of curve over Finite Field.
  91. Examples
  92. ========
  93. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  94. >>> e2 = EllipticCurve(1, 1, 1, 1, 1, modulus=5)
  95. >>> e2.points()
  96. {(0, 2), (1, 4), (2, 0), (2, 2), (3, 0), (3, 1), (4, 0)}
  97. """
  98. char = self.characteristic
  99. all_pt = set()
  100. if char >= 1:
  101. for i in range(char):
  102. congruence_eq = ((self._eq.lhs - self._eq.rhs).subs({self.x: i, self.z: 1}))
  103. sol = polynomial_congruence(congruence_eq, char)
  104. for num in sol:
  105. all_pt.add((i, num))
  106. return all_pt
  107. else:
  108. raise ValueError("Infinitely many points")
  109. def points_x(self, x):
  110. "Returns points on with curve where xcoordinate = x"
  111. pt = []
  112. if self._domain == QQ:
  113. for y in solve(self._eq.subs(self.x, x)):
  114. pt.append((x, y))
  115. congruence_eq = ((self._eq.lhs - self._eq.rhs).subs({self.x: x, self.z: 1}))
  116. for y in polynomial_congruence(congruence_eq, self.characteristic):
  117. pt.append((x, y))
  118. return pt
  119. def torsion_points(self):
  120. """
  121. Return torsion points of curve over Rational number.
  122. Return point objects those are finite order.
  123. According to Nagell-Lutz theorem, torsion point p(x, y)
  124. x and y are integers, either y = 0 or y**2 is divisor
  125. of discriminent. According to Mazur's theorem, there are
  126. at most 15 points in torsion collection.
  127. Examples
  128. ========
  129. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  130. >>> e2 = EllipticCurve(-43, 166)
  131. >>> sorted(e2.torsion_points())
  132. [(-5, -16), (-5, 16), O, (3, -8), (3, 8), (11, -32), (11, 32)]
  133. """
  134. if self.characteristic > 0:
  135. raise ValueError("No torsion point for Finite Field.")
  136. l = [EllipticCurvePoint.point_at_infinity(self)]
  137. for xx in solve(self._eq.subs({self.y: 0, self.z: 1})):
  138. if xx.is_rational:
  139. l.append(self(xx, 0))
  140. for i in divisors(self.discriminant, generator=True):
  141. j = int(i**.5)
  142. if j**2 == i:
  143. for xx in solve(self._eq.subs({self.y: j, self.z: 1})):
  144. if not xx.is_rational:
  145. continue
  146. p = self(xx, j)
  147. if p.order() != oo:
  148. l.extend([p, -p])
  149. return l
  150. @property
  151. def characteristic(self):
  152. """
  153. Return domain characteristic.
  154. Examples
  155. ========
  156. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  157. >>> e2 = EllipticCurve(-43, 166)
  158. >>> e2.characteristic
  159. 0
  160. """
  161. return self._domain.characteristic()
  162. @property
  163. def discriminant(self):
  164. """
  165. Return curve discriminant.
  166. Examples
  167. ========
  168. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  169. >>> e2 = EllipticCurve(0, 17)
  170. >>> e2.discriminant
  171. -124848
  172. """
  173. return int(self._discrim)
  174. @property
  175. def is_singular(self):
  176. """
  177. Return True if curve discriminant is equal to zero.
  178. """
  179. return self.discriminant == 0
  180. @property
  181. def j_invariant(self):
  182. """
  183. Return curve j-invariant.
  184. Examples
  185. ========
  186. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  187. >>> e1 = EllipticCurve(-2, 0, 0, 1, 1)
  188. >>> e1.j_invariant
  189. 1404928/389
  190. """
  191. c4 = self._b2**2 - 24*self._b4
  192. return self._domain.to_sympy(c4**3 / self._discrim)
  193. @property
  194. def order(self):
  195. """
  196. Number of points in Finite field.
  197. Examples
  198. ========
  199. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  200. >>> e2 = EllipticCurve(1, 0, modulus=19)
  201. >>> e2.order
  202. 19
  203. """
  204. if self.characteristic == 0:
  205. raise NotImplementedError("Still not implemented")
  206. return len(self.points())
  207. @property
  208. def rank(self):
  209. """
  210. Number of independent points of infinite order.
  211. For Finite field, it must be 0.
  212. """
  213. if self._rank is not None:
  214. return self._rank
  215. raise NotImplementedError("Still not implemented")
  216. class EllipticCurvePoint:
  217. """
  218. Point of Elliptic Curve
  219. Examples
  220. ========
  221. >>> from sympy.ntheory.elliptic_curve import EllipticCurve
  222. >>> e1 = EllipticCurve(-17, 16)
  223. >>> p1 = e1(0, -4, 1)
  224. >>> p2 = e1(1, 0)
  225. >>> p1 + p2
  226. (15, -56)
  227. >>> e3 = EllipticCurve(-1, 9)
  228. >>> e3(1, -3) * 3
  229. (664/169, 17811/2197)
  230. >>> (e3(1, -3) * 3).order()
  231. oo
  232. >>> e2 = EllipticCurve(-2, 0, 0, 1, 1)
  233. >>> p = e2(-1,1)
  234. >>> q = e2(0, -1)
  235. >>> p+q
  236. (4, 8)
  237. >>> p-q
  238. (1, 0)
  239. >>> 3*p-5*q
  240. (328/361, -2800/6859)
  241. """
  242. @staticmethod
  243. def point_at_infinity(curve):
  244. return EllipticCurvePoint(0, 1, 0, curve)
  245. def __init__(self, x, y, z, curve):
  246. dom = curve._domain.convert
  247. self.x = dom(x)
  248. self.y = dom(y)
  249. self.z = dom(z)
  250. self._curve = curve
  251. self._domain = self._curve._domain
  252. if not self._curve.__contains__(self):
  253. raise ValueError("The curve does not contain this point")
  254. def __add__(self, p):
  255. if self.z == 0:
  256. return p
  257. if p.z == 0:
  258. return self
  259. x1, y1 = self.x/self.z, self.y/self.z
  260. x2, y2 = p.x/p.z, p.y/p.z
  261. a1 = self._curve._a1
  262. a2 = self._curve._a2
  263. a3 = self._curve._a3
  264. a4 = self._curve._a4
  265. a6 = self._curve._a6
  266. if x1 != x2:
  267. slope = (y1 - y2) / (x1 - x2)
  268. yint = (y1 * x2 - y2 * x1) / (x2 - x1)
  269. else:
  270. if (y1 + y2) == 0:
  271. return self.point_at_infinity(self._curve)
  272. slope = (3 * x1**2 + 2*a2*x1 + a4 - a1*y1) / (a1 * x1 + a3 + 2 * y1)
  273. yint = (-x1**3 + a4*x1 + 2*a6 - a3*y1) / (a1*x1 + a3 + 2*y1)
  274. x3 = slope**2 + a1*slope - a2 - x1 - x2
  275. y3 = -(slope + a1) * x3 - yint - a3
  276. return self._curve(x3, y3, 1)
  277. def __lt__(self, other):
  278. return (self.x, self.y, self.z) < (other.x, other.y, other.z)
  279. def __mul__(self, n):
  280. n = as_int(n)
  281. r = self.point_at_infinity(self._curve)
  282. if n == 0:
  283. return r
  284. if n < 0:
  285. return -self * -n
  286. p = self
  287. while n:
  288. if n & 1:
  289. r = r + p
  290. n >>= 1
  291. p = p + p
  292. return r
  293. def __rmul__(self, n):
  294. return self * n
  295. def __neg__(self):
  296. return EllipticCurvePoint(self.x, -self.y - self._curve._a1*self.x - self._curve._a3, self.z, self._curve)
  297. def __repr__(self):
  298. if self.z == 0:
  299. return 'O'
  300. dom = self._curve._domain
  301. try:
  302. return '({}, {})'.format(dom.to_sympy(self.x), dom.to_sympy(self.y))
  303. except TypeError:
  304. pass
  305. return '({}, {})'.format(self.x, self.y)
  306. def __sub__(self, other):
  307. return self.__add__(-other)
  308. def order(self):
  309. """
  310. Return point order n where nP = 0.
  311. """
  312. if self.z == 0:
  313. return 1
  314. if self.y == 0: # P = -P
  315. return 2
  316. p = self * 2
  317. if p.y == -self.y: # 2P = -P
  318. return 3
  319. i = 2
  320. if self._domain != QQ:
  321. while int(p.x) == p.x and int(p.y) == p.y:
  322. p = self + p
  323. i += 1
  324. if p.z == 0:
  325. return i
  326. return oo
  327. while p.x.numerator == p.x and p.y.numerator == p.y:
  328. p = self + p
  329. i += 1
  330. if i > 12:
  331. return oo
  332. if p.z == 0:
  333. return i
  334. return oo