Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiffhomepage
path: root/theta_lib/isogenies/gluing_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/gluing_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/gluing_isogeny_dim4.py')
-rw-r--r--theta_lib/isogenies/gluing_isogeny_dim4.py200
1 files changed, 200 insertions, 0 deletions
diff --git a/theta_lib/isogenies/gluing_isogeny_dim4.py b/theta_lib/isogenies/gluing_isogeny_dim4.py
new file mode 100644
index 0000000..cd738c9
--- /dev/null
+++ b/theta_lib/isogenies/gluing_isogeny_dim4.py
@@ -0,0 +1,200 @@
+from sage.all import *
+
+from ..theta_structures.Theta_dim4 import ThetaStructureDim4
+from ..theta_structures.theta_helpers_dim4 import hadamard, squared, batch_inversion, multindex_to_index
+from .tree import Tree
+from .isogeny_dim4 import IsogenyDim4, DualIsogenyDim4
+
+def proj_equal(P1, P2):
+ if len(P1) != len(P2):
+ return False
+ for i in range(0, len(P1)):
+ if P1[i]==0:
+ if P2[i] != 0:
+ return False
+ else:
+ break
+ r=P1[i]
+ s=P2[i]
+ for i in range(0, len(P1)):
+ if P1[i]*s != P2[i]*r:
+ return False
+ return True
+
+class GluingIsogenyDim4(IsogenyDim4):
+ def __init__(self,domain,L_K_8,L_K_8_ind, coerce=None):
+ r"""
+ Input:
+ - domain: a ThetaStructureDim4.
+ - L_K_8: list of points of 8-torsion in the kernel.
+ - L_K_8_ind: list of corresponding multindices (i0,i1,i2,i3) of points in L_K_8
+ (L_K_8[i]=i0*P0+i1*P1+i2*P2+i3*P3, where (P0,..,P3) is a basis of K_8 (compatible with
+ the canonical basis of K_2) and L_K_8_ind[i]=(i0,i1,i2,i3)).
+ """
+
+ if not isinstance(domain, ThetaStructureDim4):
+ raise ValueError("Argument domain should be a ThetaStructureDim4 object.")
+ self._domain = domain
+ self._precomputation=None
+ self._coerce=coerce
+ self._special_compute_codomain(L_K_8,L_K_8_ind)
+
+ #a_i2=squared(self._domain.zero())
+ #HB_i2=hadamard(squared(hadamard(self._codomain.zero())))
+ #for i in range(16):
+ #print(HB_i2[i]/a_i2[i])
+
+ def _special_compute_codomain(self,L_K_8,L_K_8_ind):
+ r"""
+ Input:
+ - L_K_8: list of points of 8-torsion in the kernel.
+ - L_K_8_ind: list of corresponding multindices (i0,i1,i2,i3) of points in L_K_8
+ (L_K_8[i]=i0*P0+i1*P1+i2*P2+i3*P3, where (P0,..,P3) is a basis of K_8 (compatible with
+ the canonical basis of K_2) and L_K_8_ind[i]=(i0,i1,i2,i3)).
+
+ Output:
+ - codomain of the isogeny.
+ Also initializes self._precomputation, containing the inverse of theta-constants.
+ """
+ HSK_8=[hadamard(squared(P.coords())) for P in L_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(len(L_K_8)):
+ 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^multindex_to_index(L_K_8_ind[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),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(len(L_K_8)):
+ jpk=j^multindex_to_index(L_K_8_ind[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:
+ if self._coerce:
+ O_coords[t[1]]=self._coerce(t[0])
+ else:
+ O_coords[t[1]]=t[0]
+
+ # Precomputation
+ # TODO: optimize inversions and give precomputation to the codomain _arithmetic_precomputation
+ 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
+
+ for k in range(len(L_K_8)):
+ for j in range(16):
+ jpk=j^multindex_to_index(L_K_8_ind[k])
+ assert HSK_8[k][j]*O_coords[jpk]==HSK_8[k][jpk]*O_coords[j]
+
+ assert proj_equal(squared(self._domain._null_point.coords()), hadamard(squared(O_coords)))
+
+ self._codomain=ThetaStructureDim4(hadamard(O_coords),null_point_dual=O_coords)
+
+ def special_image(self,P,L_trans,L_trans_ind):
+ r"""Used when we cannot evaluate the isogeny self because the codomain has zero
+ dual theta constants.
+
+ Input:
+ - P: ThetaPointDim4 of the domain.
+ - L_trans: list of translates of P+T of P by points of 4-torsion T above the kernel.
+ - L_trans_ind: list of indices of the translation 4-torsion points T.
+ If L_trans[i]=\sum i_j*B_K4[j] then L_trans_ind[j]=\sum 2**j*i_j.
+
+ Output:
+ - the image of P by the isogeny self.
+ """
+ HS_P=hadamard(squared(P.coords()))
+ HSL_trans=[hadamard(squared(Q.coords())) for Q in L_trans]
+ O_coords=self._codomain.null_point_dual()
+
+ # L_lambda_inv: List of inverses of lambda_i such that:
+ # HS(P+Ti)=(lambda_i*U_{chi.chi_i,0}(f(P))*U_{chi,0}(0))_chi.
+ L_lambda_inv_num=[]
+ L_lambda_inv_denom=[]
+
+ for k in range(len(L_trans)):
+ for j in range(16):
+ jpk=j^L_trans_ind[k]
+ if HSL_trans[k][j]!=0 and O_coords[jpk]!=0:
+ L_lambda_inv_num.append(HS_P[jpk]*O_coords[j])
+ L_lambda_inv_denom.append(HSL_trans[k][j]*O_coords[jpk])
+ break
+ L_lambda_inv_denom=batch_inversion(L_lambda_inv_denom)
+ L_lambda_inv=[L_lambda_inv_num[i]*L_lambda_inv_denom[i] for i in range(len(L_trans))]
+
+ for k in range(len(L_trans)):
+ for j in range(16):
+ jpk=j^L_trans_ind[k]
+ assert HS_P[jpk]*O_coords[j]==L_lambda_inv[k]*HSL_trans[k][j]*O_coords[jpk]
+
+ U_fP=[]
+ for i in range(16):
+ if self._precomputation[i]!=None:
+ U_fP.append(self._precomputation[i]*HS_P[i])
+ else:
+ for k in range(len(L_trans)):
+ ipk=i^L_trans_ind[k]
+ if self._precomputation[ipk]!=None:
+ U_fP.append(self._precomputation[ipk]*HSL_trans[k][ipk]*L_lambda_inv[k])
+ break
+
+ fP=hadamard(U_fP)
+ if self._coerce:
+ fP=[self._coerce(x) for x in fP]
+
+ return self._codomain(fP)
+
+ def dual(self):
+ return DualIsogenyDim4(self._codomain,self._domain, hadamard=False)