Repository: sagemath/sage

Issue:
compute matrix of isogeny on n-torsion
<div id="comment:0"></div>

The task of computing the matrix by which an isogeny acts on a particular n‑torsion subgroup often comes up in algorithms for supersingular elliptic curves. (In particular, it appears as a subroutine of computing the kernel of an endomorphism-ring ideal.)

This patch adds a simple method to compute such a matrix, based on `AdditiveAbelianGroupWrapper.discrete_log()`.

(The dependency is only necessary for the reference in the "see also" block.)

Depends on #34718

Component: **elliptic curves**

Author: **Lorenz Panny**

Branch/Commit: **[public/matrix_of_isogeny](https://github.com/sagemath/sagetrac-mirror/tree/public/matrix_of_isogeny) @ [`0bc249d`](https://github.com/sagemath/sagetrac-mirror/commit/0bc249d5c71c5c74356fc3752cfb3caf9169145e)**

_Issue created by migration from https://trac.sagemath.org/ticket/34879_




Discussion:

Resolving patch:
diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py
--- a/src/sage/schemes/elliptic_curves/hom.py
+++ b/src/sage/schemes/elliptic_curves/hom.py
@@ -21,6 +21,8 @@
 
 - Lorenz Panny (2021): Refactor isogenies and isomorphisms into
   the common :class:`EllipticCurveHom` interface.
+
+- Lorenz Panny (2022): :meth:`~EllipticCurveHom.matrix_on_subgroup`
 """
 from sage.misc.cachefunc import cached_method
 from sage.structure.richcmp import richcmp_not_equal, richcmp, op_EQ, op_NE
@@ -722,6 +724,128 @@ def as_morphism(self):
         Y_affine = Curve(self.codomain()).affine_patch(2)
         return X_affine.hom(self.rational_maps(), Y_affine).homogenize(2)
 
+    def matrix_on_subgroup(self, domain_gens, codomain_gens=None):
+        r"""
+        Return the matrix by which this isogeny acts on the
+        `n`-torsion subgroup with respect to the given bases.
+
+        INPUT:
+
+        - ``domain_gens`` -- basis `(P,Q)` of some `n`-torsion
+          subgroup on the domain of this elliptic-curve morphism
+
+        - ``codomain_gens`` -- basis `(R,S)` of the `n`-torsion
+          on the codomain of this morphism, or (default) ``None``
+          if ``self`` is an endomorphism
+
+        OUTPUT:
+
+        A `2\times 2` matrix `M` over `\ZZ/n`, such that the
+        image of any point `[a]P + [b]Q` under this morphism
+        equals `[c]R + [d]S` where `(c\ d)^T = (a\ b) M`.
+
+        EXAMPLES::
+
+            sage: F.<i> = GF(419^2, modulus=[1,0,1])
+            sage: E = EllipticCurve(F, [1,0])
+            sage: P = E(3, 176*i)
+            sage: Q = E(i+7, 67*i+48)
+            sage: P.weil_pairing(Q, 420).multiplicative_order()
+            420
+            sage: iota = E.automorphisms()[2]; iota
+            Elliptic-curve endomorphism of Elliptic Curve defined by y^2 = x^3 + x over Finite Field in i of size 419^2
+              Via:  (u,r,s,t) = (i, 0, 0, 0)
+            sage: iota^2 == E.scalar_multiplication(-1)
+            True
+            sage: mat = iota.matrix_on_subgroup((P,Q)); mat
+            [301 386]
+            [ 83 119]
+            sage: mat.parent()
+            Full MatrixSpace of 2 by 2 dense matrices over Ring of integers modulo 420
+            sage: iota(P) == 301*P + 386*Q
+            True
+            sage: iota(Q) == 83*P + 119*Q
+            True
+            sage: a,b = 123, 456
+            sage: c,d = vector((a,b)) * mat; (c,d)
+            (111, 102)
+            sage: iota(a*P + b*Q) == c*P + d*Q
+            True
+
+        One important application of this is to compute generators of
+        the kernel subgroup of an isogeny, when the `n`-torsion subgroup
+        containing the kernel is accessible::
+
+            sage: K = E(83*i-16, 9*i-147)
+            sage: K.order()
+            7
+            sage: phi = E.isogeny(K)
+            sage: R,S = phi.codomain().gens()
+            sage: mat = phi.matrix_on_subgroup((P,Q), (R,S))
+            sage: mat  # random -- depends on R,S
+            [124 263]
+            [115 141]
+            sage: kermat = mat.left_kernel_matrix(); kermat
+            [300  60]
+            sage: ker = [ZZ(v[0])*P + ZZ(v[1])*Q for v in kermat]
+            sage: {phi(T) for T in ker}
+            {(0 : 1 : 0)}
+            sage: phi == E.isogeny(ker)
+            True
+
+        We can also compute the matrix of a Frobenius endomorphism
+        (:class:`~sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius`)
+        on a large enough subgroup to verify point-counting results::
+
+            sage: F.<a> = GF((101, 36))
+            sage: E = EllipticCurve(GF(101), [1,1])
+            sage: EE = E.change_ring(F)
+            sage: P,Q = EE.torsion_basis(37)
+            sage: pi = EE.frobenius_isogeny()
+            sage: M = pi.matrix_on_subgroup((P,Q))
+            sage: M.parent()
+            Full MatrixSpace of 2 by 2 dense matrices over Ring of integers modulo 37
+            sage: M.trace()
+            34
+            sage: E.trace_of_frobenius()
+            -3
+
+        .. SEEALSO::
+
+            To compute a basis of the `n`-torsion, you may use
+            :meth:`~sage.schemes.elliptic_curves.ell_finite_field.EllipticCurve_finite_field.torsion_basis`.
+        """
+        if codomain_gens is None:
+            if not self.is_endomorphism():
+                raise ValueError('basis of codomain subgroup is required for non-endomorphisms')
+            codomain_gens = domain_gens
+
+        P,Q = domain_gens
+        R,S = codomain_gens
+
+        ords = {P.order() for P in (P,Q,R,S)}
+        if len(ords) != 1:
+            #TODO: Is there some meaningful way to lift this restriction?
+            raise ValueError('generator points must all have the same order')
+        n, = ords
+
+        if P.weil_pairing(Q, n).multiplicative_order() != n:
+            raise ValueError('generator points on domain are not independent')
+        if R.weil_pairing(S, n).multiplicative_order() != n:
+            raise ValueError('generator points on codomain are not independent')
+
+        imP = self(P)
+        imQ = self(Q)
+
+        from sage.groups.additive_abelian.additive_abelian_wrapper import AdditiveAbelianGroupWrapper
+        H = AdditiveAbelianGroupWrapper(self.codomain().point_homset(), [R,S], [n,n])
+        vecP = H.discrete_log(imP)
+        vecQ = H.discrete_log(imQ)
+
+        from sage.matrix.constructor import matrix
+        from sage.rings.finite_rings.integer_mod_ring import Zmod
+        return matrix(Zmod(n), [vecP, vecQ])
+
 
 def compare_via_evaluation(left, right):
     r"""


Test patch: