Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiffhomepage
path: root/theta_lib/isogenies/isogeny_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/isogenies/isogeny_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/isogenies/isogeny_dim4.py')
-rw-r--r--theta_lib/isogenies/isogeny_dim4.py162
1 files changed, 162 insertions, 0 deletions
diff --git a/theta_lib/isogenies/isogeny_dim4.py b/theta_lib/isogenies/isogeny_dim4.py
new file mode 100644
index 0000000..2f483bf
--- /dev/null
+++ b/theta_lib/isogenies/isogeny_dim4.py
@@ -0,0 +1,162 @@
+from sage.all import *
+
+from ..theta_structures.Theta_dim4 import ThetaStructureDim4
+from ..theta_structures.theta_helpers_dim4 import hadamard, squared, batch_inversion
+from .tree import Tree
+
+class IsogenyDim4:
+ def __init__(self,domain,K_8,codomain=None,precomputation=None):
+ r"""
+ Input:
+ - domain: a ThetaStructureDim4.
+ - K_8: a list of 4 points of 8-torision (such that 4*K_8 is a kernel basis), used to compute the codomain.
+ - codomain: a ThetaStructureDim4 (for the codomain, used only when K_8 is None).
+ - precomputation: list of inverse of dual theta constants of the codomain, used to compute the image.
+ """
+
+ if not isinstance(domain, ThetaStructureDim4):
+ raise ValueError("Argument domain should be a ThetaStructureDim4 object.")
+ self._domain = domain
+ self._precomputation=None
+ if K_8!=None:
+ self._compute_codomain(K_8)
+ else:
+ self._codomain=codomain
+ self._precomputation=precomputation
+
+ def _compute_codomain(self,K_8):
+ r"""
+ Input:
+ - K_8: a list of 4 points of 8-torision (such that 4*K_8 is a kernel basis).
+
+ Output:
+ - codomain of the isogeny.
+ Also initializes self._precomputation, containing the inverse of theta-constants.
+ """
+ HSK_8=[hadamard(squared(P.coords())) for P in K_8]
+
+ # Choice of reference index j_0<->chi_0 corresponding to a non-vanishing theta-constant.
+ found_tree=False
+ j_0=0
+ while not found_tree:
+ found_k0=False
+ for k in range(4):
+ if j_0>15:
+ raise NotImplementedError("The codomain of this 2-isogeny could not be computed.\nWe may have encountered a product of abelian varieties\nsomewhere unexpected along the chain.\nThis is exceptionnal and should not happen in larger characteristic.")
+ if HSK_8[k][j_0]!=0:
+ k_0=k
+ found_k0=True
+ break
+ if not found_k0:
+ j_0+=1
+ else:
+ j0pk0=j_0^(2**k_0)
+ # List of tuples of indices (index chi of the denominator: HS(f(P_k))_chi,
+ #index chi.chi_k of the numerator: HS(f(P_k))_chi.chi_k, index k).
+ L_ratios_ind=[(j_0,j0pk0,k_0)]
+ L_covered_ind=[j_0,j0pk0]
+
+ # Tree containing the the theta-null points indices as nodes and the L_ratios_ind reference indices as edges.
+ tree_ratios=Tree(j_0)
+ tree_ratios.add_child(Tree(j0pk0),k_0)
+
+ # Filling in the tree
+ tree_filled=False
+ while not tree_filled:
+ found_j=False
+ for j in L_covered_ind:
+ for k in range(4):
+ jpk=j^(2**k)
+ if jpk not in L_covered_ind and HSK_8[k][j]!=0:
+ L_covered_ind.append(jpk)
+ L_ratios_ind.append((j,jpk,k))
+ tree_j=tree_ratios.look_node(j)
+ tree_j.add_child(Tree(jpk),len(L_ratios_ind)-1)
+ found_j=True
+ break
+ if found_j:
+ break
+ if not found_j or len(L_covered_ind)==16:
+ tree_filled=True
+ if len(L_covered_ind)!=16:
+ j_0+=1
+ else:
+ found_tree=True
+
+ L_denom=[HSK_8[t[2]][t[0]] for t in L_ratios_ind]
+ L_denom_inv=batch_inversion(L_denom)
+ L_num=[HSK_8[t[2]][t[1]] for t in L_ratios_ind]
+ L_ratios=[L_num[i]*L_denom_inv[i] for i in range(15)]
+
+ L_coords_ind=tree_ratios.edge_product(L_ratios)
+
+ O_coords=[ZZ(0) for i in range(16)]
+ for t in L_coords_ind:
+ O_coords[t[1]]=t[0]
+
+ # Precomputation
+ # TODO: optimize inversions
+ L_prec=[]
+ L_prec_ind=[]
+ for i in range(16):
+ if O_coords[i]!=0:
+ L_prec.append(O_coords[i])
+ L_prec_ind.append(i)
+ L_prec_inv=batch_inversion(L_prec)
+ precomputation=[None for i in range(16)]
+ for i in range(len(L_prec)):
+ precomputation[L_prec_ind[i]]=L_prec_inv[i]
+
+ self._precomputation=precomputation
+ # Assumes there is no zero theta constant. Otherwise, squared(precomputation) will raise an error (None**2 does not exist)
+ self._codomain=ThetaStructureDim4(hadamard(O_coords),null_point_dual=O_coords)
+
+ def codomain(self):
+ return self._codomain
+
+ def domain(self):
+ return self._domain
+
+ def image(self,P):
+ HS_P=list(hadamard(squared(P.coords())))
+
+ for i in range(16):
+ HS_P[i] *=self._precomputation[i]
+
+ return self._codomain(hadamard(HS_P))
+
+ def dual(self):
+ return DualIsogenyDim4(self._codomain,self._domain, hadamard=True)
+
+ def __call__(self,P):
+ return self.image(P)
+
+
+class DualIsogenyDim4:
+ def __init__(self,domain,codomain,hadamard=True):
+ # domain and codomain are respectively the domain and codomain of \tilde{f}: domain-->codomain,
+ # so respectively the codomain and domain of f: codomain-->domain.
+ # By convention, domain input is given in usual coordinates (ker(\tilde{f})=K_2).
+ # codomain is in usual coordinates if hadamard, in dual coordinates otherwise.
+ self._domain=domain.hadamard()
+ self._hadamard=hadamard
+ if hadamard:
+ self._codomain=codomain.hadamard()
+ self._precomputation=batch_inversion(codomain.zero().coords())
+ else:
+ self._codomain=codomain
+ self._precomputation=batch_inversion(codomain.zero().coords())
+
+ def image(self,P):
+ # When ker(f)=K_2, ker(\tilde{f})=K_1 so ker(\tilde{f})=K_2 after hadamard transformation of the
+ # new domain (ex codomain)
+ HS_P=list(hadamard(squared(P.coords())))
+ for i in range(16):
+ HS_P[i] *=self._precomputation[i]
+ if self._hadamard:
+ return self._codomain(hadamard(HS_P))
+ else:
+ return self._codomain(HS_P)
+
+ def __call__(self,P):
+ return self.image(P)