You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
11 KiB

5 months ago
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