diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index fda85973f75..52f520395d6 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2021,6 +2021,11 @@ REFERENCES: .. [DeVi1984] \M.-P. Delest, and G. Viennot, *Algebraic Languages and Polyominoes Enumeration.* Theoret. Comput. Sci. 34, 169-206, 1984. +.. [DJP2014] Luca De Feo, David Jao and Jérôme Plût: Towards quantum-resistant + cryptosystems from supersingular elliptic curve isogenies. Journal + of Mathematical Cryptology, vol. 8, no. 3, 2014, pp. 209-247. + https://eprint.iacr.org/2011/506.pdf + .. [DFMS1996] Philipppe Di Francesco, Pierre Mathieu, and David Sénéchal. *Conformal Field Theory*. Graduate Texts in Contemporary Physics, Springer, 1996. diff --git a/src/sage/schemes/elliptic_curves/hom_composite.py b/src/sage/schemes/elliptic_curves/hom_composite.py index a117e2e87d0..abbebccb98d 100644 --- a/src/sage/schemes/elliptic_curves/hom_composite.py +++ b/src/sage/schemes/elliptic_curves/hom_composite.py @@ -92,8 +92,6 @@ from sage.schemes.elliptic_curves.ell_curve_isogeny import EllipticCurveIsogeny from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism, identity_morphism -# TODO: Implement sparse strategies? (cf. the SIKE cryptosystem) - def _eval_factored_isogeny(phis, P): """ @@ -120,33 +118,100 @@ def _eval_factored_isogeny(phis, P): return P -def _compute_factored_isogeny_prime_power(P, l, e): - """ - This method takes a point `P` of order `l^e` and returns - a sequence of degree-`l` isogenies whose composition has +def _compute_factored_isogeny_prime_power(P, l, n, split=.8): + r""" + This method takes a point `P` of order `\ell^n` and returns + a sequence of degree-`\ell` isogenies whose composition has the subgroup generated by `P` as its kernel. + The optional argument ``split``, a real number between + `0` and `1`, controls the *strategy* used to compute the + isogeny: In general, the algorithm performs a number of + scalar multiplications `[\ell]` and a number of + `\ell`-isogeny evaluations, and there exist tradeoffs + between them. + + - Setting ``split`` to `0` skews the algorithm towards + isogenies, minimizing multiplications. + The asymptotic complexity is `O(n \log(\ell) + n^2 \ell)`. + + - Setting ``split`` to `1` skews the algorithm towards + multiplications, minimizing isogenies. + The asymptotic complexity is `O(n^2 \log(\ell) + n \ell)`. + + - Values strictly between `0` and `1` define *sparse* + strategies, which balance the number of isogenies and + multiplications according to the parameter. + The asymptotic complexity is `O(n \log(n) \ell)`. + + .. NOTE:: + + As of July 2022, good values for ``split`` range somewhere + between roughly `0.6` and `0.9`, depending on the size of + `\ell` and the cost of base-field arithmetic. + + REFERENCES: + + Sparse strategies were introduced in [DJP2014]_, §4.2.2. + + ALGORITHM: + + The splitting rule implemented here is a simple heuristic which + is usually not optimal. The advantage is that it does not rely + on prior knowledge of degrees and exact costs of elliptic-curve + arithmetic, while still working reasonably well for a fairly + wide range of situations. + EXAMPLES:: sage: from sage.schemes.elliptic_curves import hom_composite sage: E = EllipticCurve(GF(8191), [1,0]) # optional - sage.rings.finite_rings sage: P = E.random_point() # optional - sage.rings.finite_rings - sage: (l,e), = P.order().factor() # optional - sage.rings.finite_rings - sage: phis = hom_composite._compute_factored_isogeny_prime_power(P, l, e) # optional - sage.rings.finite_rings + sage: (l,n), = P.order().factor() # optional - sage.rings.finite_rings + sage: phis = hom_composite._compute_factored_isogeny_prime_power(P, l, n) # optional - sage.rings.finite_rings sage: hom_composite._eval_factored_isogeny(phis, P) # optional - sage.rings.finite_rings (0 : 1 : 0) - sage: [phi.degree() for phi in phis] == [l]*e # optional - sage.rings.finite_rings + sage: [phi.degree() for phi in phis] == [l]*n # optional - sage.rings.finite_rings + True + + All choices of ``split`` produce the same result, albeit + not equally fast:: + + sage: E = EllipticCurve(GF(2^127 - 1), [1,0]) # optional - sage.rings.finite_rings + sage: P, = E.gens() # optional - sage.rings.finite_rings + sage: (l,n), = P.order().factor() # optional - sage.rings.finite_rings + sage: phis = hom_composite._compute_factored_isogeny_prime_power(P,l,n) # optional - sage.rings.finite_rings + sage: phis == hom_composite._compute_factored_isogeny_prime_power(P,l,n, split=0) # optional - sage.rings.finite_rings + True + sage: phis == hom_composite._compute_factored_isogeny_prime_power(P,l,n, split=0.1) # optional - sage.rings.finite_rings + True + sage: phis == hom_composite._compute_factored_isogeny_prime_power(P,l,n, split=0.5) # optional - sage.rings.finite_rings + True + sage: phis == hom_composite._compute_factored_isogeny_prime_power(P,l,n, split=0.9) # optional - sage.rings.finite_rings + True + sage: phis == hom_composite._compute_factored_isogeny_prime_power(P,l,n, split=1) # optional - sage.rings.finite_rings True """ - E = P.curve() - phis = [] - for i in range(e): - K = l**(e-1-i) * P - phi = EllipticCurveIsogeny(E, K) - E = phi.codomain() - P = phi(P) - phis.append(phi) - return phis + def rec(Q, k): + + if k == 1: + # base case: Q has order l + return [EllipticCurveIsogeny(Q.curve(), Q)] + + # recursive case: k > 1 and Q has order l^k + + k1 = int(k * split + .5) + k1 = max(1, min(k - 1, k1)) # clamp to [1, k - 1] + + Q1 = l**k1 * Q + L = rec(Q1, k - k1) + + Q2 = _eval_factored_isogeny(L, Q) + R = rec(Q2, k1) + + return L + R + + return rec(P, n) def _compute_factored_isogeny_single_generator(P): @@ -196,7 +261,7 @@ def _compute_factored_isogeny(kernel): phis = [] ker = list(kernel) while ker: - K, ker = ker[0], ker[1:] + K = ker.pop(0) psis = _compute_factored_isogeny_single_generator(K) ker = [_eval_factored_isogeny(psis, P) for P in ker] phis += psis