399 lines
11 KiB
Python
399 lines
11 KiB
Python
|
from sympy.core.numbers import oo
|
||
|
from sympy.core.relational import Eq
|
||
|
from sympy.core.symbol import symbols
|
||
|
from sympy.polys.domains import FiniteField, QQ, RationalField, FF
|
||
|
from sympy.solvers.solvers import solve
|
||
|
from sympy.utilities.iterables import is_sequence
|
||
|
from sympy.utilities.misc import as_int
|
||
|
from .factor_ import divisors
|
||
|
from .residue_ntheory import polynomial_congruence
|
||
|
|
||
|
|
||
|
|
||
|
class EllipticCurve:
|
||
|
"""
|
||
|
Create the following Elliptic Curve over domain.
|
||
|
|
||
|
`y^{2} + a_{1} x y + a_{3} y = x^{3} + a_{2} x^{2} + a_{4} x + a_{6}`
|
||
|
|
||
|
The default domain is ``QQ``. If no coefficient ``a1``, ``a2``, ``a3``,
|
||
|
it create curve as following form.
|
||
|
|
||
|
`y^{2} = x^{3} + a_{4} x + a_{6}`
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] J. Silverman "A Friendly Introduction to Number Theory" Third Edition
|
||
|
.. [2] https://mathworld.wolfram.com/EllipticDiscriminant.html
|
||
|
.. [3] G. Hardy, E. Wright "An Introduction to the Theory of Numbers" Sixth Edition
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, a4, a6, a1=0, a2=0, a3=0, modulus = 0):
|
||
|
if modulus == 0:
|
||
|
domain = QQ
|
||
|
else:
|
||
|
domain = FF(modulus)
|
||
|
a1, a2, a3, a4, a6 = map(domain.convert, (a1, a2, a3, a4, a6))
|
||
|
self._domain = domain
|
||
|
self.modulus = modulus
|
||
|
# Calculate discriminant
|
||
|
b2 = a1**2 + 4 * a2
|
||
|
b4 = 2 * a4 + a1 * a3
|
||
|
b6 = a3**2 + 4 * a6
|
||
|
b8 = a1**2 * a6 + 4 * a2 * a6 - a1 * a3 * a4 + a2 * a3**2 - a4**2
|
||
|
self._b2, self._b4, self._b6, self._b8 = b2, b4, b6, b8
|
||
|
self._discrim = -b2**2 * b8 - 8 * b4**3 - 27 * b6**2 + 9 * b2 * b4 * b6
|
||
|
self._a1 = a1
|
||
|
self._a2 = a2
|
||
|
self._a3 = a3
|
||
|
self._a4 = a4
|
||
|
self._a6 = a6
|
||
|
x, y, z = symbols('x y z')
|
||
|
self.x, self.y, self.z = x, y, z
|
||
|
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)
|
||
|
if isinstance(self._domain, FiniteField):
|
||
|
self._rank = 0
|
||
|
elif isinstance(self._domain, RationalField):
|
||
|
self._rank = None
|
||
|
|
||
|
def __call__(self, x, y, z=1):
|
||
|
return EllipticCurvePoint(x, y, z, self)
|
||
|
|
||
|
def __contains__(self, point):
|
||
|
if is_sequence(point):
|
||
|
if len(point) == 2:
|
||
|
z1 = 1
|
||
|
else:
|
||
|
z1 = point[2]
|
||
|
x1, y1 = point[:2]
|
||
|
elif isinstance(point, EllipticCurvePoint):
|
||
|
x1, y1, z1 = point.x, point.y, point.z
|
||
|
else:
|
||
|
raise ValueError('Invalid point.')
|
||
|
if self.characteristic == 0 and z1 == 0:
|
||
|
return True
|
||
|
return self._eq.subs({self.x: x1, self.y: y1, self.z: z1})
|
||
|
|
||
|
def __repr__(self):
|
||
|
return 'E({}): {}'.format(self._domain, self._eq)
|
||
|
|
||
|
def minimal(self):
|
||
|
"""
|
||
|
Return minimal Weierstrass equation.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
|
||
|
>>> e1 = EllipticCurve(-10, -20, 0, -1, 1)
|
||
|
>>> e1.minimal()
|
||
|
E(QQ): Eq(y**2*z, x**3 - 13392*x*z**2 - 1080432*z**3)
|
||
|
|
||
|
"""
|
||
|
char = self.characteristic
|
||
|
if char == 2:
|
||
|
return self
|
||
|
if char == 3:
|
||
|
return EllipticCurve(self._b4/2, self._b6/4, a2=self._b2/4, modulus=self.modulus)
|
||
|
c4 = self._b2**2 - 24*self._b4
|
||
|
c6 = -self._b2**3 + 36*self._b2*self._b4 - 216*self._b6
|
||
|
return EllipticCurve(-27*c4, -54*c6, modulus=self.modulus)
|
||
|
|
||
|
def points(self):
|
||
|
"""
|
||
|
Return points of curve over Finite Field.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e2 = EllipticCurve(1, 1, 1, 1, 1, modulus=5)
|
||
|
>>> e2.points()
|
||
|
{(0, 2), (1, 4), (2, 0), (2, 2), (3, 0), (3, 1), (4, 0)}
|
||
|
|
||
|
"""
|
||
|
|
||
|
char = self.characteristic
|
||
|
all_pt = set()
|
||
|
if char >= 1:
|
||
|
for i in range(char):
|
||
|
congruence_eq = ((self._eq.lhs - self._eq.rhs).subs({self.x: i, self.z: 1}))
|
||
|
sol = polynomial_congruence(congruence_eq, char)
|
||
|
for num in sol:
|
||
|
all_pt.add((i, num))
|
||
|
return all_pt
|
||
|
else:
|
||
|
raise ValueError("Infinitely many points")
|
||
|
|
||
|
def points_x(self, x):
|
||
|
"Returns points on with curve where xcoordinate = x"
|
||
|
pt = []
|
||
|
if self._domain == QQ:
|
||
|
for y in solve(self._eq.subs(self.x, x)):
|
||
|
pt.append((x, y))
|
||
|
congruence_eq = ((self._eq.lhs - self._eq.rhs).subs({self.x: x, self.z: 1}))
|
||
|
for y in polynomial_congruence(congruence_eq, self.characteristic):
|
||
|
pt.append((x, y))
|
||
|
return pt
|
||
|
|
||
|
def torsion_points(self):
|
||
|
"""
|
||
|
Return torsion points of curve over Rational number.
|
||
|
|
||
|
Return point objects those are finite order.
|
||
|
According to Nagell-Lutz theorem, torsion point p(x, y)
|
||
|
x and y are integers, either y = 0 or y**2 is divisor
|
||
|
of discriminent. According to Mazur's theorem, there are
|
||
|
at most 15 points in torsion collection.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e2 = EllipticCurve(-43, 166)
|
||
|
>>> sorted(e2.torsion_points())
|
||
|
[(-5, -16), (-5, 16), O, (3, -8), (3, 8), (11, -32), (11, 32)]
|
||
|
|
||
|
"""
|
||
|
if self.characteristic > 0:
|
||
|
raise ValueError("No torsion point for Finite Field.")
|
||
|
l = [EllipticCurvePoint.point_at_infinity(self)]
|
||
|
for xx in solve(self._eq.subs({self.y: 0, self.z: 1})):
|
||
|
if xx.is_rational:
|
||
|
l.append(self(xx, 0))
|
||
|
for i in divisors(self.discriminant, generator=True):
|
||
|
j = int(i**.5)
|
||
|
if j**2 == i:
|
||
|
for xx in solve(self._eq.subs({self.y: j, self.z: 1})):
|
||
|
if not xx.is_rational:
|
||
|
continue
|
||
|
p = self(xx, j)
|
||
|
if p.order() != oo:
|
||
|
l.extend([p, -p])
|
||
|
return l
|
||
|
|
||
|
@property
|
||
|
def characteristic(self):
|
||
|
"""
|
||
|
Return domain characteristic.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e2 = EllipticCurve(-43, 166)
|
||
|
>>> e2.characteristic
|
||
|
0
|
||
|
|
||
|
"""
|
||
|
return self._domain.characteristic()
|
||
|
|
||
|
@property
|
||
|
def discriminant(self):
|
||
|
"""
|
||
|
Return curve discriminant.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e2 = EllipticCurve(0, 17)
|
||
|
>>> e2.discriminant
|
||
|
-124848
|
||
|
|
||
|
"""
|
||
|
return int(self._discrim)
|
||
|
|
||
|
@property
|
||
|
def is_singular(self):
|
||
|
"""
|
||
|
Return True if curve discriminant is equal to zero.
|
||
|
"""
|
||
|
return self.discriminant == 0
|
||
|
|
||
|
@property
|
||
|
def j_invariant(self):
|
||
|
"""
|
||
|
Return curve j-invariant.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e1 = EllipticCurve(-2, 0, 0, 1, 1)
|
||
|
>>> e1.j_invariant
|
||
|
1404928/389
|
||
|
|
||
|
"""
|
||
|
c4 = self._b2**2 - 24*self._b4
|
||
|
return self._domain.to_sympy(c4**3 / self._discrim)
|
||
|
|
||
|
@property
|
||
|
def order(self):
|
||
|
"""
|
||
|
Number of points in Finite field.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e2 = EllipticCurve(1, 0, modulus=19)
|
||
|
>>> e2.order
|
||
|
19
|
||
|
|
||
|
"""
|
||
|
if self.characteristic == 0:
|
||
|
raise NotImplementedError("Still not implemented")
|
||
|
return len(self.points())
|
||
|
|
||
|
@property
|
||
|
def rank(self):
|
||
|
"""
|
||
|
Number of independent points of infinite order.
|
||
|
|
||
|
For Finite field, it must be 0.
|
||
|
"""
|
||
|
if self._rank is not None:
|
||
|
return self._rank
|
||
|
raise NotImplementedError("Still not implemented")
|
||
|
|
||
|
|
||
|
class EllipticCurvePoint:
|
||
|
"""
|
||
|
Point of Elliptic Curve
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.ntheory.elliptic_curve import EllipticCurve
|
||
|
>>> e1 = EllipticCurve(-17, 16)
|
||
|
>>> p1 = e1(0, -4, 1)
|
||
|
>>> p2 = e1(1, 0)
|
||
|
>>> p1 + p2
|
||
|
(15, -56)
|
||
|
>>> e3 = EllipticCurve(-1, 9)
|
||
|
>>> e3(1, -3) * 3
|
||
|
(664/169, 17811/2197)
|
||
|
>>> (e3(1, -3) * 3).order()
|
||
|
oo
|
||
|
>>> e2 = EllipticCurve(-2, 0, 0, 1, 1)
|
||
|
>>> p = e2(-1,1)
|
||
|
>>> q = e2(0, -1)
|
||
|
>>> p+q
|
||
|
(4, 8)
|
||
|
>>> p-q
|
||
|
(1, 0)
|
||
|
>>> 3*p-5*q
|
||
|
(328/361, -2800/6859)
|
||
|
"""
|
||
|
|
||
|
@staticmethod
|
||
|
def point_at_infinity(curve):
|
||
|
return EllipticCurvePoint(0, 1, 0, curve)
|
||
|
|
||
|
def __init__(self, x, y, z, curve):
|
||
|
dom = curve._domain.convert
|
||
|
self.x = dom(x)
|
||
|
self.y = dom(y)
|
||
|
self.z = dom(z)
|
||
|
self._curve = curve
|
||
|
self._domain = self._curve._domain
|
||
|
if not self._curve.__contains__(self):
|
||
|
raise ValueError("The curve does not contain this point")
|
||
|
|
||
|
def __add__(self, p):
|
||
|
if self.z == 0:
|
||
|
return p
|
||
|
if p.z == 0:
|
||
|
return self
|
||
|
x1, y1 = self.x/self.z, self.y/self.z
|
||
|
x2, y2 = p.x/p.z, p.y/p.z
|
||
|
a1 = self._curve._a1
|
||
|
a2 = self._curve._a2
|
||
|
a3 = self._curve._a3
|
||
|
a4 = self._curve._a4
|
||
|
a6 = self._curve._a6
|
||
|
if x1 != x2:
|
||
|
slope = (y1 - y2) / (x1 - x2)
|
||
|
yint = (y1 * x2 - y2 * x1) / (x2 - x1)
|
||
|
else:
|
||
|
if (y1 + y2) == 0:
|
||
|
return self.point_at_infinity(self._curve)
|
||
|
slope = (3 * x1**2 + 2*a2*x1 + a4 - a1*y1) / (a1 * x1 + a3 + 2 * y1)
|
||
|
yint = (-x1**3 + a4*x1 + 2*a6 - a3*y1) / (a1*x1 + a3 + 2*y1)
|
||
|
x3 = slope**2 + a1*slope - a2 - x1 - x2
|
||
|
y3 = -(slope + a1) * x3 - yint - a3
|
||
|
return self._curve(x3, y3, 1)
|
||
|
|
||
|
def __lt__(self, other):
|
||
|
return (self.x, self.y, self.z) < (other.x, other.y, other.z)
|
||
|
|
||
|
def __mul__(self, n):
|
||
|
n = as_int(n)
|
||
|
r = self.point_at_infinity(self._curve)
|
||
|
if n == 0:
|
||
|
return r
|
||
|
if n < 0:
|
||
|
return -self * -n
|
||
|
p = self
|
||
|
while n:
|
||
|
if n & 1:
|
||
|
r = r + p
|
||
|
n >>= 1
|
||
|
p = p + p
|
||
|
return r
|
||
|
|
||
|
def __rmul__(self, n):
|
||
|
return self * n
|
||
|
|
||
|
def __neg__(self):
|
||
|
return EllipticCurvePoint(self.x, -self.y - self._curve._a1*self.x - self._curve._a3, self.z, self._curve)
|
||
|
|
||
|
def __repr__(self):
|
||
|
if self.z == 0:
|
||
|
return 'O'
|
||
|
dom = self._curve._domain
|
||
|
try:
|
||
|
return '({}, {})'.format(dom.to_sympy(self.x), dom.to_sympy(self.y))
|
||
|
except TypeError:
|
||
|
pass
|
||
|
return '({}, {})'.format(self.x, self.y)
|
||
|
|
||
|
def __sub__(self, other):
|
||
|
return self.__add__(-other)
|
||
|
|
||
|
def order(self):
|
||
|
"""
|
||
|
Return point order n where nP = 0.
|
||
|
|
||
|
"""
|
||
|
if self.z == 0:
|
||
|
return 1
|
||
|
if self.y == 0: # P = -P
|
||
|
return 2
|
||
|
p = self * 2
|
||
|
if p.y == -self.y: # 2P = -P
|
||
|
return 3
|
||
|
i = 2
|
||
|
if self._domain != QQ:
|
||
|
while int(p.x) == p.x and int(p.y) == p.y:
|
||
|
p = self + p
|
||
|
i += 1
|
||
|
if p.z == 0:
|
||
|
return i
|
||
|
return oo
|
||
|
while p.x.numerator == p.x and p.y.numerator == p.y:
|
||
|
p = self + p
|
||
|
i += 1
|
||
|
if i > 12:
|
||
|
return oo
|
||
|
if p.z == 0:
|
||
|
return i
|
||
|
return oo
|