Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiffhomepage
path: root/theta_lib/isogenies/Kani_clapoti.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/Kani_clapoti.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/Kani_clapoti.py')
-rw-r--r--theta_lib/isogenies/Kani_clapoti.py258
1 files changed, 258 insertions, 0 deletions
diff --git a/theta_lib/isogenies/Kani_clapoti.py b/theta_lib/isogenies/Kani_clapoti.py
new file mode 100644
index 0000000..66030e2
--- /dev/null
+++ b/theta_lib/isogenies/Kani_clapoti.py
@@ -0,0 +1,258 @@
+from sage.all import *
+import itertools
+
+from ..basis_change.kani_base_change import clapoti_cob_splitting_matrix
+from ..basis_change.base_change_dim4 import base_change_theta_dim4
+from ..theta_structures.Tuple_point import TuplePoint
+from ..theta_structures.montgomery_theta import null_point_to_montgomery_coeff, theta_point_to_montgomery_point
+from ..theta_structures.theta_helpers_dim4 import product_to_theta_points_dim4
+from ..utilities.supersingular import torsion_basis_to_Fp_rational_point
+from .Kani_gluing_isogeny_chain_dim4 import KaniClapotiGluing
+from .isogeny_chain_dim4 import IsogenyChainDim4
+
+class KaniClapotiIsog(IsogenyChainDim4):
+ r"""Class representing the 4-dimensional isogeny obtained via Kani's lemma F: Eu^2*Ev^2 --> Ea^2*A
+ where Ea=[\mf{a}]*E is the result of the ideal class group action by \mf{a} when given relevant
+ constants and torsion point information.
+
+ INPUT:
+ - Pu, Qu = phi_u(P, Q)\in Eu;
+ - Pv, Qv = phi_v*phi_{ck}*\hat{\phi}_{bk}(P, Q)\in Ev;
+ - gu, xu, yu, gv, xv, yv, Nbk, Nck, e: positive integers;
+ where:
+ * gu(xu^2+yu^2)Nbk+gv(xv^2+yv^2)Nck=2^e;
+ * gcd(u*Nbk,v*Nck)=1 with u:=gu(xu^2+yu^2) and v:=gv(xv^2+yv^2);
+ * xu and xv are odd and yu and yv are even;
+ * \mf{b}=\mf{be}*\mf{bk} is a product of ideals of norms Nbe and Nbk respectively,
+ where Nbe is a product of small Elkies primes;
+ * \mf{c}=\mf{ce}*\mf{ck} is a product of ideals of norms Nce and Nck respectively,
+ where Nbe is a product of small Elkies primes;
+ * phi_{bk}: E --> E1 and phi_{ck}: E --> E2 are induced by the action of
+ ideals \mf{bk} and \mf{ck} respectively;
+ * <P,Q>=E_1[2^{e+2}];
+ * phi_u: E1 --> Eu and phi_v: E2 --> Ev are gu and gv-isogenies respectively.
+
+ OUTPUT: F: Eu^2*Ev^2 --> Ea^2*A is the isogeny:
+
+ F := [[Phi_{bp}*\tilde{Phi}_u, Phi_{cp}*\tilde{Phi}_v],
+ [-Psi, \tilde{Phi}]]
+
+ obtained from the Kani isogeny diamond:
+
+ A --------------------Phi------------------> Ev^2
+ ^ ^
+ | |
+ | Phi_v
+ | |
+ Psi E2^2
+ | ^
+ | |
+ | \tilde{Phi}_{ck}
+ | |
+ Eu^2 --\tilde{Phi}_{u}--> E1^2 --Phi_{bk}--> Ea^2
+
+ where Phi_{bk}:=Diag(phi_{bk},phi_{bk}), Phi_{ck}:=Diag(phi_{ck},phi_{ck}),
+
+ Phi_u := [[xu, -yu],
+ [yu, xu]] * Diag(phi_u,phi_u)
+
+ Phi_v := [[xv, -yv],
+ [yv, xv]] * Diag(phi_v,phi_v)
+ """
+
+ def __init__(self,points,integers,strategy=None):
+ gu,xu,yu,gv,xv,yv,Nbk,Nck,e = integers
+ Pu,Qu,Pv,Qv = points
+ if gu*(xu**2+yu**2)*Nbk+gv*(xv**2+yv**2)*Nck!=2**e:
+ raise ValueError("Wrong parameters: gu(xu^2+yu^2)Nbk + gv(xv^2+yv^2)Nck != 2^e")
+ if gcd(ZZ(gu*(xu**2+yu**2)*Nbk),ZZ(gv*(xv**2+yv**2)*Nck))!=1:
+ raise ValueError("Non coprime parameters: gcd(gu(xu^2+yu^2)Nbk, gv(xv^2+yv^2)Nck) != 1")
+ if xu%2==0:
+ xu,yu=yu,xu
+ if xv%2==0:
+ xv,yv=yv,xv
+
+ self.Eu = Pu.curve()
+ self.Ev = Pv.curve()
+ Fp2 = parent(Pu[0])
+ Fp = Fp2.base_ring()
+
+ # Number of dimension 2 steps before dimension 4 gluing Am^2-->B
+ m=valuation(xv*yu-xu*yv,2)
+ integers=[gu,xu,yu,gv,xv,yv,Nbk,Nck,e,m]
+
+ points_mp3=[(2**(e-m-1))*P for P in points]
+ points_mp2=[2*P for P in points_mp3]
+ points_4=[(2**m)*P for P in points_mp2]
+
+ self.Ru_Fp = torsion_basis_to_Fp_rational_point(self.Eu,points_4[0],points_4[1],4)
+
+ self.gluing_isogeny_chain = KaniClapotiGluing(points_mp3,points_mp2,points_4,integers, coerce=Fp)
+
+ xuNbk = xu*Nbk
+ yuNbk = yu*Nbk
+ two_ep2 = 2**(e+2)
+ inv_Nbk = inverse_mod(Nbk,two_ep2)
+ u = gu*(xu**2+yu**2)
+ inv_u = inverse_mod(u,4)
+ lambxu = ((1-2**e*inv_u*inv_Nbk)*xu)%two_ep2
+ lambyu = ((1-2**e*inv_u*inv_Nbk)*yu)%two_ep2
+ xv_Nbk = (xv*inv_Nbk)%two_ep2
+ yv_Nbk = (yv*inv_Nbk)%two_ep2
+
+
+ B_Kpp = [TuplePoint(xuNbk*Pu,yuNbk*Pu,xv*Pv,yv*Pv),
+ TuplePoint(-yuNbk*Pu,xuNbk*Pu,-yv*Pv,xv*Pv),
+ TuplePoint(lambxu*Qu,lambyu*Qu,xv_Nbk*Qv,yv_Nbk*Qv),
+ TuplePoint(-lambyu*Qu,lambxu*Qu,-yv_Nbk*Qv,xv_Nbk*Qv)]
+
+ IsogenyChainDim4.__init__(self, B_Kpp, self.gluing_isogeny_chain, e, m, splitting=True, strategy=strategy)
+
+ # Splitting
+ M_split = clapoti_cob_splitting_matrix(integers)
+
+ self.N_split = base_change_theta_dim4(M_split,self.gluing_isogeny_chain.e4)
+
+ self.codomain_product = self._isogenies[-1]._codomain.base_change_struct(self.N_split)
+
+ # Extracting the group action image Ea=[\mathfrak{a}]*E from the codomain Ea^2*E'^2
+ self.theta_null_Ea, self.theta_null_Ep, self.Ea, self.Ep = self.extract_montgomery_curve()
+
+
+
+ def extract_montgomery_curve(self):
+
+ # Computing the theta null point of Ea
+ null_point=self.codomain_product.zero()
+ Fp2=parent(null_point[0])
+ Fp = Fp2.base_ring()
+ for i3, i4 in itertools.product([0,1],repeat=2):
+ if null_point[4*i3+8*i4]!=0:
+ i30=i3
+ i40=i4
+ theta_Ea_0=Fp(null_point[4*i3+8*i4])
+ theta_Ea_1=Fp(null_point[1+4*i3+8*i4])
+ break
+ for i1, i2 in itertools.product([0,1],repeat=2):
+ if null_point[i1+2*i2]!=0:
+ i10=i1
+ i20=i2
+ theta_Ep_0=Fp(null_point[i1+2*i2])
+ theta_Ep_1=Fp(null_point[i1+2*i2+4])
+ break
+
+ # Sanity check: is the codomain of F a product of the form Ea^2*E'^2 ?
+ theta_Ea=[Fp(theta_Ea_0),Fp(theta_Ea_1)]
+ theta_Ep=[Fp(theta_Ep_0),Fp(theta_Ep_1)]
+
+ theta_Ea2Ep2=[0 for i in range(16)]
+ for i1,i2,i3,i4 in itertools.product([0,1],repeat=4):
+ theta_Ea2Ep2[i1+2*i2+4*i3+8*i4]=theta_Ea[i1]*theta_Ea[i2]*theta_Ep[i3]*theta_Ep[i4]
+ theta_Ea2Ep2=self.codomain_product(theta_Ea2Ep2)
+
+ assert theta_Ea2Ep2.is_zero()
+
+ A_Ep = null_point_to_montgomery_coeff(theta_Ep_0,theta_Ep_1)
+ Ep = EllipticCurve([0,A_Ep,0,1,0])
+
+ ## ## Recovering Ea over Fp and not Fp2
+ ## self.find_Fp_rational_theta_struct_Ea()
+
+ ## theta_Ea = self.iso_Ea(theta_Ea)
+ ## A_Ea = null_point_to_montgomery_coeff(theta_Ea[0],theta_Ea[1])
+
+ ## # Sanity check : the curve Ea should be defined over Fp
+ ## # assert A_Ea[1] == 0
+
+ ## # Twisting Ea if necessary: if A_Ea+2 is not a square in Fp, then we take the twist (A_Ea --> -A_Ea)
+ ## p=self.Eu.base_field().characteristic()
+ ## self.twist = False
+ ## if (A_Ea+2)**((p-1)//2)==-1:
+ ## A_Ea = -A_Ea
+ ## self.twist = True
+
+ ## Ea = EllipticCurve([0,A_Ea,0,1,0])
+
+ A = null_point_to_montgomery_coeff(theta_Ea_0, theta_Ea_1)
+ Ab = null_point_to_montgomery_coeff(theta_Ea_0+theta_Ea_1, theta_Ea_0-theta_Ea_1)
+ Acan = min([A, -A, Ab, -Ab])
+ Acan = A
+ if (Acan == A or Acan == -A):
+ # 'Id' corresponds to the point on the twist
+ self.iso_type = 'Id'
+ else:
+ # 'Hadamard' corresponds to the point on the curve
+ self.iso_type = 'Hadamard'
+ if ((self.iso_type == 'Hadamard' and not (Acan+2).is_square()) or (self.iso_type == 'Id' and (Acan+2).is_square())):
+ Acan=-Acan
+ if (Acan == A or Acan == Ab):
+ self.twist = False
+ else:
+ self.twist = True
+ Ea = EllipticCurve([0,Acan,0,1,0])
+
+ # Find the dual null point
+ return theta_Ea, theta_Ep, Ea, Ep
+
+ def eval_rational_point_4_torsion(self):
+ T = TuplePoint(self.Ru_Fp,self.Eu(0),self.Ev(0),self.Ev(0))
+
+ FPu_4 = self.evaluate_isogeny(T)
+ FPu_4=self.codomain_product.base_change_coords(self.N_split,FPu_4)
+ FPu_4=product_to_theta_points_dim4(FPu_4)
+
+ return FPu_4[0]
+
+ def find_Fp_rational_theta_struct_Ea(self):
+ Pa = self.eval_rational_point_4_torsion()
+
+ HPa = (Pa[0]+Pa[1],Pa[0]-Pa[1])
+ i = self.Eu.base_field().gen()
+ self.i = i
+ iHPa = (Pa[0]+i*Pa[1],Pa[0]-i*Pa[1])
+
+ if Pa[0]==0 or Pa[1]==0:
+ self.iso_type='Id'
+ elif HPa[0]==0 or HPa[1]==0:
+ self.iso_type='Hadamard'
+ elif iHPa[0]==0 or iHPa[1]==0:
+ self.iso_type='iHadamard'
+ else:
+ raise ValueError("A rational theta point should be mapped to (0:1) or (1:0) after change of theta coordinates on Ea.")
+
+ def iso_Ea(self,P):
+ # Change of theta coordinates to obtain Fp-rational theta coordinates on Ea
+
+ if self.iso_type == 'Id':
+ return P
+ elif self.iso_type == 'Hadamard':
+ return (P[0]+P[1],P[0]-P[1])
+ else:
+ return (P[0]+self.i*P[1],P[0]-self.i*P[1])
+
+
+ def evaluate(self,P):
+ FP=self.evaluate_isogeny(P)
+ FP=self.codomain_product.base_change_coords(self.N_split,FP)
+
+ FP=product_to_theta_points_dim4(FP)
+ FP=TuplePoint([theta_point_to_montgomery_point(self.Ea,self.theta_null_Ea,self.iso_Ea(FP[0]),self.twist),
+ theta_point_to_montgomery_point(self.Ea,self.theta_null_Ea,self.iso_Ea(FP[1]),self.twist),
+ theta_point_to_montgomery_point(self.Ep,self.theta_null_Ep,FP[2]),
+ theta_point_to_montgomery_point(self.Ep,self.theta_null_Ep,FP[3])])
+
+ return FP
+
+ def __call__(self,P):
+ return self.evaluate(P)
+
+
+
+
+
+
+
+
+
+
+