A tutorial on how to use AVIsogenies in SageMath

With this notebook we introduce the main functionalities of the AVIsogenies package.

Table of contents

  1. Introduction

  2. Basic arithmetic

  3. Morphisms

Introduction

To import the functionalities of this package, start with the following line:

[2]:
from avisogenies_sage import *

The following cell shows how to create an Abelian Variety with theta structure, by giving its Theta Null point.

[3]:
FF = GF(331); n = 2; g = 2

pt = [328 , 213 , 75 , 1]
A = KummerVariety(FF, g, pt)

It is possible to check that the given point is a valid Theta Null point. It checks that the given data satisfies the Riemann Relations. This is not tested unless it is specified.

[4]:
B = AbelianVariety(GF(331), 4, 1, [26, 191, 70, 130]); B
[4]:
Abelian variety of dimension 1 with theta null point (26 : 191 : 70 : 130) defined over Finite Field of size 331
[5]:
B = AbelianVariety(GF(331), 4, 1, [26, 191, 70, 130], check=True)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_44692/81372161.py in <cell line: 1>()
----> 1 B = AbelianVariety(GF(Integer(331)), Integer(4), Integer(1), [Integer(26), Integer(191), Integer(70), Integer(130)], check=True)

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/avisogenies_sage/constructor.py in AbelianVariety(*data, **kwargs)
     86             data = data[:1] + data[2:]
     87             return theta_null_point.KummerVariety(*data, **kwargs)
---> 88         return theta_null_point.AbelianVariety_ThetaStructure(*data, **kwargs)
     89
     90     return ModularAbelianVariety(data[0])

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/avisogenies_sage/theta_null_point.py in __init__(self, R, n, g, T, check)
    532
    533             if any(T[idx(-i)] != val for i, val in zip(D, T)):
--> 534                 raise ValueError('The given list does not define a valid thetanullpoint')
    535
    536             for (idxi, i), (idxj, j) in product(enumerate(D), repeat=2):

ValueError: The given list does not define a valid thetanullpoint

The following cell shows how to define a point in the constructed abelian variety.

[6]:
P0 = A(0)
P = A([255 , 89 , 30 , 1])

As with the theta null point, it is possible to check that the given data defines a valid point, but it is not tested unless it is specified. For that we need to use the AbelianVariety method point.

[7]:
Q = B([1 , 1 , 1 , 1])
[8]:
Q = B.point([1 , 1 , 1 , 1], check=True)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_44692/3498440664.py in <cell line: 1>()
----> 1 Q = B.point([Integer(1) , Integer(1) , Integer(1) , Integer(1)], check=True)

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/avisogenies_sage/theta_null_point.py in point(self, P, **kwds)
    235             AP = A(P)
    236             return AP.to_algebraic(A=self)
--> 237         return self._point(self, P, **kwds)
    238
    239     __call__ = point

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/avisogenies_sage/theta_point.py in __init__(self, X, v, check)
    788         if check:
    789             O = X.theta_null_point()
--> 790             idx = partial(tools.idx, n=O.level())
    791             dual = X._dual
    792             D = X._D

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/sage/structure/element.pyx in sage.structure.element.Element.__getattr__ (build/cythonized/sage/structure/element.c:4826)()
    492             AttributeError: 'LeftZeroSemigroup_with_category.element_class' object has no attribute 'blah_blah'
    493         """
--> 494         return self.getattr_from_category(name)
    495
    496     cdef getattr_from_category(self, name):

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/sage/structure/element.pyx in sage.structure.element.Element.getattr_from_category (build/cythonized/sage/structure/element.c:4938)()
    505         else:
    506             cls = P._abstract_element_class
--> 507         return getattr_from_other_class(self, cls, name)
    508
    509     def __dir__(self):

~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/sage/cpython/getattr.pyx in sage.cpython.getattr.getattr_from_other_class (build/cythonized/sage/cpython/getattr.c:2702)()
    359         dummy_error_message.cls = type(self)
    360         dummy_error_message.name = name
--> 361         raise AttributeError(dummy_error_message)
    362     attribute = <object>attr
    363     # Check for a descriptor (__get__ in Python)

AttributeError: 'AbelianVarietyPoint' object has no attribute 'level'

You can access a given coordinate using the corresponding element of \((\mathbb{Z}/n\mathbb{Z})^g\):

[9]:
P[[1,0]]
[9]:
89

Basic arithmetic

Follows the example in Section 6 of ‘Efficient Pairing Computation with theta functions’ by David Lubicz and Damien Robert.

[10]:
l = 1889
lP = l*P; lP
lP == A(0) #as projective points
[10]:
True
[11]:
R.<X> = PolynomialRing(FF)
poly = X^4 + 3*X^2 + 290*X + 3
FF2.<t> = poly.splitting_field()

Q_list = [158*t^3 + 67*t^2 + 9*t + 293, 290*t^3 + 25*t^2 + 235*t + 280,
 155*t^3 + 84*t^2 + 15*t + 170, 1]
A2 = A.change_ring(FF2)
P = A2(P)
Q = A2(Q_list)

P + Q #returns P + Q and P - Q
[11]:
((221*t^3 + 178*t^2 + 126*t + 27 : 32*t^3 + 17*t^2 + 175*t + 171 : 180*t^3 + 188*t^2 + 161*t + 119 : 261*t^3 + 107*t^2 + 37*t + 135),
 (1 : 56*t^3 + 312*t^2 + 147*t + 287 : 277*t^3 + 295*t^2 + 7*t + 287 : 290*t^3 + 203*t^2 + 274*t + 10))
[12]:
PmQ_list = (62*t^3 + 16*t^2 + 255*t + 129 , 172*t^3 + 157*t^2 + 43*t + 222 ,
 258*t^3 + 39*t^2 + 313*t + 150 , 1)
PmQ = A2.point(PmQ_list)

PQ = Q.diff_add(P, PmQ); PQ
[12]:
(261*t^3 + 107*t^2 + 37*t + 135 : 205*t^3 + 88*t^2 + 195*t + 125 : 88*t^3 + 99*t^2 + 164*t + 98 : 159*t^3 + 279*t^2 + 254*t + 276)
[13]:
P.weil_pairing(l, Q, PQ)
[13]:
17*t^3 + 153*t^2 + 305*t + 187
[14]:
lPQ, lP = P.diff_multadd(l, PQ, Q)
PlQ, lQ = Q.diff_multadd(l, PQ, P)
P._weil_pairing_from_points(Q, lP, lQ, lPQ, PlQ)
[14]:
17*t^3 + 153*t^2 + 305*t + 187

Computing points from other data

This section focuses on the computation of morphisms between hyperelliptic curves and the corresponding abelian varieties (their jacobians) with theta functions of level 2 and 4.

We can define a curve and its Jacobian

[18]:
F = GF(83^2); z, = F.gens(); Fx.<x> = PolynomialRing(F)

g = 2
a = [F(0), 1, 3, 15, 20]
rac = sqrt(a[1] - a[0])

f = prod(x - al for al in a)
C = HyperellipticCurve(f); C
[18]:
Hyperelliptic Curve over Finite Field in z2 of size 83^2 defined by y^2 = x^5 + 44*x^4 + 28*x^3 + 23*x^2 + 70*x

The most natural way to construct the corresponding Abelian Variety is with the function AbelianVariety.from_curve:

[19]:
A = AbelianVariety.from_curve(C); A
[19]:
Abelian variety of dimension 2 with theta null point (68 : z2 + 33 : 46 : z2 + 33 : 2*z2 + 29 : 77*z2 + 58 : 81*z2 + 31 : 38*z2 + 16 : 8 : 67*z2 + 53 : 48 : 67*z2 + 53 : 2*z2 + 29 : 38*z2 + 16 : 81*z2 + 31 : 77*z2 + 58) defined over Finite Field in z2 of size 83^2

Alternatively, if we have the classical theta constants associated to the Jacobian, we can also use AbelianVariety.with_theta_basis('F(2,2)')

[20]:
thc = [0]*(2**(2*g))
idx = lambda x : ZZ(x, 2)
thc[idx([0,0,0,0])]=F(1)
thc[idx([0,0,1,1])]=z^1491
thc[idx([0,0,1,0])]=z^777
thc[idx([0,0,0,1])]=F(30)
thc[idx([1,0,0,0])]=F(37)
thc[idx([1,0,0,1])]=z^2058
thc[idx([0,1,0,0])]=F(56)
thc[idx([1,1,0,0])]=F(57)
thc[idx([0,1,1,0])]=z^609
thc[idx([1,1,1,1])]=z^1533
thc[idx([0,1,0,1])]=F(0)
thc[idx([0,1,1,1])]=F(0)
thc[idx([1,0,1,0])]=F(0)
thc[idx([1,1,1,0])]=F(0)
thc[idx([1,0,1,1])]=F(0)
thc[idx([1,1,0,1])]=F(0)
thc = AbelianVariety.with_theta_basis('F(2,2)', F, 4, g, thc, curve=C, wp=a, rac=rac)

Now we can map some points between Mumford and Theta representation.

We define the jacobian of C and consider the Mumford divisor defined by:

[21]:
J = Jacobian(C)
u = (x-43)*(x-10); v = z^954*x + z^2518;
D = J([u,v]); D
[21]:
(x^2 + 30*x + 15, y + (21*z2 + 71)*x + 10*z2 + 10)

Then we can simply create a point of the abelian variety with this data:

[22]:
thD = thc(D);

Note that, even if internally we use the classical basis for these computations, the result is always given with basis \(\mathcal{F}(4)\):

[23]:
thD.abelian_variety()
[23]:
Abelian variety of dimension 2 with theta null point (68 : z2 + 33 : 46 : z2 + 33 : 2*z2 + 29 : 77*z2 + 58 : 81*z2 + 31 : 38*z2 + 16 : 8 : 67*z2 + 53 : 48 : 67*z2 + 53 : 2*z2 + 29 : 38*z2 + 16 : 81*z2 + 31 : 77*z2 + 58) defined over Finite Field in z2 of size 83^2

But one can always recover the point in terms of the basis \(\mathcal{F}(2,2)\) as follows:

[39]:
thD.with_theta_basis('F(2,2)')
[39]:
(78*z2 + 13 : 77*z2 + 26 : 43*z2 + 3 : 54*z2 + 67 : 77*z2 + 61 : 35*z2 + 2 : 31*z2 + 8 : 19*z2 + 38 : 25*z2 + 9 : z2 + 65 : 17*z2 + 75 : 18*z2 + 38 : 50*z2 + 17 : 41*z2 + 6 : 18*z2 + 48 : 39*z2 + 73)

Conversely, if we define the theta point

[24]:
th = [0]*(2**(2*g))
th[idx([0,0,0,0])] = z^1755
th[idx([0,0,1,1])] = z^1179
th[idx([0,0,1,0])] = z^977
th[idx([0,0,0,1])] = z^1105
th[idx([1,0,0,0])] = z^352
th[idx([1,0,0,1])] = z^1674
th[idx([0,1,0,0])] = z^2523
th[idx([1,1,0,0])] = z^5890
th[idx([0,1,1,0])] = z^5051
th[idx([1,1,1,1])] = z^5243
th[idx([0,1,0,1])] = z^4021
th[idx([0,1,1,1])] = z^4716
th[idx([1,0,1,0])] = z^139
th[idx([1,1,1,0])] = z^507
th[idx([1,0,1,1])] = z^2832
th[idx([1,1,0,1])] = z^3382
th = thc(th, basis='F(2,2)')

The function Level4ThetaPointToMumford returns the corresponding Mumford polynomials

[25]:
from avisogenies_sage.morphisms_level4 import Level4ThetaPointToMumford
u,v = Level4ThetaPointToMumford(a, rac, th.with_theta_basis('F(2,2)'))
D == J([u, v])
[25]:
True

In level 2

First we define the curve and its Kummer surface

A curve y² = f(x) is defined by a list a containing the roots of f(x); it is important that f be of odd degree and a be ordered (the Theta constants depend on this ordering).

First defined the curve and the Kummer Surface

[27]:
F = GF(83^2); z, = F.gens(); Fx.<x> = PolynomialRing(F)

g = 2;
a = [F(el) for el in [0,1,3,15,20]]
f = prod(x - al for al in a)
C = HyperellipticCurve(f); C
[27]:
Hyperelliptic Curve over Finite Field in z2 of size 83^2 defined by y^2 = x^5 + 44*x^4 + 28*x^3 + 23*x^2 + 70*x

The Theta constants of the Kummer surface.

[28]:
thc2 = [0]*(2**(2*g))
idx = lambda x : ZZ(x, 2)
thc2[idx([0,0,0,0])] = F(1)
thc2[idx([0,0,1,1])] = z^2982
thc2[idx([0,0,1,0])] = z^1554
thc2[idx([0,0,0,1])] = F(70)
thc2[idx([1,0,0,0])] = F(41)
thc2[idx([1,0,0,1])] = F(76)
thc2[idx([0,1,0,0])] = F(65)
thc2[idx([1,1,0,0])] = F(12)
thc2[idx([0,1,1,0])] = z^1218
thc2[idx([1,1,1,1])] = z^3066
thc2[idx([0,1,0,1])] = F(0)
thc2[idx([0,1,1,1])] = F(0)
thc2[idx([1,0,1,0])] = F(0)
thc2[idx([1,1,1,0])] = F(0)
thc2[idx([1,0,1,1])] = F(0)
thc2[idx([1,1,0,1])] = F(0)
thc2 = KummerVariety.with_theta_basis('F(2,2)^2', F, 2, g, thc2, curve=C, wp=a)

Now we can map points between Mumford and Theta representations. Consider the Mumford divisor defined by:

[31]:
J = Jacobian(C)
u = (x-43)*(x-10); v2 = (z^954*x + z^2518)^2;
D = J([u,v]); D
[31]:
(x^2 + 30*x + 15, y + (21*z2 + 71)*x + 10*z2 + 10)

And as we did for level 4, we compute the corresponding point.

[32]:
th2D = thc2(D)

Conversely, define the Theta functions

[34]:
th2 = [0]*(2**(2*g))
th2[idx([0,0,0,0])] = z^3608
th2[idx([0,0,1,1])] = z^5026
th2[idx([0,0,1,0])] = z^1654
th2[idx([0,0,0,1])] = z^6408
th2[idx([1,0,0,0])] = z^5576
th2[idx([1,0,0,1])] = z^3952
th2[idx([0,1,0,0])] = z^734
th2[idx([1,1,0,0])] = z^2674
th2[idx([0,1,1,0])] = z^3262
th2[idx([1,1,1,1])] = z^5436
th2[idx([0,1,0,1])] = F(82)
th2[idx([0,1,1,1])] = z^6258
th2[idx([1,0,1,0])] = z^4746
th2[idx([1,1,1,0])] = z^798
th2[idx([1,0,1,1])] = z^5082
th2[idx([1,1,0,1])] = F(2)
th2 = thc2(th2, basis='F(2,2)^2')

The function Level2ThetaPointToMumford returns the corresponding Mumford polynomials (u, v²)

[38]:
from avisogenies_sage.morphisms_level2 import Level2ThetaPointToMumford
uth,v2th = Level2ThetaPointToMumford(a, th2.with_theta_basis('F(2,2)^2'))
D == J([uth, sqrt(v2th)])
[38]:
True