Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sparse strategies for composite elliptic-curve isogenies #34965

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/doc/en/reference/references/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
103 changes: 84 additions & 19 deletions src/sage/schemes/elliptic_curves/hom_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -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
yyyyx4 marked this conversation as resolved.
Show resolved Hide resolved
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):
Expand Down Expand Up @@ -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
Expand Down