Skip to content

Commit 2df4906

Browse files
author
Release Manager
committed
gh-39036: implement elliptic-curve point addition for general rings Here we revive the effort from #33228 to implement point-addition formulas on elliptic curves over (more) general rings. This resolves #33228. URL: #39036 Reported by: Lorenz Panny Reviewer(s): Lorenz Panny, Vincent Macri
2 parents e16589b + f077c1a commit 2df4906

File tree

7 files changed

+536
-99
lines changed

7 files changed

+536
-99
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ REFERENCES:
496496
.. [BeBo2009] Olivier Bernardi and Nicolas Bonichon, *Intervals in Catalan
497497
lattices and realizers of triangulations*, JCTA 116 (2009)
498498
499+
.. [Best2021] Alex J. Best: Tools and Techniques for Rational Points on Curves.
500+
PhD Thesis, Boston University, 2021.
501+
499502
.. [BBGL2008] \A. Blondin Massé, S. Brlek, A. Garon, and S. Labbé,
500503
Combinatorial properties of f -palindromes in the
501504
Thue-Morse sequence. Pure Math. Appl.,
@@ -970,6 +973,10 @@ REFERENCES:
970973
Anal. Appl. 15 (1994) 804-823.
971974
:doi:`10.1137/S0895479892230031`
972975
976+
.. [BL1995] W. Bosma, H.W. Lenstra: Complete Systems of Two Addition Laws for
977+
Elliptic Curves. Journal of Number Theory, volume 53, issue 2,
978+
pages 229-240. 1995.
979+
973980
.. [BHMPW20a] Tom Braden, June Huh, Jacob P. Matherne, Nicholas Proudfoot,
974981
and Botong Wang, *A semi-small decomposition of the Chow
975982
ring of a matroid*, :arxiv:`2002.03341` (2020).
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
def _add(E, P, Q):
3+
r"""
4+
Addition formulas for elliptic curves over general rings
5+
with trivial Picard group.
6+
7+
This function returns a generator which yields tuples of projective
8+
coordinates. Some linear combination of those coordinate tuples is
9+
guaranteed to form a valid projective point.
10+
11+
.. NOTE::
12+
13+
This function is for internal use. To add two elliptic-curve
14+
points, users should simply use the `+` operator.
15+
16+
REFERENCES:
17+
18+
These formulas were derived by Bosma and Lenstra [BL1995]_,
19+
with corrections by Best [Best2021]_ (Appendix A).
20+
21+
EXAMPLES::
22+
23+
sage: from sage.schemes.elliptic_curves.addition_formulas_ring import _add
24+
sage: M = Zmod(13*17*19)
25+
sage: R.<U,V> = M[]
26+
sage: S.<u,v> = R.quotient(U*V - 17)
27+
sage: E = EllipticCurve(S, [1,2,3,4,5])
28+
sage: P = E(817, 13, 19)
29+
sage: Q = E(425, 123, 17)
30+
sage: PQ1, PQ2 = _add(E, P, Q)
31+
sage: PQ1
32+
(1188, 1674, 540)
33+
sage: PQ2
34+
(582, 2347, 1028)
35+
sage: E(PQ1) == E(PQ2)
36+
True
37+
38+
TESTS:
39+
40+
We ensure that these formulas return the same result as the ones over a field::
41+
42+
sage: from sage.schemes.elliptic_curves.addition_formulas_ring import _add
43+
sage: F = GF(2^127-1)
44+
sage: E = EllipticCurve(j=F.random_element())
45+
sage: E = choice(E.twists())
46+
sage: P = E.random_point()
47+
sage: Q = E.random_point()
48+
sage: PQ1, PQ2 = _add(E, P, Q)
49+
sage: assert E(*PQ1) == P + Q
50+
sage: assert E(*PQ2) == P + Q
51+
"""
52+
a1, a2, a3, a4, a6 = E.a_invariants()
53+
b2, b4, b6, b8 = E.b_invariants()
54+
55+
if P not in E:
56+
raise ValueError('P must be in E')
57+
if Q not in E:
58+
raise ValueError('Q must be in E')
59+
X1, Y1, Z1 = P
60+
X2, Y2, Z2 = Q
61+
62+
# TODO: I've made a half-hearted attempt at simplifying the formulas
63+
# by caching common subexpressions. This could almost certainly be
64+
# sped up significantly with some more serious optimization effort.
65+
66+
XYdif = X1*Y2 - X2*Y1
67+
XYsum = X1*Y2 + X2*Y1
68+
XZdif = X1*Z2 - X2*Z1
69+
XZsum = X1*Z2 + X2*Z1
70+
YZdif = Y1*Z2 - Y2*Z1
71+
YZsum = Y1*Z2 + Y2*Z1
72+
73+
a1sq, a2sq, a3sq, a4sq = (a**2 for a in (a1, a2, a3, a4))
74+
75+
X31 = XYdif*YZsum+XZdif*Y1*Y2+a1*X1*X2*YZdif+a1*XYdif*XZsum-a2*X1*X2*XZdif+a3*XYdif*Z1*Z2+a3*XZdif*YZsum-a4*XZsum*XZdif-3*a6*XZdif*Z1*Z2
76+
77+
Y31 = -3*X1*X2*XYdif-Y1*Y2*YZdif-2*a1*XZdif*Y1*Y2+(a1sq+3*a2)*X1*X2*YZdif-(a1sq+a2)*XYsum*XZdif+(a1*a2-3*a3)*X1*X2*XZdif-(2*a1*a3+a4)*XYdif*Z1*Z2+a4*XZsum*YZdif+(a1*a4-a2*a3)*XZsum*XZdif+(a3sq+3*a6)*YZdif*Z1*Z2+(3*a1*a6-a3*a4)*XZdif*Z1*Z2
78+
79+
Z31 = 3*X1*X2*XZdif-YZsum*YZdif+a1*XYdif*Z1*Z2-a1*XZdif*YZsum+a2*XZsum*XZdif-a3*YZdif*Z1*Z2+a4*XZdif*Z1*Z2
80+
81+
yield (X31, Y31, Z31)
82+
83+
X32 = Y1*Y2*XYsum+a1*(2*X1*Y2+X2*Y1)*X2*Y1+a1sq*X1*X2**2*Y1-a2*X1*X2*XYsum-a1*a2*X1**2*X2**2+a3*X2*Y1*(YZsum+Y2*Z1)+a1*a3*X1*X2*YZdif-a1*a3*XYsum*XZdif-a4*X1*X2*YZsum-a4*XYsum*XZsum-a1sq*a3*X1**2*X2*Z2-a1*a4*X1*X2*(X1*Z2+XZsum)-a2*a3*X1*X2**2*Z1-a3sq*X1*Z2*(Y2*Z1+YZsum)-3*a6*XYsum*Z1*Z2-3*a6*XZsum*YZsum-a1*a3sq*X1*Z2*(XZsum+X2*Z1)-3*a1*a6*X1*Z2*(XZsum+X2*Z1)-a3*a4*(X1*Z2+XZsum)*X2*Z1-b8*YZsum*Z1*Z2-a1*b8*X1*Z1*Z2**2-a3**3*XZsum*Z1*Z2-3*a3*a6*(XZsum+X2*Z1)*Z1*Z2-a3*b8*Z1**2*Z2**2
84+
85+
Y32 = Y1**2*Y2**2+a1*X2*Y1**2*Y2+(a1*a2-3*a3)*X1*X2**2*Y1+a3*Y1**2*Y2*Z2-(a2sq-3*a4)*X1**2*X2**2+(a1*a4-a2*a3)*(2*X1*Z2+X2*Z1)*X2*Y1+(a1sq*a4-2*a1*a2*a3+3*a3sq)*X1**2*X2*Z2-(a2*a4-9*a6)*X1*X2*XZsum+(3*a1*a6-a3*a4)*(XZsum+X2*Z1)*Y1*Z2+(3*a1sq*a6-2*a1*a3*a4+a2*a3sq+3*a2*a6-a4sq)*X1*Z2*(XZsum+X2*Z1)+(3*a2*a6-a4sq)*X2*Z1*(2*X1*Z2+Z1*X2)+(a1**3*a6-a1sq*a3*a4+a1*a2*a3sq-a1*a4sq+4*a1*a2*a6-a3**3-3*a3*a6)*Y1*Z1*Z2**2+(a1**4*a6-a1**3*a3*a4+5*a1sq*a2*a6+a1sq*a2*a3sq-a1*a2*a3*a4-a1*a3**3-3*a1*a3*a6-a1sq*a4sq+a2sq*a3sq-a2*a4sq+4*a2sq*a6-a3**2*a4-3*a4*a6)*X1*Z1*Z2**2+(a1sq*a2*a6-a1*a2*a3*a4+3*a1*a3*a6+a2sq*a3sq-a2*a4sq+4*a2sq*a6-2*a3sq*a4-3*a4*a6)*X2*Z1**2*Z2+(a1**3*a3*a6-a1sq*a3sq*a4+a1sq*a4*a6+a1*a2*a3**3+4*a1*a2*a3*a6-2*a1*a3*a4sq+a2*a3sq*a4+4*a2*a4*a6-a3**4-6*a3**2*a6-a4**3-9*a6**2)*Z1**2*Z2**2
86+
87+
Z32 = 3*X1*X2*XYsum+Y1*Y2*YZsum+3*a1*X1**2*X2**2+a1*(2*X1*Y2+Y1*X2)*Y1*Z2+a1sq*X1*Z2*(2*X2*Y1+X1*Y2)+a2*X1*X2*YZsum+a2*XYsum*XZsum+a1**3*X1**2*X2*Z2+a1*a2*X1*X2*(2*X1*Z2+X2*Z1)+3*a3*X1*X2**2*Z1+a3*Y1*Z2*(YZsum+Y2*Z1)+2*a1*a3*X1*Z2*YZsum+2*a1*a3*X2*Y1*Z1*Z2+a4*XYsum*Z1*Z2+a4*XZsum*YZsum+(a1sq*a3+a1*a4)*X1*Z2*(XZsum+X2*Z1)+a2*a3*X2*Z1*(2*X1*Z2+X2*Z1)+a3sq*Y1*Z1*Z2**2+(a3sq+3*a6)*YZsum*Z1*Z2+a1*a3sq*(2*X1*Z2+X2*Z1)*Z1*Z2+3*a1*a6*X1*Z1*Z2**2+a3*a4*(XZsum+X2*Z1)*Z1*Z2+(a3**3+3*a3*a6)*Z1**2*Z2**2
88+
89+
yield (X32, Y32, Z32)

src/sage/schemes/elliptic_curves/ell_generic.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@
5555
import math
5656
from sage.arith.misc import valuation
5757

58-
import sage.rings.abc
59-
from sage.rings.finite_rings.integer_mod import mod
6058
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
6159
from sage.rings.polynomial.polynomial_ring import polygen, polygens
6260
from sage.rings.polynomial.polynomial_element import polynomial_is_variable
@@ -175,15 +173,49 @@ def __init__(self, K, ainvs, category=None):
175173

176174
self.__divpolys = ({}, {}, {})
177175

178-
# See #1975: we deliberately set the class to
179-
# EllipticCurvePoint_finite_field for finite rings, so that we
180-
# can do some arithmetic on points over Z/NZ, for teaching
181-
# purposes.
182-
if isinstance(K, sage.rings.abc.IntegerModRing):
183-
self._point = ell_point.EllipticCurvePoint_finite_field
184-
185176
_point = ell_point.EllipticCurvePoint
186177

178+
def assume_base_ring_is_field(self, flag=True):
179+
r"""
180+
Set a flag to pretend that this elliptic curve is defined over a
181+
field while doing arithmetic, which is useful in some algorithms.
182+
183+
184+
.. WARNING::
185+
186+
The flag affects all points created while the flag is set. Note
187+
that elliptic curves are unique parents, hence setting this flag
188+
may break seemingly unrelated parts of Sage.
189+
190+
.. NOTE::
191+
192+
This method is a **hack** provided for educational purposes.
193+
194+
EXAMPLES::
195+
196+
sage: E = EllipticCurve(Zmod(35), [1,1])
197+
sage: P = E(-5, 9)
198+
sage: 4*P
199+
(23 : 26 : 1)
200+
sage: 9*P
201+
(10 : 11 : 5)
202+
sage: E.assume_base_ring_is_field()
203+
sage: P = E(-5, 9)
204+
sage: 4*P
205+
(23 : 26 : 1)
206+
sage: 9*P
207+
Traceback (most recent call last):
208+
...
209+
ZeroDivisionError: Inverse of 5 does not exist (characteristic = 35 = 5*7)
210+
"""
211+
if flag:
212+
if self.__base_ring.is_finite():
213+
self._point = ell_point.EllipticCurvePoint_finite_field
214+
else:
215+
self._point = ell_point.EllipticCurvePoint_field
216+
else:
217+
self._point = ell_point.EllipticCurvePoint
218+
187219
def _defining_params_(self):
188220
r"""
189221
Internal function. Return a tuple of the base ring of this
@@ -582,7 +614,7 @@ def __call__(self, *args, **kwds):
582614
# infinity.
583615
characteristic = self.base_ring().characteristic()
584616
if characteristic != 0 and isinstance(args[0][0], Rational) and isinstance(args[0][1], Rational):
585-
if mod(args[0][0].denominator(),characteristic) == 0 or mod(args[0][1].denominator(),characteristic) == 0:
617+
if characteristic.divides(args[0][0].denominator()) or characteristic.divides(args[0][1].denominator()):
586618
return self._reduce_point(args[0], characteristic)
587619
args = tuple(args[0])
588620

0 commit comments

Comments
 (0)