Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiffhomepage
path: root/theta_lib/utilities/supersingular.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/utilities/supersingular.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/utilities/supersingular.py')
-rw-r--r--theta_lib/utilities/supersingular.py413
1 files changed, 413 insertions, 0 deletions
diff --git a/theta_lib/utilities/supersingular.py b/theta_lib/utilities/supersingular.py
new file mode 100644
index 0000000..e760c1b
--- /dev/null
+++ b/theta_lib/utilities/supersingular.py
@@ -0,0 +1,413 @@
+"""
+Helper functions for the supersingular elliptic curve computations in FESTA
+"""
+
+# =========================================== #
+# Compute points of order D and Torsion Bases #
+# =========================================== #
+
+"""
+Most of this code has been taken from:
+https://github.com/FESTA-PKE/FESTA-SageMath
+
+Copyright (c) 2023 Andrea Basso, Luciano Maino and Giacomo Pope.
+
+Functions with another Copyright mention are not from the above authors.
+"""
+
+# Sage Imports
+from sage.all import ZZ, GF, Integers
+
+# Local imports
+from .order import has_order_D
+from .discrete_log import weil_pairing_pari, ell_discrete_log_pari, discrete_log_pari
+from .fast_sqrt import sqrt_Fp2, sqrt_Fp
+
+
+
+
+def random_point(E):
+ """
+ Returns a random point on the elliptic curve E
+ assumed to be in Montgomery form with a base
+ field which characteristic p = 3 mod 4
+ """
+ A = E.a_invariants()[1]
+ if E.a_invariants() != (0,A,0,1,0):
+ raise ValueError("Function `generate_point` assumes the curve E is in the Montgomery model")
+
+ # Try 10000 times then give up, just protection
+ # for infinite loops
+ F = E.base_ring()
+ for _ in range(10000):
+ x = F.random_element()
+ y2 = x*(x**2 + A*x + 1)
+ if y2.is_square():
+ y = sqrt_Fp2(y2)
+ return E(x, y)
+
+ raise ValueError("Generated 10000 points, something is probably going wrong somewhere.")
+
+def generate_point(E, x_start=0):
+ """
+ Generate points on a curve E with x-coordinate
+ i + x for x in Fp and i is the generator of Fp^2
+ such that i^2 = -1.
+ """
+ F = E.base_ring()
+ one = F.one()
+
+ if x_start:
+ x = x_start + one
+ else:
+ x = F.gen() + one
+
+ A = E.a_invariants()[1]
+ if E.a_invariants() != (0,A,0,1,0):
+ raise ValueError("Function `generate_point` assumes the curve E is in the Montgomery model")
+
+ # Try 10000 times then give up, just protection
+ # for infinite loops
+ for _ in range(10000):
+ y2 = x*(x**2 + A*x + 1)
+ if y2.is_square():
+ y = sqrt_Fp2(y2)
+ yield E(x, y)
+ x += one
+
+ raise ValueError("Generated 10000 points, something is probably going wrong somewhere.")
+
+def generate_Fp_point(E):
+ """
+ Returns a random Fp-rational point on the
+ elliptic curve E assumed to be in Montgomery
+ form with a base field which characteristic
+ p = 3 mod 4 and coefficients defined over Fp.
+
+ Copyright (c) Pierrick Dartois 2025.
+ """
+
+ A = E.a_invariants()[1]
+ if E.a_invariants() != (0,A,0,1,0):
+ raise ValueError("Function `generate_Fp_point` assumes the curve E is in the Montgomery model")
+ if A[1] != 0:
+ raise ValueError("The curve E should be Fp-rational")
+
+ p=E.base_field().characteristic()
+ Fp=GF(p,proof=False)
+ one=Fp(1)
+ x=one
+
+ # Try 10000 times then give up, just protection
+ # for infinite loops
+ for _ in range(10000):
+ y2 = x*(x**2 + A*x + 1)
+ if y2.is_square():
+ y = sqrt_Fp(y2)
+ yield E(x, y)
+ x += one
+
+ raise ValueError("Generated 10000 points, something is probably going wrong somewhere.")
+
+def generate_point_order_D(E, D, x_start=0):
+ """
+ Input: An elliptic curve E / Fp2
+ An integer D dividing (p +1)
+ Output: A point P of order D.
+ """
+ p = E.base().characteristic()
+ n = (p + 1) // D
+
+ Ps = generate_point(E, x_start=x_start)
+ for G in Ps:
+ P = n * G
+
+ # Case when we randomly picked
+ # a point in the n-torsion
+ if P.is_zero():
+ continue
+
+ # Check that P has order exactly D
+ if has_order_D(P, D):
+ P._order = ZZ(D)
+ yield P
+
+ raise ValueError(f"Never found a point P of order D.")
+
+def generate_Fp_point_order_D(E, D):
+ """
+ Input: An elliptic curve E / Fp2
+ An integer D dividing (p +1)
+ Output: A point P of order D.
+
+ Copyright (c) Pierrick Dartois 2025.
+ """
+ p = E.base().characteristic()
+ n = (p + 1) // D
+ if D%2==0:
+ n=n//2
+
+ Ps = generate_Fp_point(E)
+ for G in Ps:
+ P = n * G
+
+ # Case when we randomly picked
+ # a point in the n-torsion
+ if P.is_zero():
+ continue
+
+ # Check that P has order exactly D
+ if has_order_D(P, D):
+ P._order = ZZ(D)
+ yield P
+
+ raise ValueError(f"Never found a point P of order D.")
+
+def compute_point_order_D(E, D, x_start=0):
+ """
+ Wrapper function around a generator which returns the first
+ point of order D
+ """
+ return generate_point_order_D(E, D, x_start=x_start).__next__()
+
+def compute_Fp_point_order_D(E, D):
+ """
+ Wrapper function around a generator which returns the first
+ point of order D
+
+ Copyright (c) Pierrick Dartois 2025.
+ """
+ return generate_Fp_point_order_D(E, D).__next__()
+
+def compute_linearly_independent_point_with_pairing(E, P, D, x_start=0):
+ """
+ Input: An elliptic curve E / Fp2
+ A point P ∈ E[D]
+ An integer D dividing (p +1)
+ Output: A point Q such that E[D] = <P, Q>
+ The Weil pairing e(P,Q)
+ """
+ Qs = generate_point_order_D(E, D, x_start=x_start)
+ for Q in Qs:
+ # Make sure the point is linearly independent
+ pair = weil_pairing_pari(P, Q, D)
+ if has_order_D(pair, D, multiplicative=True):
+ Q._order = ZZ(D)
+ return Q, pair
+ raise ValueError("Never found a point Q linearly independent to P")
+
+def compute_linearly_independent_point(E, P, D, x_start=0):
+ """
+ Wrapper function around `compute_linearly_independent_point_with_pairing`
+ which only returns a linearly independent point
+ """
+ Q, _ = compute_linearly_independent_point_with_pairing(E, P, D, x_start=x_start)
+ return Q
+
+def torsion_basis_with_pairing(E, D):
+ """
+ Generate basis of E(Fp^2)[D] of supersingular curve
+
+ While computing E[D] = <P, Q> we naturally compute the
+ Weil pairing e(P,Q), which we also return as in some cases
+ the Weil pairing is then used when solving the BiDLP
+ """
+ p = E.base().characteristic()
+
+ # Ensure D divides the curve's order
+ if (p + 1) % D != 0:
+ print(f"{ZZ(D).factor() = }")
+ print(f"{ZZ(p+1).factor() = }")
+ raise ValueError(f"D must divide the point's order")
+
+ P = compute_point_order_D(E, D)
+ Q, ePQ = compute_linearly_independent_point_with_pairing(E, P, D, x_start=P[0])
+
+ return P, Q, ePQ
+
+def torsion_basis(E, D):
+ """
+ Wrapper function around torsion_basis_with_pairing which only
+ returns the torsion basis <P,Q> = E[D]
+ """
+ P, Q, _ = torsion_basis_with_pairing(E, D)
+ return P, Q
+
+def torsion_basis_2f_frobenius(E,f):
+ """
+ Returns a basis (P, Q) of E[2^f] with pi_p(P)=P and pi_p(Q)=-Q,
+ pi_p being the p-th Frobenius endomorphism of E.
+
+ Copyright (c) Pierrick Dartois 2025.
+ """
+
+ P = compute_Fp_point_order_D(E, 2**f)
+
+ i = E.base_field().gen()
+ p = E.base_field().characteristic()
+
+ R = compute_linearly_independent_point(E, P, 2**f)
+ piR = E(R[0]**p,R[1]**p)
+ piR_p_R = piR + R
+
+ two_lamb = ell_discrete_log_pari(E,piR_p_R,P,2**f)
+ lamb = two_lamb//2
+ Q = R-lamb*P
+
+ piQ = E(Q[0]**p,Q[1]**p)
+
+ assert piQ == -Q
+
+ return P, Q
+
+def torsion_basis_to_Fp_rational_point(E,P,Q,D):
+ """
+ Returns an Fp-rational point R=[lamb]P+[mu]Q
+ of D-torsion given a D-torsion basis (P,Q) of E.
+
+ Copyright (c) Pierrick Dartois 2025.
+ """
+
+ p=E.base_field().characteristic()
+ piP = E(P[0]**p,P[1]**p)
+ piQ = E(Q[0]**p,Q[1]**p)
+
+ e_PQ = weil_pairing_pari(P,Q,D)
+ e_PpiP = weil_pairing_pari(P, piP, D)
+ e_QpiP = weil_pairing_pari(Q, piP, D)
+ e_PpiQ = weil_pairing_pari(P, piQ, D)
+ e_QpiQ = weil_pairing_pari(Q, piQ, D)
+
+ # pi(P)=[a]P+[b]Q, pi(Q)=[c]P+[d]Q
+ a = -discrete_log_pari(e_QpiP,e_PQ,D)
+ b = discrete_log_pari(e_PpiP,e_PQ,D)
+ c = -discrete_log_pari(e_QpiQ,e_PQ,D)
+ d = discrete_log_pari(e_PpiQ,e_PQ,D)
+
+ ZD = Integers(D)
+
+ if a%D==1:
+ mu = ZD(b/d)
+ R=P+mu*Q
+ elif c==0:
+ # c=0 and a!=1 => d=1 so pi(Q)=Q
+ R=Q
+ else:
+ # c!=0 and a!=1
+ mu = ZD((1-a)/c)
+ R=P+mu*Q
+
+ piR = E(R[0]**p,R[1]**p)
+
+ assert piR==R
+
+ return R
+
+# =========================================== #
+# Entangled torsion basis for fast E[2^k] #
+# =========================================== #
+
+def precompute_elligator_tables(F):
+ """
+ Precomputes tables of quadratic non-residue or
+ quadratic residue in Fp2. Used to compute entangled
+ torsion bases following https://ia.cr/2017/1143
+ """
+ u = 2*F.gen()
+
+ T1 = dict()
+ T2 = dict()
+ # TODO: estimate how large r should be
+ for r in range(1, 30):
+ v = 1 / (1 + u*r**2)
+ if v.is_square():
+ T2[r] = v
+ else:
+ T1[r] = v
+ return T1, T2
+
+def entangled_torsion_basis(E, elligator_tables, cofactor):
+ """
+ Optimised algorithm following https://ia.cr/2017/1143
+ which modifies the elligator method of hashing to points
+ to find points P,Q of order k*2^b. Clearing the cofactor
+ gives the torsion basis without checking the order or
+ computing a Weil pairing.
+
+ To do this, we need tables TQNR, TQR of pairs of values
+ (r, v) where r is an integer and v = 1/(1 + ur^2) where
+ v is either a quadratic non-residue or quadratic residue
+ in Fp2 and u = 2i = 2*sqrt(-1).
+ """
+ F = E.base_ring()
+ p = F.characteristic()
+ p_sqrt = (p+1)//4
+
+ i = F.gen()
+ u = 2 * i
+ u0 = 1 + i
+
+ TQNR, TQR = elligator_tables
+
+ # Pick the look up table depending on whether
+ # A = a + ib is a QR or NQR
+ A = E.a_invariants()[1]
+ if (0,A,0,1,0) != E.a_invariants():
+ raise ValueError("The elliptic curve E must be in Montgomery form")
+ if A.is_square():
+ T = TQNR
+ else:
+ T = TQR
+
+ # Look through the table to find point with
+ # rational (x,y)
+ y = None
+ for r, v in T.items():
+ x = -A * v
+
+ t = x * (x**2 + A*x + 1)
+
+ # Break when we find rational y: t = y^2
+ c, d = t.list()
+ z = c**2 + d**2
+ s = z**p_sqrt
+ if s**2 == z:
+ y = sqrt_Fp2(t)
+ break
+
+ if y is None:
+ raise ValueError("Never found a y-coordinate, increase the lookup table size")
+
+ z = (c + s) // 2
+ alpha = z**p_sqrt
+ beta = d / (2*alpha)
+
+ if alpha**2 == z:
+ y = F([alpha, beta])
+ else:
+ y = -F([beta, alpha])
+
+ S1 = E([x, y])
+ S2 = E([u*r**2*x, u0*r*y])
+
+ return cofactor*S1, cofactor*S2
+
+# =============================================== #
+# Ensure Basis <P,Q> of E[2^k] has (0,0) under Q #
+# =============================================== #
+
+def fix_torsion_basis_renes(P, Q, k):
+ """
+ Set the torsion basis P,Q such that
+ 2^(k-1)Q = (0,0) to ensure that (0,0)
+ is never a kernel of a two isogeny
+ """
+ cofactor = 2**(k-1)
+
+ R = cofactor*P
+ if R[0] == 0:
+ return Q, P
+ R = cofactor*Q
+ if R[0] == 0:
+ return P, Q
+ return P, P + Q