Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiffhomepage
path: root/theta_lib/theta_structures/Theta_dim4.py
diff options
context:
space:
mode:
authorRyan Rueger <git@rueg.re>2025-03-01 20:25:41 +0100
committerRyan Rueger <git@rueg.re>2025-03-01 22:11:11 +0100
commitd40de259097c5e8d8fd35539560ca7c3d47523e7 (patch)
tree18e0f94350a2329060c2a19b56b0e3e2fdae56f1 /theta_lib/theta_structures/Theta_dim4.py
downloadpegasis-d40de259097c5e8d8fd35539560ca7c3d47523e7.tar.gz
pegasis-d40de259097c5e8d8fd35539560ca7c3d47523e7.tar.bz2
pegasis-d40de259097c5e8d8fd35539560ca7c3d47523e7.zip
Initial Commit
Co-Authored-By: Damien Robert <Damien.Olivier.Robert+git@gmail.com> Co-Authored-By: Frederik Vercauteren <frederik.vercauteren@gmail.com> Co-Authored-By: Jonathan Komada Eriksen <jonathan.eriksen97@gmail.com> Co-Authored-By: Pierrick Dartois <pierrickdartois@icloud.com> Co-Authored-By: Riccardo Invernizzi <nidadoni@gmail.com> Co-Authored-By: Ryan Rueger <git@rueg.re> [0.01s] Co-Authored-By: Benjamin Wesolowski <benjamin@pasch.umpa.ens-lyon.fr> Co-Authored-By: Arthur Herlédan Le Merdy <ahlm@riseup.net> Co-Authored-By: Boris Fouotsa <tako.fouotsa@epfl.ch>
Diffstat (limited to 'theta_lib/theta_structures/Theta_dim4.py')
-rw-r--r--theta_lib/theta_structures/Theta_dim4.py351
1 files changed, 351 insertions, 0 deletions
diff --git a/theta_lib/theta_structures/Theta_dim4.py b/theta_lib/theta_structures/Theta_dim4.py
new file mode 100644
index 0000000..7851c56
--- /dev/null
+++ b/theta_lib/theta_structures/Theta_dim4.py
@@ -0,0 +1,351 @@
+from sage.all import *
+from sage.structure.element import get_coercion_model
+
+from .theta_helpers_dim4 import (
+ hadamard,
+ batch_inversion,
+ product_theta_point_dim4,
+ product_to_theta_points_dim4,
+ product_theta_point_dim2_dim4,
+ product_to_theta_points_dim4_dim2,
+ act_point,
+ squared,
+)
+from .Theta_dim1 import ThetaStructureDim1
+from .Tuple_point import TuplePoint
+from ..basis_change.base_change_dim4 import (
+ apply_base_change_theta_dim4,
+ random_symplectic_matrix,
+ base_change_theta_dim4,
+)
+
+cm = get_coercion_model()
+
+
+class ThetaStructureDim4:
+ def __init__(self,null_point,null_point_dual=None,inv_null_point_dual_sq=None):
+ r"""
+ INPUT:
+ - null_point: theta-constants.
+ - inv_null_point_dual_sq: inverse of the squares of dual theta-constants, if provided
+ (meant to prevent duplicate computation, since this data is already computed when the
+ codomain of an isogeny is computed).
+ """
+ if not len(null_point) == 16:
+ raise ValueError("Entry null_point should have 16 coordinates.")
+
+ self._base_ring = cm.common_parent(*(c.parent() for c in null_point))
+ self._point = ThetaPointDim4
+ self._null_point = self._point(self, null_point)
+ self._null_point_dual=null_point_dual
+ self._inv_null_point=None
+ self._inv_null_point_dual_sq=inv_null_point_dual_sq
+
+ def null_point(self):
+ """
+ """
+ return self._null_point
+
+ def null_point_dual(self):
+ if self._null_point_dual==None:
+ self._null_point_dual=hadamard(self._null_point.coords())
+ return self._null_point_dual
+
+ def base_ring(self):
+ """
+ """
+ return self._base_ring
+
+ def zero(self):
+ """
+ """
+ return self.null_point()
+
+ def zero_dual(self):
+ return self.null_point_dual()
+
+ def __repr__(self):
+ return f"Theta structure over {self.base_ring()} with null point: {self.null_point()}"
+
+ def __call__(self,coords):
+ return self._point(self,coords)
+
+ def act_null(self,I,J):
+ r"""
+ Point of 2-torsion.
+
+ INPUT:
+ - I, J: two 4-tuples of indices in {0,1}.
+
+ OUTPUT: the action of (I,\chi_J) on the theta null point given by:
+ (I,\chi_J)*P=(\chi_J(I+K)^{-1}P[I+K])_K
+ """
+ return self.null_point().act_point(I,J)
+
+ def is_K2(self,B):
+ r"""
+ Given a symplectic decomposition A[2]=K_1\oplus K_2 canonically
+ induced by the theta-null point, determines if B is the canonical
+ basis of K_2 given by act_nul(0,\delta_i)_{1\leq i\leq 4}.
+
+ INPUT:
+ - B: Basis of 4 points of 2-torsion.
+
+ OUTPUT: Boolean True if and only if B is the canonical basis of K_2.
+ """
+ I0=(0,0,0,0)
+ if B[0]!=self.act_null(I0,(1,0,0,0)):
+ return False
+ if B[1]!=self.act_null(I0,(0,1,0,0)):
+ return False
+ if B[2]!=self.act_null(I0,(0,0,1,0)):
+ return False
+ if B[3]!=self.act_null(I0,(0,0,0,1)):
+ return False
+ return True
+
+ def base_change_struct(self,N):
+ null_coords=self.null_point().coords()
+ new_null_coords=apply_base_change_theta_dim4(N,null_coords)
+ return ThetaStructureDim4(new_null_coords)
+
+ def base_change_coords(self,N,P):
+ coords=P.coords()
+ new_coords=apply_base_change_theta_dim4(N,coords)
+ return self.__call__(new_coords)
+
+ #@cached_method
+ def _arithmetic_precomputation(self):
+ r"""
+ Initializes the precomputation containing the inverse of the theta-constants in standard
+ and dual (Hadamard transformed) theta-coordinates. Assumes no theta-constant is zero.
+ """
+ O=self.null_point()
+ if all([O[k]!=0 for k in range(16)]):
+ self._inv_null_point=batch_inversion(O.coords())
+ if self._inv_null_point_dual_sq==None:
+ U_chi_0_sq=hadamard(squared(O.coords()))
+ if all([U_chi_0_sq[k]!=0 for k in range(16)]):
+ self._inv_null_point_dual_sq=batch_inversion(U_chi_0_sq)
+ self._arith_base_change=False
+ else:
+ self._arith_base_change=True
+ self._arithmetic_base_change()
+ #print("Zero dual theta constants.\nRandom symplectic base change is being used for duplication.\nDoublings are more costly than expected.")
+ else:
+ self._arith_base_change=True
+ self._arithmetic_base_change()
+ #print("Zero theta constants.\nRandom symplectic base change is being used for duplication.\nDoublings are more costly than expected.")
+
+ def _arithmetic_base_change(self,max_iter=50):
+ F=self._base_ring
+ if F.degree() == 2:
+ i=self._base_ring.gen()
+ else:
+ assert(F.degree() == 1)
+ Fp2 = GF((F.characteristic(), 2), name='i', modulus=var('x')**2 + 1)
+ i=Fp2.gen()
+
+ count=0
+ O=self.null_point()
+ while count<max_iter:
+ count+=1
+ M=random_symplectic_matrix(4)
+ N=base_change_theta_dim4(M,i)
+
+ NO=apply_base_change_theta_dim4(N,O)
+ NU_chi_0_sq=hadamard(squared(NO))
+
+ if all([NU_chi_0_sq[k]!=0 for k in range(16)]) and all([NO[k]!=0 for k in range(16)]):
+ self._arith_base_change_matrix=N
+ self._arith_base_change_matrix_inv=N.inverse()
+ self._inv_null_point=batch_inversion(NO)
+ self._inv_null_point_dual_sq=batch_inversion(NU_chi_0_sq)
+ break
+
+ def has_suitable_doubling(self):
+ O=self.null_point()
+ UO=hadamard(O.coords())
+ if all([O[k]!=0 for k in range(16)]) and all([UO[k]!=0 for k in range(16)]):
+ return True
+ else:
+ return False
+
+ def hadamard(self):
+ return ThetaStructureDim4(self.null_point_dual())
+
+ def hadamard_change_coords(self,P):
+ new_coords=hadamard(P)
+ return self.__call__(new_coords)
+
+
+class ProductThetaStructureDim1To4(ThetaStructureDim4):
+ def __init__(self,*args):
+ r"""Defines the product theta structure at level 2 of 4 elliptic curves.
+
+ Input: Either
+ - 4 theta structures of dimension 1: T0, T1, T2, T3;
+ - 4 elliptic curves: E0, E1, E2, E3.
+ - 4 elliptic curves E0, E1, E2, E3 and their respective canonical 4-torsion basis B0, B1, B2, B3.
+ """
+ if len(args)==4:
+ theta_structures=list(args)
+ for k in range(4):
+ if not isinstance(theta_structures[k],ThetaStructureDim1):
+ try:
+ theta_structures[k]=ThetaStructureDim1(theta_structures[k])
+ except:
+ pass
+ elif len(args)==8:
+ theta_structures=[ThetaStructureDim1(args[k],args[4+k][0],args[4+k][1]) for k in range(4)]
+ else:
+ raise ValueError("4 or 8 arguments expected but {} were given.\nYou should enter a list of 4 elliptic curves or ThetaStructureDim1\nor a list of 4 elliptic curves with a 4-torsion basis for each of them.".format(len(args)))
+
+ self._theta_structures=theta_structures
+
+ null_point=product_theta_point_dim4(theta_structures[0].zero().coords(),theta_structures[1].zero().coords(),
+ theta_structures[2].zero().coords(),theta_structures[3].zero().coords())
+
+ ThetaStructureDim4.__init__(self,null_point)
+
+ def product_theta_point(self,theta_points):
+ return self._point(self,product_theta_point_dim4(theta_points[0].coords(),theta_points[1].coords(),
+ theta_points[2].coords(),theta_points[3].coords()))
+
+ def __call__(self,point):
+ if isinstance(point,TuplePoint):
+ theta_points=[]
+ theta_structures=self._theta_structures
+ for i in range(4):
+ theta_points.append(theta_structures[i](point[i]))
+ return self.product_theta_point(theta_points)
+ else:
+ return self._point(self,point)
+
+ def to_theta_points(self,P):
+ theta_coords=product_to_theta_points_dim4(P)
+ theta_points=[self._theta_structures[i](theta_coords[i]) for i in range(4)]
+ return theta_points
+
+ def to_tuple_point(self,P):
+ theta_points=self.to_theta_points(P)
+ montgomery_points=[self._theta_structures[i].to_montgomery_point(theta_points[i]) for i in range(4)]
+ return TuplePoint(montgomery_points)
+
+class ProductThetaStructureDim2To4(ThetaStructureDim4):
+ def __init__(self,theta1,theta2):
+ self._theta_structures=(theta1,theta2)
+
+ null_point=product_theta_point_dim2_dim4(theta1.zero().coords(),theta2.zero().coords())
+
+ ThetaStructureDim4.__init__(self,null_point)
+
+ def product_theta_point(self,P1,P2):
+ return self._point(self,product_theta_point_dim2_dim4(P1.coords(),P2.coords()))
+
+ def __call__(self,point):
+ return self._point(self,point)
+
+ def to_theta_points(self,P):
+ theta_coords=product_to_theta_points_dim4_dim2(P)
+ theta_points=[self._theta_structures[i](theta_coords[i]) for i in range(2)]
+ return theta_points
+
+class ThetaPointDim4:
+ def __init__(self, parent, coords):
+ """
+ """
+ if not isinstance(parent, ThetaStructureDim4):
+ raise ValueError("Entry parent should be a ThetaStructureDim4 object.")
+
+ self._parent = parent
+ self._coords = tuple(coords)
+
+ def parent(self):
+ """
+ """
+ return self._parent
+
+ def theta(self):
+ """
+ """
+ return self.parent()
+
+ def coords(self):
+ """
+ """
+ return self._coords
+
+ def is_zero(self):
+ """
+ """
+ return self == self.parent().zero()
+
+ def __eq__(self, other):
+ P=self.coords()
+ Q=other.coords()
+
+ k0=0
+ while k0<15 and P[k0]==0:
+ k0+=1
+
+ for l in range(16):
+ if P[l]*Q[k0]!=Q[l]*P[k0]:
+ return False
+ return True
+
+ def __repr__(self):
+ return f"Theta point with coordinates: {self.coords()}"
+
+ def __getitem__(self,i):
+ return self._coords[i]
+
+ def scale(self,lamb):
+ if lamb==0:
+ raise ValueError("Entry lamb should be non-zero.")
+
+ P=self.coords()
+ return self._parent([lamb*x for x in P])
+
+ def act_point(self,I,J):
+ r"""
+ Translation by a point of 2-torsion.
+
+ Input:
+ - I, J: two 4-tuples of indices in {0,1}.
+
+ Output: the action of (I,\chi_J) on P given by:
+ (I,\chi_J)*P=(\chi_J(I+K)^{-1}P[I+K])_K
+ """
+ return self._parent(act_point(self._coords,I,J))
+
+ def double(self):
+ ## This formula is projective.
+ ## Works only when theta constants are non-zero.
+ P=self.coords()
+ if self.parent()._inv_null_point==None or self.parent()._inv_null_point_dual_sq==None:
+ self.parent()._arithmetic_precomputation()
+ inv_O,inv_U_chi_0_sq=self.parent()._inv_null_point,self.parent()._inv_null_point_dual_sq
+
+ if self.parent()._arith_base_change:
+ P=apply_base_change_theta_dim4(self.parent()._arith_base_change_matrix,P)
+
+ U_chi_P=squared(hadamard(squared(P)))
+ for chi in range(16):
+ U_chi_P[chi]*=inv_U_chi_0_sq[chi]
+
+ theta_2P = list(hadamard(U_chi_P))
+ for i in range(16):
+ theta_2P[i] *= inv_O[i]
+
+ if self.parent()._arith_base_change:
+ theta_2P=apply_base_change_theta_dim4(self.parent()._arith_base_change_matrix_inv,theta_2P)
+
+ return self._parent(theta_2P)
+
+ def double_iter(self,n):
+ ## Computes 2**n*self
+ Q=self
+ for i in range(n):
+ Q=Q.double()
+ return Q