Skip to content

Commit de15e88

Browse files
author
Release Manager
committed
gh-35935: add multiple= option to EllipticCurvePoint_field.set_order() Calling `order_from_multiple()` followed by `.set_order()` on elliptic- curve points is a common pattern in algorithms for elliptic curves over finite fields (for instance, this is part of a simple algorithm for sampling points of specified order). This patch adds a shorthand for the combination, allowing us to write `P.set_order(multiple=m)` instead. URL: #35935 Reported by: Lorenz Panny Reviewer(s): John Cremona
2 parents c0b1683 + 3e97ff9 commit de15e88

File tree

1 file changed

+80
-8
lines changed

1 file changed

+80
-8
lines changed

src/sage/schemes/elliptic_curves/ell_point.py

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,16 +1188,24 @@ def _divide_out(self, p):
11881188
pts = Q.division_points(p)
11891189
return (Q, k)
11901190

1191-
def set_order(self, value, *, check=True):
1191+
def set_order(self, value=None, *, multiple=None, check=True):
11921192
r"""
1193-
Set the value of ``self._order`` to ``value``.
1193+
Set the cached order of this point (i.e., the value of
1194+
``self._order``) to the given ``value``.
11941195
1195-
Use this when you know a priori the order of this point to avoid a
1196-
potentially expensive order calculation.
1196+
Alternatively, when ``multiple`` is given, this method will
1197+
first run :func:`~sage.groups.generic.order_from_multiple`
1198+
to determine the exact order from the given multiple of the
1199+
point order, then cache the result.
1200+
1201+
Use this when you know a priori the order of this point, or
1202+
a multiple of the order, to avoid a potentially expensive
1203+
order calculation.
11971204
11981205
INPUT:
11991206
12001207
- ``value`` -- positive integer
1208+
- ``multiple`` -- positive integer; mutually exclusive with ``value``
12011209
12021210
OUTPUT: ``None``
12031211
@@ -1212,6 +1220,10 @@ def set_order(self, value, *, check=True):
12121220
sage: G.set_order(2) # optional - sage.rings.finite_rings
12131221
sage: 2*G # optional - sage.rings.finite_rings
12141222
(0 : 1 : 0)
1223+
sage: G = E(0, 6) # optional - sage.rings.finite_rings
1224+
sage: G.set_order(multiple=12) # optional - sage.rings.finite_rings
1225+
sage: G._order # optional - sage.rings.finite_rings
1226+
3
12151227
12161228
We now give a more interesting case, the NIST-P521 curve. Its
12171229
order is too big to calculate with Sage, and takes a long time
@@ -1233,7 +1245,31 @@ def set_order(self, value, *, check=True):
12331245
(0 : 1 : 0)
12341246
sage: proof.arithmetic(prev_proof_state) # restore state
12351247
1236-
It is an error to pass a `value` equal to `0`::
1248+
Using ``.set_order()`` with a ``multiple=`` argument can
1249+
be used to compute a point's order *significantly* faster
1250+
than calling :meth:`order` if the point is already known
1251+
to be `m`-torsion::
1252+
1253+
sage: F.<a> = GF((10007, 23))
1254+
sage: E = EllipticCurve(F, [9,9])
1255+
sage: n = E.order()
1256+
sage: m = 5 * 47 * 139 * 1427 * 2027 * 4831 * 275449 * 29523031
1257+
sage: assert m.divides(n)
1258+
sage: P = n/m * E.lift_x(6747+a)
1259+
sage: assert m * P == 0
1260+
sage: P.set_order(multiple=m) # compute exact order
1261+
sage: factor(m // P.order()) # order is now cached
1262+
47 * 139
1263+
1264+
The algorithm used internally for this functionality is
1265+
:meth:`~sage.groups.generic.order_from_multiple`.
1266+
Indeed, simply calling :meth:`order` on ``P`` would take
1267+
much longer since factoring ``n`` is fairly expensive::
1268+
1269+
sage: n == m * 6670822796985115651 * 441770032618665681677 * 9289973478285634606114927
1270+
True
1271+
1272+
It is an error to pass a ``value`` equal to `0`::
12371273
12381274
sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 # optional - sage.rings.finite_rings
12391275
sage: G = E.random_point() # optional - sage.rings.finite_rings
@@ -1257,20 +1293,53 @@ def set_order(self, value, *, check=True):
12571293
...
12581294
ValueError: Value 11 illegal: 11 * (5 : 0 : 1) is not the identity
12591295
1260-
However, ``set_order`` can be fooled, though it's not likely in "real cases
1261-
of interest". For instance, the order can be set to a multiple the
1262-
actual order::
1296+
However, ``set_order`` can be fooled. For instance, the order
1297+
can be set to a multiple the actual order::
12631298
12641299
sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 # optional - sage.rings.finite_rings
12651300
sage: G = E(5, 0) # G has order 2 # optional - sage.rings.finite_rings
12661301
sage: G.set_order(8) # optional - sage.rings.finite_rings
12671302
sage: G.order() # optional - sage.rings.finite_rings
12681303
8
12691304
1305+
TESTS:
1306+
1307+
Check that some invalid inputs are caught::
1308+
1309+
sage: E = EllipticCurve(GF(101), [5,5])
1310+
sage: P = E.lift_x(11)
1311+
sage: P.set_order(17, multiple=119)
1312+
Traceback (most recent call last):
1313+
...
1314+
ValueError: cannot pass both value and multiple
1315+
sage: P.set_order(17)
1316+
sage: P.set_order(multiple=119+1)
1317+
Traceback (most recent call last):
1318+
...
1319+
ValueError: previously cached order 17 does not divide given multiple 120
1320+
sage: P.set_order(119)
1321+
Traceback (most recent call last):
1322+
...
1323+
ValueError: value 119 contradicts previously cached order 17
1324+
12701325
AUTHORS:
12711326
12721327
- Mariah Lenox (2011-02-16)
1328+
- Lorenz Panny (2022): add ``multiple=`` option
12731329
"""
1330+
if multiple is not None:
1331+
if value is not None:
1332+
raise ValueError('cannot pass both value and multiple')
1333+
1334+
if hasattr(self, '_order'): # already known
1335+
if check and not self._order.divides(multiple):
1336+
raise ValueError(f'previously cached order {self._order} does not divide given multiple {multiple}')
1337+
return
1338+
1339+
from sage.groups.generic import order_from_multiple
1340+
value = order_from_multiple(self, multiple, check=check)
1341+
check = False
1342+
12741343
value = Integer(value)
12751344

12761345
if check:
@@ -1283,6 +1352,9 @@ def set_order(self, value, *, check=True):
12831352
raise ValueError('Value %s illegal: outside max Hasse bound' % value)
12841353
if value * self != E(0):
12851354
raise ValueError('Value %s illegal: %s * %s is not the identity' % (value, value, self))
1355+
if hasattr(self, '_order') and self._order != value: # already known
1356+
raise ValueError(f'value {value} contradicts previously cached order {self._order}')
1357+
12861358
self._order = value
12871359

12881360
# ############################# end ################################

0 commit comments

Comments
 (0)