Skip to content

Commit 1646859

Browse files
author
Release Manager
committed
Trac #32744: composite elliptic-curve isogenies
This patch introduces two important improvements for isogeny-minded users: 1. Implement `EllipticCurveHom_composite`, a type of elliptic-curve morphism that's fundamentally a formal composite map, but designed to behave almost exactly like `EllipticCurveIsogeny`. Leaving the isogeny decomposed can be exponentially more efficient in some scenarios (such as in cryptography). 2. Implement "factored" isogenies, that is, construction of an isogeny from a smooth-order kernel subgroup in time logarithmic in the degree (whereas `EllipticCurveIsogeny` is linear in the degree). The new code is marked as experimental and not used anywhere by default, but adventurous users can opt-in by calling `EllipticCurveHom_composite.make_default()` or passing `algorithm="factored"` to `E.isogeny()`. Changes to the existing codebase are intentionally kept minimal. Here's a diff without the dependency: - https://git.sagemath.org/sage.git/diff?id2=444330c857ee57a22e7e7e974cb 63478b1535398&id=72ab0b8cdc18b15ea893a3db914419631252de23 Most of the algorithms are straightforward, except perhaps for equality testing: This relies on the fact that distinct isogenies cannot act the same on "too many" points (the bound is four times the degree). See also #31850. URL: https://trac.sagemath.org/32744 Reported by: lorenz Ticket author(s): Lorenz Panny Reviewer(s): John Cremona
2 parents 9a48a8a + 72ab0b8 commit 1646859

File tree

8 files changed

+1038
-38
lines changed

8 files changed

+1038
-38
lines changed

build/pkgs/configure/checksums.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
tarball=configure-VERSION.tar.gz
2-
sha1=90c6a61858cddd0d4e3b65b05d899c742356a82e
3-
md5=eddfeb607a50616c02ca39dbfaae91a5
4-
cksum=3731009361
2+
sha1=b0c428abfc3936a2418e105844b43e5a28b386ef
3+
md5=d9736cf1bb521bb49edfb60b056e652e
4+
cksum=2428638712
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
464e8c3d08cd20837a05fc6afa7386f89a309133
1+
f467c7383e24dfb6089500c5704298a404f3d546

src/doc/en/reference/arithmetic_curves/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Maps between them
2121
sage/schemes/elliptic_curves/weierstrass_morphism
2222
sage/schemes/elliptic_curves/ell_curve_isogeny
2323
sage/schemes/elliptic_curves/isogeny_small_degree
24+
sage/schemes/elliptic_curves/hom_composite
2425

2526

2627
Elliptic curves over number fields

src/sage/schemes/elliptic_curves/ell_curve_isogeny.py

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565

6666
from copy import copy
6767

68+
from sage.structure.sequence import Sequence
69+
6870
from sage.schemes.elliptic_curves.hom import EllipticCurveHom
6971

7072
from sage.rings.all import PolynomialRing, Integer, LaurentSeriesRing
@@ -74,7 +76,8 @@
7476

7577
from sage.rings.number_field.number_field_base import is_NumberField
7678

77-
from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism, isomorphisms
79+
from sage.schemes.elliptic_curves.weierstrass_morphism \
80+
import WeierstrassIsomorphism, isomorphisms, baseWI
7881

7982
from sage.sets.set import Set
8083

@@ -1019,6 +1022,67 @@ def __init__(self, E, kernel, codomain=None, degree=None, model=None, check=True
10191022

10201023
self.__perform_inheritance_housekeeping()
10211024

1025+
def _eval(self, P):
1026+
r"""
1027+
Less strict evaluation method for internal use.
1028+
1029+
In particular, this can be used to evaluate ``self`` at a
1030+
point defined over an extension field.
1031+
1032+
INPUT: a sequence of 3 coordinates defining a point on ``self``
1033+
1034+
OUTPUT: the result of evaluating ``self'' at the given point
1035+
1036+
EXAMPLES::
1037+
1038+
sage: E = EllipticCurve([1,0]); E
1039+
Elliptic Curve defined by y^2 = x^3 + x over Rational Field
1040+
sage: phi = E.isogeny(E(0,0))
1041+
sage: P = E.change_ring(QQbar).lift_x(QQbar.random_element())
1042+
sage: phi._eval(P).curve()
1043+
Elliptic Curve defined by y^2 = x^3 + (-4)*x over Algebraic Field
1044+
1045+
::
1046+
1047+
sage: E = EllipticCurve(j=Mod(0,419))
1048+
sage: K = next(filter(bool, E(0).division_points(5)))
1049+
sage: psi = E.isogeny(K)
1050+
sage: Ps = E.change_ring(GF(419**2))(0).division_points(5)
1051+
sage: {psi._eval(P).curve() for P in Ps}
1052+
{Elliptic Curve defined by y^2 = x^3 + 140*x + 214 over Finite Field in z2 of size 419^2}
1053+
"""
1054+
if self._domain.defining_polynomial()(*P):
1055+
raise ValueError(f'{P} not on {self._domain}')
1056+
1057+
if not P:
1058+
k = Sequence(tuple(P)).universe()
1059+
return self._codomain(0).change_ring(k)
1060+
1061+
Q = P.xy()
1062+
pre_iso = self.get_pre_isomorphism()
1063+
if pre_iso is not None:
1064+
Q = baseWI.__call__(pre_iso, Q)
1065+
1066+
if self.__algorithm == 'velu':
1067+
compute = self.__compute_via_velu
1068+
elif self.__algorithm == 'kohel':
1069+
compute = self.__compute_via_kohel
1070+
else:
1071+
raise NotImplementedError
1072+
1073+
try:
1074+
Q = compute(*Q)
1075+
except ZeroDivisionError:
1076+
Q = (0,1,0)
1077+
1078+
post_iso = self.get_post_isomorphism()
1079+
if post_iso is not None:
1080+
Q = baseWI.__call__(post_iso, Q)
1081+
1082+
k = Sequence(tuple(P) + tuple(Q)).universe()
1083+
return self._codomain.base_extend(k).point(Q)
1084+
1085+
10221086
def _call_(self, P):
10231087
r"""
10241088
Function that implements the call-ability of elliptic curve
@@ -2534,17 +2598,10 @@ def __compute_via_kohel_numeric(self, xP, yP):
25342598
25352599
"""
25362600
# first check if this point is in the kernel:
2537-
25382601
if 0 == self.__inner_kernel_polynomial(x=xP):
25392602
return self.__intermediate_codomain(0)
25402603

2541-
(xP_out, yP_out) = self.__compute_via_kohel(xP, yP)
2542-
2543-
# xP_out and yP_out do not always get evaluated to field
2544-
# elements but rather constant polynomials, so we do some
2545-
# explicit casting
2546-
2547-
return (self.__base_field(xP_out), self.__base_field(yP_out))
2604+
return self.__compute_via_kohel(xP, yP)
25482605

25492606
def __compute_via_kohel(self, xP, yP):
25502607
r"""
@@ -2571,9 +2628,7 @@ def __compute_via_kohel(self, xP, yP):
25712628
a = self.__phi(xP)
25722629
b = self.__omega(xP, yP)
25732630
c = self.__psi(xP)
2574-
cc = self.__mpoly_ring(c)
2575-
2576-
return (a/c**2, b/cc**3)
2631+
return (a/c**2, b/c**3)
25772632

25782633
def __initialize_rational_maps_via_kohel(self):
25792634
r"""
@@ -2595,7 +2650,7 @@ def __initialize_rational_maps_via_kohel(self):
25952650
25962651
"""
25972652
x = self.__poly_ring.gen()
2598-
y = self.__mpoly_ring.gen(1)
2653+
y = self.__xyfield.gen(1)
25992654
return self.__compute_via_kohel(x,y)
26002655

26012656
#

src/sage/schemes/elliptic_curves/ell_field.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,9 +767,9 @@ def descend_to(self, K, f=None):
767767
Elist = [E.minimal_model() for E in Elist]
768768
return Elist
769769

770-
def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
770+
def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, algorithm=None):
771771
r"""
772-
Return an elliptic curve isogeny from self.
772+
Return an elliptic-curve isogeny from this elliptic curve.
773773
774774
The isogeny can be determined in two ways, either by a
775775
polynomial or a set of torsion points. The methods used are:
@@ -785,6 +785,14 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
785785
(little endian)) which will define the kernel of the
786786
isogeny.
787787
788+
- Factored Isogenies (*experimental* --- see
789+
:mod:`sage.schemes.elliptic_curves.hom_composite`):
790+
Given a list of points which generate a composite-order
791+
subgroup, decomposes the isogeny into prime-degree steps.
792+
This can be used to construct isogenies of extremely large,
793+
smooth degree.
794+
This algorithm is selected using ``algorithm="factored"``.
795+
788796
INPUT:
789797
790798
- ``E`` - an elliptic curve, the domain of the isogeny to
@@ -825,6 +833,11 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
825833
kernel polynomial, meaning that its roots
826834
are the x-coordinates of a finite subgroup.
827835
836+
- ``algorithm`` (optional): When ``algorithm="factored"`` is
837+
passed, decompose the isogeny into prime-degree steps.
838+
The ``degree`` and ``model`` parameters are not supported by
839+
``algorithm="factored"``.
840+
828841
OUTPUT:
829842
830843
An isogeny between elliptic curves. This is a morphism of curves.
@@ -838,11 +851,15 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
838851
sage: phi.rational_maps()
839852
((x^2 + x + 1)/(x + 1), (x^2*y + x)/(x^2 + 1))
840853
854+
::
855+
841856
sage: E = EllipticCurve('11a1')
842857
sage: P = E.torsion_points()[1]
843858
sage: E.isogeny(P)
844859
Isogeny of degree 5 from Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field to Elliptic Curve defined by y^2 + y = x^3 - x^2 - 7820*x - 263580 over Rational Field
845860
861+
::
862+
846863
sage: E = EllipticCurve(GF(19),[1,1])
847864
sage: P = E(15,3); Q = E(2,12);
848865
sage: (P.order(), Q.order())
@@ -852,6 +869,17 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
852869
sage: phi(E.random_point()) # all points defined over GF(19) are in the kernel
853870
(0 : 1 : 0)
854871
872+
::
873+
874+
sage: E = EllipticCurve(GF(2^32-5), [170246996, 2036646110])
875+
sage: P = E.lift_x(2)
876+
sage: E.isogeny(P, algorithm="factored") # experimental
877+
doctest:warning
878+
...
879+
Composite morphism of degree 1073721825 = 3^4*5^2*11*19*43*59:
880+
From: Elliptic Curve defined by y^2 = x^3 + 170246996*x + 2036646110 over Finite Field of size 4294967291
881+
To: Elliptic Curve defined by y^2 = x^3 + 272790262*x + 1903695400 over Finite Field of size 4294967291
882+
855883
Not all polynomials define a finite subgroup (:trac:`6384`)::
856884
857885
sage: E = EllipticCurve(GF(31),[1,0,0,1,2])
@@ -860,6 +888,8 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
860888
...
861889
ValueError: The polynomial x^3 + 4*x^2 + 27*x + 14 does not define a finite subgroup of Elliptic Curve defined by y^2 + x*y = x^3 + x + 2 over Finite Field of size 31.
862890
891+
TESTS:
892+
863893
Until the checking of kernel polynomials was implemented in
864894
:trac:`23222`, the following raised no error but returned an
865895
invalid morphism. See also :trac:`11578`::
@@ -873,6 +903,13 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True):
873903
...
874904
ValueError: The polynomial x^2 + (-396/5*a - 2472/5)*x + 223344/5*a - 196272/5 does not define a finite subgroup of Elliptic Curve defined by y^2 = x^3 + (-13392)*x + (-1080432) over Number Field in a with defining polynomial x^2 - x - 1.
875905
"""
906+
if algorithm == "factored":
907+
if degree is not None:
908+
raise TypeError('algorithm="factored" does not support the "degree" parameter')
909+
if model is not None:
910+
raise TypeError('algorithm="factored" does not support the "model" parameter')
911+
from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
912+
return EllipticCurveHom_composite(self, kernel, codomain=codomain)
876913
try:
877914
return EllipticCurveIsogeny(self, kernel, codomain, degree, model, check=check)
878915
except AttributeError as e:

src/sage/schemes/elliptic_curves/hom.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
from sage.categories.morphism import Morphism
2626

27+
import sage.schemes.elliptic_curves.weierstrass_morphism as wm
28+
2729

2830
class EllipticCurveHom(Morphism):
2931
"""
@@ -162,9 +164,11 @@ def degree(self):
162164
r"""
163165
Return the degree of this elliptic-curve morphism.
164166
165-
Implemented by child classes. For examples,
166-
see :meth:`EllipticCurveIsogeny.degree` and
167-
:meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.degree`.
167+
Implemented by child classes. For examples, see:
168+
169+
- :meth:`EllipticCurveIsogeny.degree`
170+
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.degree`
171+
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.degree`
168172
169173
TESTS::
170174
@@ -180,9 +184,11 @@ def kernel_polynomial(self):
180184
r"""
181185
Return the kernel polynomial of this elliptic-curve morphism.
182186
183-
Implemented by child classes. For examples,
184-
see :meth:`EllipticCurveIsogeny.kernel_polynomial` and
185-
:meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.kernel_polynomial`.
187+
Implemented by child classes. For examples, see:
188+
189+
- :meth:`EllipticCurveIsogeny.kernel_polynomial`
190+
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.kernel_polynomial`
191+
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.kernel_polynomial`
186192
187193
TESTS::
188194
@@ -198,9 +204,11 @@ def dual(self):
198204
r"""
199205
Return the dual of this elliptic-curve morphism.
200206
201-
Implemented by child classes. For examples,
202-
see :meth:`EllipticCurveIsogeny.dual` and
203-
:meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.dual`.
207+
Implemented by child classes. For examples, see:
208+
209+
- :meth:`EllipticCurveIsogeny.dual`
210+
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.dual`
211+
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.dual`
204212
205213
TESTS::
206214
@@ -218,9 +226,11 @@ def rational_maps(self):
218226
elliptic-curve morphism as fractions of bivariate
219227
polynomials in `x` and `y`.
220228
221-
Implemented by child classes. For examples,
222-
see :meth:`EllipticCurveIsogeny.rational_maps` and
223-
:meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.rational_maps`.
229+
Implemented by child classes. For examples, see:
230+
231+
- :meth:`EllipticCurveIsogeny.rational_maps`
232+
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.rational_maps`
233+
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.rational_maps`
224234
225235
TESTS::
226236
@@ -237,9 +247,11 @@ def x_rational_map(self):
237247
Return the `x`-coordinate rational map of this elliptic-curve
238248
morphism as a univariate rational expression in `x`.
239249
240-
Implemented by child classes. For examples,
241-
see :meth:`EllipticCurveIsogeny.x_rational_map` and
242-
:meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.x_rational_map`.
250+
Implemented by child classes. For examples, see:
251+
252+
- :meth:`EllipticCurveIsogeny.x_rational_map`
253+
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.x_rational_map`
254+
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.x_rational_map`
243255
244256
TESTS::
245257
@@ -448,7 +460,7 @@ def is_surjective(self):
448460
sage: phi.is_surjective()
449461
True
450462
"""
451-
return True
463+
return bool(self.degree())
452464

453465
def is_injective(self):
454466
r"""
@@ -477,9 +489,9 @@ def is_injective(self):
477489
sage: phi.is_injective()
478490
True
479491
"""
480-
# This will become wrong once purely inseparable isogenies
481-
# are implemented. We should probably add something like a
482-
# separable_degree() method then.
492+
if not self.is_separable():
493+
#TODO: should implement .separable_degree() or similar
494+
raise NotImplementedError
483495
return self.degree() == 1
484496

485497
def is_zero(self):
@@ -501,6 +513,25 @@ def is_zero(self):
501513
"""
502514
return not self.degree()
503515

516+
def __neg__(self):
517+
r"""
518+
Return the negative of this elliptic-curve morphism. In other
519+
words, return `[-1]\circ\varphi` where `\varphi` is ``self``
520+
and `[-1]` is the negation automorphism on the codomain curve.
521+
522+
EXAMPLES::
523+
524+
sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom
525+
sage: E = EllipticCurve(GF(1019), [5,5])
526+
sage: phi = E.isogeny(E.lift_x(73))
527+
sage: f,g = phi.rational_maps()
528+
sage: psi = EllipticCurveHom.__neg__(phi)
529+
sage: psi.rational_maps() == (f, -g)
530+
True
531+
"""
532+
a1,_,a3,_,_ = self.codomain().a_invariants()
533+
return wm.WeierstrassIsomorphism(self.codomain(), (-1,0,-a1,-a3)) * self
534+
504535
@cached_method
505536
def __hash__(self):
506537
"""

0 commit comments

Comments
 (0)