Skip to content

Chapter 22: Isogenies for programmers

Chapters 19 through 21 built cryptosystems from linear codes over GF(2)\text{GF}(2). McEliece’s 1978 scheme hides a structured code behind a random-looking generator matrix; HQC keeps a random-looking code but gives it quasi-cyclic structure, so a single row describes a whole circulant block. Both rest on the hardness of decoding a random linear code.

This chapter moves to a different mathematical object: maps between elliptic curves. An isogeny is a group homomorphism from one elliptic curve to another that is also a rational map. Given a finite subgroup HH of a curve EE, there is a unique isogeny ϕ:EE/H\phi: E \to E/H with kernel HH. Computing ϕ\phi is efficient (Velu gave explicit formulas in 1971). In the supersingular graph at cryptographic parameters, recovering a low-degree isogeny path between two given curves, without auxiliary information about the connecting kernel, is believed to be hard.

SIDH (Supersingular Isogeny Diffie-Hellman) built a key exchange on this hardness assumption in 2011 (De Feo et al., 2014). In July 2022, Castryck and Decru broke it by exploiting auxiliary information that the protocol published: heuristic polynomial time when the starting curve’s endomorphism ring is known, which holds for SIKE’s starting curve (Castryck & Decru, 2023). The break made SIKE non-viable as a NIST candidate. The SIKE team’s final postscript states that SIKE and SIDH are insecure and should not be used (Jao et al., 2022). The attack does not apply to CSIDH or SQIsign (Chapter 23): they are not SIDH-style protocols and do not publish the secret isogeny’s auxiliary torsion-point images, the data Castryck-Decru exploit. That separates them from the SIDH break; it is not a blanket proof of their security.

This chapter builds a toy SIDH from scratch at p=431p = 431, then explains why the break works.

The curve E0:y2=x3+xE_0: y^2 = x^3 + x over F431\mathbb{F}_{431} has 432=2433432 = 2^4 \cdot 3^3 rational points. The point T=(0,0)T = (0, 0) satisfies 2T=O2T = \mathcal{O} (it is a 2-torsion point, since yT=0y_T = 0 forces the tangent line to be vertical). The kernel is H={O,T}H = \{\mathcal{O}, T\}, so the Velu sum has one nontrivial term.

This formula is equivalent to Velu’s original (Vélu, 1971). See also (Silverman, 2009). For a kernel SS and a point QSQ \notin S,

x(ϕ(Q))=x(Q)+RS{O}[x(Q+R)x(R)]x(\phi(Q)) = x(Q) + \sum_{R \in S \setminus \{\mathcal{O}\}} \bigl[x(Q + R) - x(R)\bigr] y(ϕ(Q))=y(Q)+RS{O}[y(Q+R)y(R)]y(\phi(Q)) = y(Q) + \sum_{R \in S \setminus \{\mathcal{O}\}} \bigl[y(Q + R) - y(R)\bigr]

This requires only point addition on the source curve.

p = 431
a, b = 1, 0 # E_0: y^2 = x^3 + x
def point_add(P, Q, a, p):
if P is None: return Q
if Q is None: return P
x1, y1 = P; x2, y2 = Q
if x1 == x2:
if (y1 + y2) % p == 0: return None
lam = (3*x1*x1 + a) * pow(2*y1, -1, p) % p
else:
lam = (y2 - y1) * pow(x2 - x1, -1, p) % p
x3 = (lam*lam - x1 - x2) % p
y3 = (lam*(x1 - x3) - y1) % p
return (x3, y3)
def scalar_mul(k, P, a, p):
R = None
while k:
if k & 1: R = point_add(R, P, a, p)
P = point_add(P, P, a, p)
k >>= 1
return R
# The 2-torsion point T = (0, 0): 2T = O since y_T = 0.
T = (0, 0)
print(scalar_mul(2, T, a, p))
# ==> None
# Velu evaluation formula with kernel {O, T}.
G = (13, 290) # generator of E_0(F_431), order 432
GpT = point_add(G, T, a, p)
phi_G = ((G[0] + GpT[0] - T[0]) % p,
(G[1] + GpT[1] - T[1]) % p)
print(phi_G)
# ==> (212, 426)
# A second image to recover the codomain y^2 = x^3 + a'x + b'.
P2 = scalar_mul(5, G, a, p)
P2pT = point_add(P2, T, a, p)
phi_P2 = ((P2[0] + P2pT[0] - T[0]) % p,
(P2[1] + P2pT[1] - T[1]) % p)
# Two points on the codomain give two equations:
# y_i^2 - x_i^3 = a'*x_i + b'
# Subtract to solve for a'.
x1, y1 = phi_G
x2, y2 = phi_P2
lhs1 = (y1*y1 - x1**3) % p
lhs2 = (y2*y2 - x2**3) % p
a_new = (lhs1 - lhs2) * pow(x1 - x2, -1, p) % p
b_new = (lhs1 - a_new*x1) % p
def j_inv(a, b, p):
num = 4 * pow(a, 3, p) % p
denom = (num + 27 * pow(b, 2, p)) % p
return 1728 * num * pow(denom, -1, p) % p
# j = 1728 * 4a^3 / (4a^3 + 27b^2). Over F_431, 1728 mod 431 = 4.
print(f"j(E_0) = {j_inv(1, 0, p)}")
# ==> j(E_0) = 4
print(f"j(E_0/<T>) = {j_inv(a_new, b_new, p)}")
# ==> j(E_0/<T>) = 4

Every Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch22/, one file per block. Appendix C covers the clone and the environment they run on.

The j-invariant did not change: j(E0)=j(E0/T)=17284(mod431)j(E_0) = j(E_0/\langle T \rangle) = 1728 \equiv 4 \pmod{431}. This is not a bug. The curve y2=x3+xy^2 = x^3 + x has j=1728j = 1728, which admits extra automorphisms (the map (x,y)(x,iy)(x,y) \mapsto (-x, iy) is an automorphism over Fp2\mathbb{F}_{p^2} where i2=1i^2 = -1). The degree-2 isogeny with this specific kernel produces a curve isomorphic to the original. A degree-3 isogeny moves to a different j-invariant:

p = 431
a, b = 1, 0
def point_add(P, Q, a, p):
if P is None: return Q
if Q is None: return P
x1, y1 = P; x2, y2 = Q
if x1 == x2:
if (y1 + y2) % p == 0: return None
lam = (3*x1*x1 + a) * pow(2*y1, -1, p) % p
else:
lam = (y2 - y1) * pow(x2 - x1, -1, p) % p
x3 = (lam*lam - x1 - x2) % p
y3 = (lam*(x1 - x3) - y1) % p
return (x3, y3)
def scalar_mul(k, P, a, p):
R = None
while k:
if k & 1: R = point_add(R, P, a, p)
P = point_add(P, P, a, p)
k >>= 1
return R
def j_inv(a, b, p):
num = 4 * pow(a, 3, p) % p
denom = (num + 27 * pow(b, 2, p)) % p
return 1728 * num * pow(denom, -1, p) % p
G = (13, 290)
# 3-torsion point: 144*G has order 3 (since 432/3 = 144).
P3 = scalar_mul(144, G, a, p)
print(f"P3 = {P3}")
# ==> P3 = (261, 309)
print(f"3*P3 = {scalar_mul(3, P3, a, p)}")
# ==> 3*P3 = None
# Degree-3 isogeny via the Velu evaluation formula.
kernel_3 = [None, P3, scalar_mul(2, P3, a, p)]
x_new, y_new = G[0], G[1]
for R in kernel_3:
if R is None: continue
GpR = point_add(G, R, a, p)
x_new = (x_new + GpR[0] - R[0]) % p
y_new = (y_new + GpR[1] - R[1]) % p
phi3_G = (x_new, y_new)
print(f"phi_3(G) = {phi3_G}")
# ==> phi_3(G) = (412, 94)
# Recover the codomain's j-invariant.
P2 = scalar_mul(7, G, a, p)
x2n, y2n = P2[0], P2[1]
for R in kernel_3:
if R is None: continue
P2R = point_add(P2, R, a, p)
x2n = (x2n + P2R[0] - R[0]) % p
y2n = (y2n + P2R[1] - R[1]) % p
x1, y1 = phi3_G; x2, y2 = x2n, y2n
lhs1 = (y1*y1 - x1**3) % p
lhs2 = (y2*y2 - x2**3) % p
a3 = (lhs1 - lhs2) * pow(x1 - x2, -1, p) % p
b3 = (lhs1 - a3*x1) % p
print(f"j(E_0/<P3>) = {j_inv(a3, b3, p)}")
# ==> j(E_0/<P3>) = 319

The degree-3 isogeny sends j4j \equiv 4 to j=319j = 319. At p=431p = 431 the supersingular isogeny graph has 37 vertices. This computation traversed one edge.

An isogeny ϕ:E1E2\phi: E_1 \to E_2 is a nonconstant rational map between elliptic curves that sends the identity to the identity. Because it is a rational map satisfying ϕ(O)=O\phi(\mathcal{O}) = \mathcal{O}, it is automatically a group homomorphism: ϕ(P+Q)=ϕ(P)+ϕ(Q)\phi(P + Q) = \phi(P) + \phi(Q) for all P,QE1P, Q \in E_1 (Theorem III.4.8 in Silverman, 2009).

The kernel ker(ϕ)={PE1:ϕ(P)=O}\ker(\phi) = \{P \in E_1 : \phi(P) = \mathcal{O}\} is a finite subgroup of E1E_1. The converse holds: for every finite subgroup HEH \subseteq E, there exists a unique (up to isomorphism of the target) separable isogeny ϕ:EE/H\phi: E \to E/H with ker(ϕ)=H\ker(\phi) = H (Proposition III.4.12 in Silverman, 2009). The subgroup determines the isogeny.

The degree of a separable isogeny equals the size of its kernel: deg(ϕ)=ker(ϕ)\deg(\phi) = |\ker(\phi)| (Theorem III.4.10c in Silverman, 2009). For every isogeny ϕ:E1E2\phi: E_1 \to E_2 of degree dd, there exists a unique dual isogeny ϕ^:E2E1\hat{\phi}: E_2 \to E_1 satisfying ϕ^ϕ=[d]\hat{\phi} \circ \phi = [d], the multiplication-by-dd map on E1E_1 (Theorem III.6.1 in Silverman, 2009). The dual has the same degree: deg(ϕ^)=d\deg(\hat{\phi}) = d.

Two elliptic curves over an algebraically closed field are isomorphic if and only if they have the same j-invariant (Proposition III.1.4b in Silverman, 2009). For a short Weierstrass curve y2=x3+ax+by^2 = x^3 + ax + b, the j-invariant is

j=17284a34a3+27b2j = 1728 \cdot \frac{4a^3}{4a^3 + 27b^2}

The j-invariant classifies curves up to isomorphism over an algebraically closed field. Two curves with different j-invariants are non-isomorphic. Two curves with the same j-invariant become isomorphic after passing to the algebraic closure. Over a finite base field they may still be non-isomorphic twists (Proposition X.5.4 in Silverman, 2009).

Velu’s 1971 paper (Vélu, 1971) gives explicit rational formulas for computing EE/HE \to E/H given a finite subgroup HH. An equivalent evaluation formula computes the image of a point QHQ \notin H by summing correction terms from each kernel element (Vélu, 1971):

ϕ(Q)=(xQ+RH[xQ+RxR],  yQ+RH[yQ+RyR])\phi(Q) = \Bigl(x_Q + \sum_{R \in H^*} [x_{Q+R} - x_R],\; y_Q + \sum_{R \in H^*} [y_{Q+R} - y_R]\Bigr)

where H=H{O}H^* = H \setminus \{\mathcal{O}\}. This requires H1|H| - 1 point additions on the source curve. For a kernel of order \ell, the cost is O()O(\ell) field operations.

For p>3p > 3, an elliptic curve EE over Fp\mathbb{F}_p is supersingular if and only if the trace of Frobenius is zero, equivalently #E(Fp)=p+1\#E(\mathbb{F}_p) = p + 1 (Exercise V.5.10a and V.5.10b in Silverman, 2009) (see Chapter 3). The chapter works at p=431>3p = 431 > 3, so this criterion applies directly.

Over Fp2\mathbb{F}_{p^2}, the number of supersingular j-invariants is p/12+ε\lfloor p/12 \rfloor + \varepsilon, where ε{0,1,2}\varepsilon \in \{0, 1, 2\} depends on pmod12p \bmod 12 (Theorem V.4.1c in Silverman, 2009). For p=43111(mod12)p = 431 \equiv 11 \pmod{12}, 431/12=35\lfloor 431/12 \rfloor = 35 and ε=2\varepsilon = 2, giving 37 total.

Fix a prime p\ell \neq p. The \ell-isogeny graph has supersingular j-invariants over Fp2\mathbb{F}_{p^2} as vertices and \ell-isogenies as edges. The torsion subgroup E[](Z/Z)2E[\ell] \cong (\mathbb{Z}/\ell\mathbb{Z})^2 has exactly +1\ell + 1 cyclic subgroups of order \ell (one for each point of P1(F)\mathbb{P}^1(\mathbb{F}_\ell)), so each vertex has +1\ell + 1 outgoing edges (Corollary III.6.4 in Silverman, 2009). For =2\ell = 2, the graph is 3-regular; for =3\ell = 3, it is 4-regular.

Figure 22.1 draws the local 2-isogeny structure at the chapter’s starting vertex j=17284(mod431)j = 1728 \equiv 4 \pmod{431}. This vertex is not generic. The curve E0:y2=x3+xE_0: y^2 = x^3 + x has extra automorphisms, so its three order-2 kernels do not reach three distinct neighbors. The kernel (0,0)\langle(0,0)\rangle returns a curve isomorphic to E0E_0, a self-loop at j=4j = 4. The two kernels (±i,0)\langle(\pm i,0)\rangle over F4312\mathbb{F}_{431^2} both reach j=19j = 19, a double edge.

The local 2-isogeny structure at the special vertex j = 1728 (4 mod 431) at p = 431. The supersingular vertex j = 4 (equal to 1728 mod 431) is drawn at left. A self-loop curves off its left side, labeled as the 2-isogeny from the kernel generated by (0,0), whose codomain is isomorphic to the starting curve E_0. Two parallel curved edges run from j = 4 to a second vertex j = 19 at right, labeled as the two 2-isogenies from the kernels generated by (i,0) and (-i,0) over the field of order 431 squared. Together the self-loop and the double edge are the three 2-isogenies of this vertex counted with multiplicity. A legend states the graph is regular only with multiplicity, that loops and parallel edges arise at the special j-invariants 0 and 1728, and that the full graph at p = 431 has 37 vertices and is a Ramanujan expander. Local 2-isogeny structure at j = 1728 (= 4 mod 431) j = 4 (= 1728) j = 19 self-loop kernel (0,0): codomain isomorphic to E_0 double edge: 2 parallel 2-isogenies kernels (i,0) and (-i,0) over F_(431^2) special vertex: extra automorphisms Three 2-isogenies counted with multiplicity: one self-loop plus a double edge, not three distinct neighbors. Regular only with multiplicity; loops/parallel edges arise at j = 0, j = 1728. Full graph at p = 431: 37 vertices, Ramanujan expander.
Figure 22.1. The local 2-isogeny structure at the chapter's starting vertex j=17284(mod431)j = 1728 \equiv 4 \pmod{431}, computed directly. The kernel (0,0)\langle(0,0)\rangle yields a curve isomorphic to E0:y2=x3+xE_0: y^2 = x^3 + x, drawn as a self-loop; the two kernels (±i,0)\langle(\pm i,0)\rangle over F4312\mathbb{F}_{431^2} both yield j=19j = 19, drawn as a double edge. This vertex is not generic: j=1728j = 1728 has extra automorphisms, so the supersingular 2-isogeny graph is 3-regular only when edges are counted with multiplicity. A generic vertex has three distinct neighbors. The full graph at p=431p = 431 has 37 vertices and is a Ramanujan expander (Pizer, 1990).

The supersingular \ell-isogeny graph is therefore regular only when edges are counted with multiplicity. Vertices with extra automorphisms, such as j=0j = 0 and j=1728j = 1728, can produce loops or parallel edges. A generic vertex has three distinct neighbors. The degeneracy is not confined to degree 2. The same vertex has four order-3 kernels reaching only two distinct neighbors, so a breadth-first search from here expands more slowly than a branching factor of four would predict (Appendix D, Exercise 3).

Pizer showed that this graph is a Ramanujan graph: its spectral gap is optimal, which means random walks mix rapidly to their stationary distribution (Pizer, 1990). That distribution is uniform up to the vertices with extra automorphisms. For the natural walk that picks one of the +1\ell + 1 kernels uniformly, a vertex’s stationary weight is proportional to its out-degree divided by the order of its reduced automorphism group, so j=1728j = 1728 carries half a generic vertex’s weight and j=0j = 0 a third (Theorem 4.9 in Florit & Smith, 2021). Both exceptional vertices are present at p=431p = 431, so “uniform over all jj-invariants” is a statement about the generic ones. Walking the graph is efficient. Finding the path someone walked, given only the start and end vertices, is believed to be hard: the best classical algorithm ran in O~(p1/2)\tilde{O}(p^{1/2}) time for three decades (Delfs & Galbraith, 2016), and the heuristic July 2026 algorithm that reaches p1/3+o(1)p^{1/3+o(1)}, at the cost of p1/3+o(1)p^{1/3+o(1)} memory, is still exponential in logp\log p (Wesolowski, 2026).

Why Fp2\mathbb{F}_{p^2}?

Section titled “Why F_{p^2}?”

The curve E0:y2=x3+xE_0: y^2 = x^3 + x has 432=2433432 = 2^4 \cdot 3^3 points over F431\mathbb{F}_{431}. But the full 242^4-torsion subgroup E0[16](Z/16Z)2E_0[16] \cong (\mathbb{Z}/16\mathbb{Z})^2 has 256256 elements, most of which live over the extension field F4312\mathbb{F}_{431^2}. Over F431\mathbb{F}_{431} alone, the 2-torsion is {O,(0,0)}\{{\mathcal{O}, (0,0)}\}, cyclic of order 2 (because x2+1x^2 + 1 has no roots mod 431, since 4313(mod4)431 \equiv 3 \pmod{4}).

SIDH needs independent generators for the full e\ell^e-torsion to define a key exchange. This forces the arithmetic into Fp2\mathbb{F}_{p^2}. We represent Fp2=Fp[i]/(i2+1)\mathbb{F}_{p^2} = \mathbb{F}_p[i]/(i^2+1) with elements as pairs (a,b)(a, b) meaning a+bia + bi.

p = 431
def fp2_mul(x, y, p):
return ((x[0]*y[0] - x[1]*y[1]) % p,
(x[0]*y[1] + x[1]*y[0]) % p)
def fp2_inv(x, p):
norm = (x[0]*x[0] + x[1]*x[1]) % p
inv_n = pow(norm, -1, p)
return ((x[0]*inv_n) % p, ((-x[1])*inv_n) % p)
# Verify: i^2 = -1 in F_{431^2}.
i = (0, 1)
print(fp2_mul(i, i, p))
# ==> (430, 0)
# Verify: (3 + 5i)*(3 + 5i)^{-1} = 1.
z = (3, 5)
z_inv = fp2_inv(z, p)
print(fp2_mul(z, z_inv, p))
# ==> (1, 0)

The result (430,0)(430, 0) is 1mod431-1 \bmod 431, confirming i2=1i^2 = -1.

The endomorphism ring of a supersingular curve over Fp2\mathbb{F}_{p^2} is a maximal order in the quaternion algebra Bp,B_{p,\infty} ramified at pp and \infty. Deuring’s theorem makes the reverse direction exact: conjugacy classes of maximal orders in Bp,B_{p,\infty} correspond to supersingular jj-invariants taken up to Galois conjugacy, that is to the pairs {j,jp}\{j, j^p\} rather than to single jj-invariants (Deuring, 1941; Lemma 42.4.1 in Voight, 2021). Chapter 23 states the theorem in the form SQIsign needs and builds key generation and signing on it.

Elliptic curve arithmetic over Fp2\mathbb{F}_{p^2}

Section titled “Elliptic curve arithmetic over F_{p^2}”

Chapter 4 implemented point addition for secp256k1 (y2=x3+7y^2 = x^3 + 7 over a 256-bit prime field). The arithmetic generalizes to an arbitrary short Weierstrass curve y2=x3+ax+by^2 = x^3 + ax + b with coordinates in Fp2\mathbb{F}_{p^2}.

p = 431
def fp2_add(x, y, p):
return ((x[0]+y[0]) % p, (x[1]+y[1]) % p)
def fp2_sub(x, y, p):
return ((x[0]-y[0]) % p, (x[1]-y[1]) % p)
def fp2_mul(x, y, p):
return ((x[0]*y[0]-x[1]*y[1]) % p, (x[0]*y[1]+x[1]*y[0]) % p)
def fp2_inv(x, p):
norm = (x[0]*x[0]+x[1]*x[1]) % p
return ((x[0]*pow(norm,-1,p)) % p, ((-x[1])*pow(norm,-1,p)) % p)
def fp2_neg(x, p):
return ((-x[0]) % p, (-x[1]) % p)
def fp2_sqr(x, p):
return ((x[0]*x[0]-x[1]*x[1]) % p, (2*x[0]*x[1]) % p)
def ec_add(P, Q, a, p):
if P is None: return Q
if Q is None: return P
x1, y1 = P; x2, y2 = Q
if x1[0]%p == x2[0]%p and x1[1]%p == x2[1]%p:
ny2 = fp2_neg(y2, p)
if y1[0]%p == ny2[0]%p and y1[1]%p == ny2[1]%p:
return None
num = fp2_add(fp2_mul((3,0), fp2_sqr(x1,p), p), a, p)
den = fp2_mul((2,0), y1, p)
lam = fp2_mul(num, fp2_inv(den,p), p)
else:
lam = fp2_mul(fp2_sub(y2,y1,p), fp2_inv(fp2_sub(x2,x1,p),p), p)
x3 = fp2_sub(fp2_sub(fp2_sqr(lam,p), x1, p), x2, p)
y3 = fp2_sub(fp2_mul(lam, fp2_sub(x1,x3,p), p), y1, p)
return (x3, y3)
def ec_mul(k, P, a, p):
R = None
while k:
if k & 1: R = ec_add(R, P, a, p)
P = ec_add(P, P, a, p)
k >>= 1
return R
# E_0: y^2 = x^3 + x, with a = (1,0), b = (0,0) in F_{p^2}.
a0 = (1, 0)
G = ((13, 0), (290, 0))
print(ec_mul(432, G, a0, p))
# ==> None

Velu’s formulas over Fp2\mathbb{F}_{p^2}

Section titled “Velu’s formulas over F_{p^2}”

Velu’s evaluation formula is field-agnostic: substitute Fp2\mathbb{F}_{p^2} operations throughout.

p = 431
def fp2_add(x, y, p):
return ((x[0]+y[0]) % p, (x[1]+y[1]) % p)
def fp2_sub(x, y, p):
return ((x[0]-y[0]) % p, (x[1]-y[1]) % p)
def fp2_mul(x, y, p):
return ((x[0]*y[0]-x[1]*y[1]) % p, (x[0]*y[1]+x[1]*y[0]) % p)
def fp2_inv(x, p):
norm = (x[0]*x[0]+x[1]*x[1]) % p
return ((x[0]*pow(norm,-1,p)) % p, ((-x[1])*pow(norm,-1,p)) % p)
def fp2_neg(x, p):
return ((-x[0]) % p, (-x[1]) % p)
def fp2_sqr(x, p):
return ((x[0]*x[0]-x[1]*x[1]) % p, (2*x[0]*x[1]) % p)
def ec_add(P, Q, a, p):
if P is None: return Q
if Q is None: return P
x1, y1 = P; x2, y2 = Q
if x1[0]%p == x2[0]%p and x1[1]%p == x2[1]%p:
ny2 = fp2_neg(y2, p)
if y1[0]%p == ny2[0]%p and y1[1]%p == ny2[1]%p:
return None
num = fp2_add(fp2_mul((3,0), fp2_sqr(x1,p), p), a, p)
den = fp2_mul((2,0), y1, p)
lam = fp2_mul(num, fp2_inv(den,p), p)
else:
lam = fp2_mul(fp2_sub(y2,y1,p), fp2_inv(fp2_sub(x2,x1,p),p), p)
x3 = fp2_sub(fp2_sub(fp2_sqr(lam,p), x1, p), x2, p)
y3 = fp2_sub(fp2_mul(lam, fp2_sub(x1,x3,p), p), y1, p)
return (x3, y3)
def ec_mul(k, P, a, p):
R = None
while k:
if k & 1: R = ec_add(R, P, a, p)
P = ec_add(P, P, a, p)
k >>= 1
return R
def velu_eval(Q, kernel, a, p):
if Q is None: return None
xQ, yQ = Q
x_new, y_new = xQ, yQ
for R in kernel:
if R is None: continue
xR, yR = R
if xQ[0]%p == xR[0]%p and xQ[1]%p == xR[1]%p:
return None
QpR = ec_add(Q, R, a, p)
if QpR is None: return None
x_new = fp2_add(x_new, fp2_sub(QpR[0], xR, p), p)
y_new = fp2_add(y_new, fp2_sub(QpR[1], yR, p), p)
return (x_new, y_new)
a0 = (1, 0)
G = ((13, 0), (290, 0))
P3 = ec_mul(144, G, a0, p) # order 3
P3_2 = ec_mul(2, P3, a0, p)
kernel_3 = [None, P3, P3_2]
phi_G = velu_eval(G, kernel_3, a0, p)
print(phi_G)
# ==> ((412, 0), (94, 0))
print(velu_eval(P3, kernel_3, a0, p))
# ==> None

SIDH (De Feo et al., 2014) is a Diffie-Hellman-style key exchange on the supersingular isogeny graph. The setup:

  • Fix a prime p=f2eA3eB1p = f \cdot 2^{e_A} \cdot 3^{e_B} - 1, often with a small cofactor ff (in this toy chapter f=1f = 1, so p=24331=431p = 2^4 \cdot 3^3 - 1 = 431), and a supersingular curve E0E_0 over Fp2\mathbb{F}_{p^2}.
  • Fix torsion bases {PA,QA}\{P_A, Q_A\} generating E0[2eA]E_0[2^{e_A}] and {PB,QB}\{P_B, Q_B\} generating E0[3eB]E_0[3^{e_B}].

Alice picks a secret α{0,,2eA1}\alpha \in \{0, \ldots, 2^{e_A}-1\} and computes:

  1. Kernel generator RA=PA+αQAR_A = P_A + \alpha \cdot Q_A (order 2eA2^{e_A}).
  2. Isogeny ϕA:E0EA=E0/RA\phi_A: E_0 \to E_A = E_0/\langle R_A \rangle.
  3. Torsion images ϕA(PB)\phi_A(P_B) and ϕA(QB)\phi_A(Q_B) on EAE_A.

Alice publishes (EA,ϕA(PB),ϕA(QB))(E_A, \phi_A(P_B), \phi_A(Q_B)).

Bob picks a secret β\beta and does the same with the 3-power torsion, publishing

(EB,ϕB(PA),ϕB(QA)).(E_B, \phi_B(P_A), \phi_B(Q_A)).

To derive the shared secret, Alice uses Bob’s published data to compute j(EB/ϕB(PA)+αϕB(QA))j(E_B / \langle \phi_B(P_A) + \alpha \cdot \phi_B(Q_A) \rangle). Bob computes j(EA/ϕA(PB)+βϕA(QB))j(E_A / \langle \phi_A(P_B) + \beta \cdot \phi_A(Q_B) \rangle). Both arrive at the same j-invariant. Writing RA=PA+αQAR_A = P_A + \alpha Q_A and RB=PB+βQBR_B = P_B + \beta Q_B, Alice’s codomain is

EB/ϕB(RA)    (E0/RB)/ϕB(RA)    E0/RA,RB,E_B / \langle \phi_B(R_A) \rangle \;\cong\; (E_0/\langle R_B \rangle) / \langle \phi_B(R_A) \rangle \;\cong\; E_0 / \langle R_A, R_B \rangle,

and Bob’s is isomorphic to the same E0/RA,RBE_0 / \langle R_A, R_B \rangle. The shared quotient is independent of the order in which the two kernels are quotiented out (De Feo et al., 2014), which is what makes the Diffie-Hellman analogy hold.

A degree-e\ell^e isogeny is too expensive to compute in one step (Velu’s formulas cost O(e)O(\ell^e)). Instead, decompose it into ee steps of degree \ell. For Alice’s degree-24=162^4 = 16 isogeny:

  1. Start with kernel generator RAR_A of order 16.
  2. Step 0: compute 8RA8 \cdot R_A (order 2). Apply the degree-2 Velu isogeny with this kernel. Push RAR_A and the torsion points through.
  3. Step 1: the pushed RAR_A now has order 8. Compute 4RA4 \cdot R_A (order 2). Apply degree-2 Velu. Push everything through.
  4. Steps 2 and 3: repeat.

After 4 steps, the accumulated isogeny has degree 24=162^4 = 16.

p = 431
def fp2_add(x, y, p):
return ((x[0]+y[0]) % p, (x[1]+y[1]) % p)
def fp2_sub(x, y, p):
return ((x[0]-y[0]) % p, (x[1]-y[1]) % p)
def fp2_mul(x, y, p):
return ((x[0]*y[0]-x[1]*y[1]) % p, (x[0]*y[1]+x[1]*y[0]) % p)
def fp2_inv(x, p):
norm = (x[0]*x[0]+x[1]*x[1]) % p
inv_n = pow(norm, -1, p)
return ((x[0]*inv_n) % p, ((-x[1])*inv_n) % p)
def fp2_neg(x, p):
return ((-x[0]) % p, (-x[1]) % p)
def fp2_sqr(x, p):
return ((x[0]*x[0]-x[1]*x[1]) % p, (2*x[0]*x[1]) % p)
def ec_add(P, Q, a, p):
if P is None: return Q
if Q is None: return P
x1, y1 = P
x2, y2 = Q
if x1[0]%p == x2[0]%p and x1[1]%p == x2[1]%p:
ny2 = fp2_neg(y2, p)
if y1[0]%p == ny2[0]%p and y1[1]%p == ny2[1]%p:
return None
num = fp2_add(fp2_mul((3, 0), fp2_sqr(x1, p), p), a, p)
den = fp2_mul((2, 0), y1, p)
lam = fp2_mul(num, fp2_inv(den, p), p)
else:
num = fp2_sub(y2, y1, p)
den = fp2_sub(x2, x1, p)
lam = fp2_mul(num, fp2_inv(den, p), p)
x3 = fp2_sub(fp2_sub(fp2_sqr(lam, p), x1, p), x2, p)
y3 = fp2_sub(fp2_mul(lam, fp2_sub(x1, x3, p), p), y1, p)
return (x3, y3)
def ec_mul(k, P, a, p):
R = None
while k:
if k & 1: R = ec_add(R, P, a, p)
P = ec_add(P, P, a, p)
k >>= 1
return R
def velu_eval(Q, kernel, a, p):
if Q is None:
return None
xQ, yQ = Q
x_new, y_new = xQ, yQ
for R in kernel:
if R is None:
continue
xR, yR = R
if (xQ[0]%p == xR[0]%p and xQ[1]%p == xR[1]%p):
return None
QpR = ec_add(Q, R, a, p)
if QpR is None:
return None
x_new = fp2_add(x_new, fp2_sub(QpR[0], xR, p), p)
y_new = fp2_add(y_new, fp2_sub(QpR[1], yR, p), p)
return (x_new, y_new)
def compute_kernel(gen, order, a, p):
pts = [None]
cur = gen
for _ in range(1, order):
pts.append(cur)
cur = ec_add(cur, gen, a, p)
return pts
def recover_curve(img1, img2, p):
"""Recover a', b' from two points on y^2 = x^3 + a'x + b'."""
x1, y1 = img1
x2, y2 = img2
lhs1 = fp2_sub(fp2_sqr(y1, p), fp2_mul(fp2_sqr(x1, p), x1, p), p)
lhs2 = fp2_sub(fp2_sqr(y2, p), fp2_mul(fp2_sqr(x2, p), x2, p), p)
num = fp2_sub(lhs1, lhs2, p)
den = fp2_sub(x1, x2, p)
a_new = fp2_mul(num, fp2_inv(den, p), p)
b_new = fp2_sub(lhs1, fp2_mul(a_new, x1, p), p)
return a_new, b_new
def velu_step(kernel_gen, l, a, b, p, aux):
kernel = compute_kernel(kernel_gen, l, a, p)
pushed = [velu_eval(pt, kernel, a, p) for pt in aux]
probes = [img for img in pushed if img is not None]
# Need two points with distinct x-coords for curve recovery.
# Remove duplicates by x-coordinate.
unique = []
seen_x = set()
for pr in probes:
key = (pr[0][0] % p, pr[0][1] % p)
if key not in seen_x:
seen_x.add(key)
unique.append(pr)
probes = unique
if len(probes) < 2:
for x_int in range(2, p):
x = (x_int, 0)
x3 = fp2_mul(fp2_sqr(x, p), x, p)
rhs = fp2_add(fp2_add(x3, fp2_mul(a, x, p), p), b, p)
if rhs[1] != 0: continue
if rhs[0] == 0: continue
if pow(rhs[0], (p-1)//2, p) != 1: continue
y_int = pow(rhs[0], (p+1)//4, p)
probe = ((x_int, 0), (y_int, 0))
img = velu_eval(probe, kernel, a, p)
if img is None: continue
key = (img[0][0] % p, img[0][1] % p)
if key in seen_x: continue
seen_x.add(key)
probes.append(img)
if len(probes) >= 2: break
a_new, b_new = recover_curve(probes[0], probes[1], p)
return a_new, b_new, pushed
def walk_chain(kernel_gen, l, e, a, b, p, aux, probes=None):
"""Walk an l^e isogeny as e steps of degree l.
*probes* are extra points pushed through for curve recovery only;
they are not returned.
"""
gen = kernel_gen
a_cur, b_cur = a, b
cur_aux = list(aux)
if probes is None:
probes = []
cur_probes = list(probes)
for step in range(e):
remaining = e - step
step_gen = ec_mul(l**(remaining-1), gen, a_cur, p)
push = [gen] + cur_aux + cur_probes
a_new, b_new, pushed = velu_step(
step_gen, l, a_cur, b_cur, p, push)
gen = pushed[0]
cur_aux = pushed[1:1+len(cur_aux)]
cur_probes = pushed[1+len(cur_aux):]
a_cur, b_cur = a_new, b_new
return a_cur, b_cur, cur_aux
# --- SIDH at p = 431 = 2^4 * 3^3 - 1 ---
a0 = (1, 0)
b0 = (0, 0)
# Torsion bases (precomputed, verified in test suite).
PA = ((372, 0), (48, 0)) # order 16
QA = ((178, 168), (190, 428)) # order 16, independent of PA
PB = ((123, 0), (396, 0)) # order 27
QB = ((128, 133), (47, 6)) # order 27, independent of PB
# Alice: alpha = 3.
alpha = 3
RA = ec_add(PA, ec_mul(alpha, QA, a0, p), a0, p)
a_A, b_A, aux_A = walk_chain(RA, 2, 4, a0, b0, p, [PB, QB])
phiA_PB, phiA_QB = aux_A
# Bob: beta = 5.
beta = 5
RB = ec_add(PB, ec_mul(beta, QB, a0, p), a0, p)
a_B, b_B, aux_B = walk_chain(RB, 3, 3, a0, b0, p, [PA, QA])
phiB_PA, phiB_QA = aux_B
# Alice derives the shared secret.
# Pass torsion images as probes for curve recovery.
kernel_alice = ec_add(
phiB_PA, ec_mul(alpha, phiB_QA, a_B, p), a_B, p)
a_AB_a, b_AB_a, _ = walk_chain(
kernel_alice, 2, 4, a_B, b_B, p, [],
probes=[phiB_PA, phiB_QA])
def j_inv_fp2(a, b, p):
a3 = fp2_mul(fp2_sqr(a, p), a, p)
four_a3 = fp2_mul((4, 0), a3, p)
b2 = fp2_sqr(b, p)
denom = fp2_add(four_a3, fp2_mul((27, 0), b2, p), p)
return fp2_mul(fp2_mul((1728, 0), four_a3, p), fp2_inv(denom, p), p)
j_alice = j_inv_fp2(a_AB_a, b_AB_a, p)
# Bob derives the shared secret.
kernel_bob = ec_add(
phiA_PB, ec_mul(beta, phiA_QB, a_A, p), a_A, p)
a_AB_b, b_AB_b, _ = walk_chain(
kernel_bob, 3, 3, a_A, b_A, p, [],
probes=[phiA_PB, phiA_QB])
j_bob = j_inv_fp2(a_AB_b, b_AB_b, p)
print(f"j_alice = {j_alice}")
# ==> j_alice = (315, 132)
print(f"j_bob = {j_bob}")
# ==> j_bob = (315, 132)
print(f"Match: {j_alice == j_bob}")
# ==> Match: True

Alice and Bob arrive at the same j-invariant (315,132)F4312(315, 132) \in \mathbb{F}_{431^2}. The shared secret is this j-invariant.

The public data each party sends is a curve (two Fp2\mathbb{F}_{p^2} elements a,ba, b) and two torsion images (four Fp2\mathbb{F}_{p^2} elements). The secret is one integer (α\alpha or β\beta). At real SIKE parameters (SIKEp434, NIST level 1), the public key was 330 bytes (Jao et al., 2022).

SIDH publishes more than just the destination curve EAE_A. It also publishes ϕA(PB)\phi_A(P_B) and ϕA(QB)\phi_A(Q_B): the images of Bob’s torsion basis under Alice’s secret isogeny. This auxiliary data is necessary for the protocol (Bob needs it to compute the shared secret), but it encodes Alice’s secret redundantly.

Alice’s secret is the coefficient α\alpha in RA=PA+αQAR_A = P_A + \alpha \cdot Q_A. Once the attacker knows α\alpha, they can reconstruct RAR_A and compute the shared secret. The published torsion images ϕA(PB)\phi_A(P_B) and ϕA(QB)\phi_A(Q_B) encode how ϕA\phi_A acts on the 3eB3^{e_B}-torsion. Because ϕA\phi_A is determined by its kernel RA\langle R_A \rangle, and the kernel is determined by α\alpha, the torsion images are functions of α\alpha over the public 3eB3^{e_B}-torsion group.

In July 2022, Castryck and Decru showed that this auxiliary data enables a key recovery in heuristic polynomial time when the endomorphism ring of the starting curve is known, apart from the factorization of a few integers that depend only on the system parameters (Castryck & Decru, 2023). Robert then removed the starting-curve condition and the heuristic: his attack runs in polynomial time from the torsion images and the factored smooth degrees alone (Robert, 2022). The attack uses three ingredients:

Kani’s theorem. An abelian surface is the two-dimensional analogue of an elliptic curve (an elliptic curve is a one-dimensional abelian variety). Given two elliptic curves E1,E2E_1, E_2 and isogenies between them, the Kani-Frey gluing construction stitches E1E_1 and E2E_2 into a product abelian surface E1×E2E_1 \times E_2 and builds a (2,2)(2,2)-isogeny on it (Kani, 1997). The published torsion images are the gluing data this construction consumes.

Richelot isogenies. Isogenies between abelian surfaces (genus-2 curves or products of elliptic curves) can be computed explicitly. The Richelot isogeny is the genus-2 analog of Velu’s formula.

Decomposition. The product surface isogeny, constructed from the published torsion data, factors through a chain of Richelot isogenies. Each step of this chain reveals partial information about Alice’s kernel. After enough steps, the attacker recovers α\alpha completely.

Under either analysis the attack runs in polynomial time in logp\log p. It does not require quantum computation.

The torsion images amount to evaluations of Alice’s secret isogeny at two independent generators of Bob’s 3eB3^{e_B}-torsion subgroup. Together they pin down the kernel more tightly than the bare isogeny problem would. The Kani-Frey lifting trick converts these constraints into a system that recovers α\alpha in polynomial time.

The Castryck-Decru attack targets SIDH’s protocol design. It does not apply to isogeny problems that omit torsion-image disclosure.

CSIDH (Castryck et al., 2018) uses supersingular curves defined over Fp\mathbb{F}_p (not Fp2\mathbb{F}_{p^2}) with a commutative class group action on the Fp\mathbb{F}_p-rational endomorphism ring. The public key is a single curve. No torsion images are published. The attack does not apply.

SQIsign (Chapter 23) is a signature scheme, not a key exchange. The signature is an isogeny itself, not a pair of torsion images taken under a secret isogeny. Round-2 SQIsign does publish torsion-point images, but only of the public response isogeny ϕrsp\phi_{\mathsf{rsp}}, so the verifier can evaluate it (The SQIsign Team, 2025, sec. 1.3). It never publishes the secret isogeny’s action on a torsion basis, which is the data the Castryck-Decru attack needs, so the attack has no input here (Chapter 23 gives the full Round-2 protocol geometry). SQIsign’s security rests on the endomorphism ring problem for supersingular curves, in a variant that hands the adversary hints (The SQIsign Team, 2025, sec. 1.1).

The supersingular isogeny problem without auxiliary torsion data remains hard. The best known classical algorithm ran in O~(p1/2)\tilde{O}(p^{1/2}) for the general supersingular graph for three decades (Delfs & Galbraith, 2016); in July 2026 Wesolowski gave a heuristic algorithm in p1/3+o(1)p^{1/3+o(1)} time and memory, still exponential in logp\log p, which the SQIsign team’s round-3 specification prices for its own parameters (The SQIsign Team, 2026, sec. 8.2; Wesolowski, 2026). Curves with jj-invariant in Fp\mathbb{F}_p admit a faster structure-exploiting walk, again exponential. The best known quantum algorithm gives only a square-root speedup to O~(p1/4)\tilde{O}(p^{1/4}) (Biasse et al., 2014), also exponential in logp\log p. No polynomial-time (Shor-style) quantum algorithm is known as of September 2026.

  • 1997: Couveignes formulates the first cryptographic use of isogenies (Couveignes, 1997). The manuscript is not published until 2006.
  • 2006: Rostovtsev and Stolbunov independently rediscover the group-action approach on ordinary curves (Rostovtsev & Stolbunov, 2006). Castryck et al. later reformulate it over supersingular curves as CSIDH (Castryck et al., 2018).
  • 2011: De Feo, Jao, and Plut propose SIDH, the first efficient isogeny-based key exchange (published in journal form in 2014) (De Feo et al., 2014).
  • 2017: SIKE (Supersingular Isogeny Key Encapsulation), based on SIDH, is submitted to NIST’s PQC standardization process.
  • July to August 2022: Castryck and Decru publish a key recovery attack on SIDH, heuristic polynomial time when the starting curve’s endomorphism ring is known (Castryck & Decru, 2023). Maino and Martindale independently find a subexponential attack with no condition on the starting curve (Maino & Martindale, 2022). Robert gives a polynomial-time attack for any starting curve, proved without heuristics (Robert, 2022).
  • September 2022: The SIKE team submits a fourth-round proposal dated 15 September 2022 whose only change is a postscript stating that SIKE and SIDH are insecure and should not be used (Jao et al., 2022). NIST does not formally withdraw the submission, and the team’s stated reason for submitting rather than withdrawing is that the closing record should reflect the break where an unwitting user would see it. SIKE is no longer a viable standardization candidate.
  • March 2025: NIST IR 8545 closes the fourth round: HQC is selected for standardization as a code-based backup KEM; SIKE, the only isogeny-based fourth-round candidate, is not selected (National Institute of Standards and Technology, 2025).

Eleven years separated SIDH’s publication from the Castryck-Decru attack. SIKE had survived five years of NIST evaluation before the attack landed.

PropertySIDH/SIKESQIsign (level 1)ML-KEM-512HQC-1
TypeKEMSignatureKEMKEM
NIST statusBroken (2022)Additional signatures Round 3 candidateFIPS 203Selected (2025)
Public key330 B83 B800 B2,241 B
Ciphertext/sig346 B200 B768 B4,433 B
pk + ct/sig676 B283 B1,568 B6,674 B
AssumptionSIDH torsion-aided isogeny (broken)EndRing (hint variant)Module-LWEQCSD
First proposed201120202017 (as Kyber)2017
Quantum statusClassically broken (2022)No known poly-time quantum attackNo known poly-time quantum attackNo known poly-time quantum attack

Sources: SIDH/SIKE (Jao et al., 2022), SQIsign (Table 1 in The SQIsign Team, 2026), ML-KEM (National Institute of Standards and Technology, 2024), HQC (Gaborit et al., 2025). The ML-KEM column’s first-proposed date is CRYSTALS-Kyber’s, from the original paper (Bos et al., 2018). FIPS 203 §1.1 records only that ML-KEM is derived from the round-three Kyber submission, and gives no date.

Isogeny-based designs can produce very small public keys and signatures. Among current NIST additional-signature candidates, SQIsign has the smallest combined public-key-plus-signature size: 283 bytes at level 1, smaller than any lattice or code-based signature alternative. The tradeoff is assumption maturity: isogeny-based cryptography has the shortest cryptographic track record among the major PQC families, and SIDH’s break shows that protocol-specific auxiliary data can invalidate an otherwise plausible hardness assumption. CSIDH remains standing but has not been submitted to NIST. SQIsign is a Round 3 candidate in NIST’s additional-signatures evaluation track (National Institute of Standards and Technology, 2026). The sizes and timings here are from the round-3 specification, version 3.0 of 1 September 2026 (The SQIsign Team, 2026); the round-2 specification of July 2025 had a 65-byte key and a 148-byte signature, and the increase prices the p1/3+o(1)p^{1/3+o(1)} algorithm above (The SQIsign Team, 2025). SQIsign’s security rests on the endomorphism ring problem, in the hint variant its EUF-CMA proof reduces to. Chapter 23 develops the details.

Signing latency is a second tradeoff: the round-3 SQIsign specification reports about 28 ms per signature and about 3.6 ms per verification for the optimized 64-bit Intel implementation on an Intel Core i7-13700K at its 3.4 GHz nominal clock (Table 2 in The SQIsign Team, 2026). Per-transaction signing on a high-throughput Layer-1 blockchain operates in the microsecond class, so 28 ms makes SQIsign unattractive for high-throughput per-transaction signing without categorically excluding it: a wallet’s signing demand, its parallelism and the verifier’s workload are different constraints, and Chapter 23 draws the same line. Chapter 23 covers the governance-key context where the latency is acceptable.

Chapter 23 builds SQIsign from scratch, using this chapter’s isogeny graph together with quaternion algebra. It picks up both threads the cryptanalysis section left open. Deuring’s correspondence, previewed above as a bijection between conjugacy classes of maximal orders and Galois-conjugate pairs of supersingular jj-invariants, becomes the machinery that lets a signer connect two curves while revealing neither the secret one nor its endomorphism ring. And the distinction that lets SQIsign survive Castryck-Decru, publishing point images of the public response isogeny but never of the secret isogeny, becomes the commitment-challenge-response square the verifier checks.

Exercise 1. Find all points of order 2 on E0:y2=x3+xE_0: y^2 = x^3 + x over F431\mathbb{F}_{431}. (Hint: 2-torsion points have y=0y = 0, so solve x3+x=0mod431x^3 + x = 0 \bmod 431.) How many 2-torsion points does E0E_0 have over F4312\mathbb{F}_{431^2}?

Exercise 2. Starting from E0E_0, walk the 3-isogeny graph for 4 steps. At each step, choose a 3-torsion point on the current curve, compute the degree-3 Velu isogeny, and record the j-invariant of the target. Does the walk revisit any j-invariant within 4 steps?

Exercise 3. Compute the number of supersingular j-invariants over F4312\mathbb{F}_{431^2} using the formula p/12+ε\lfloor p/12 \rfloor + \varepsilon. The correction ε\varepsilon depends on pmod12p \bmod 12: it is 0 for p1p \equiv 1, 1 for p5p \equiv 5 or 77, and 2 for p11(mod12)p \equiv 11 \pmod{12}. Verify by enumerating all j-invariants reachable from j=1728j = 1728 via repeated 3-isogenies.

Exercise 4. Explain in two sentences why the Castryck-Decru attack does not apply to CSIDH.

Exercise 5. (Harder) Verify that p=25331=863p' = 2^5 \cdot 3^3 - 1 = 863 is prime. The curve E0:y2=x3+xE_0: y^2 = x^3 + x over F863\mathbb{F}_{863} has 864=2533864 = 2^5 \cdot 3^3 points. How many steps would Alice’s isogeny chain have at this prime? How many for Bob?

Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 22. A separate track, for rebuilding rather than reading: the package exercises/ch22-isogenies has every function the chapter teaches replaced by a stub. Run PQC_IMPL=exercises pytest tests/ch22 to grade your version against the suite that proves the reference one.

Biasse, J.-F., Jao, D., & Sankar, A. (2014). A quantum algorithm for computing isogenies between supersingular elliptic curves. Progress in Cryptology, INDOCRYPT 2014, 428–442. https://doi.org/10.1007/978-3-319-13039-2_25
Bos, J., Ducas, L., Kiltz, E., Lepoint, T., Lyubashevsky, V., Schanck, J. M., Schwabe, P., Seiler, G., & Stehlé, D. (2018). CRYSTALS-Kyber: A CCA-Secure Module-Lattice-Based KEM. 2018 IEEE European Symposium on Security and Privacy (EuroS&P), 353–367. https://doi.org/10.1109/EuroSP.2018.00032
Castryck, W., & Decru, T. (2023). An efficient key recovery attack on SIDH. Advances in Cryptology – EUROCRYPT 2023, Part V, 14008, 423–447. https://doi.org/10.1007/978-3-031-30589-4_15
Castryck, W., Lange, T., Martindale, C., Panny, L., & Renes, J. (2018). CSIDH: An efficient post-quantum commutative group action. Advances in Cryptology – ASIACRYPT 2018, Part III, 11274, 395–427. https://doi.org/10.1007/978-3-030-03332-3_15
Couveignes, J.-M. (1997). Hard homogeneous spaces. Cryptology ePrint Archive, Paper 2006/291. https://eprint.iacr.org/2006/291
De Feo, L., Jao, D., & Plût, J. (2014). Towards quantum-resistant cryptosystems from supersingular elliptic curve isogenies. Journal of Mathematical Cryptology, 8(3), 209–247. https://doi.org/10.1515/jmc-2012-0015
Delfs, C., & Galbraith, S. D. (2016). Computing isogenies between supersingular elliptic curves over 𝔽p. Designs, Codes and Cryptography, 78(2), 425–440. https://doi.org/10.1007/s10623-014-0010-1
Deuring, M. (1941). Die Typen der Multiplikatorenringe elliptischer Funk\-tion\-enkörper. Abhandlungen Aus Dem Mathematischen Seminar Der Universität Hamburg, 14, 197–272. https://doi.org/10.1007/BF02940746
Florit, E., & Smith, B. (2021). Automorphisms and isogeny graphs of abelian varieties, with applications to the superspecial Richelot isogeny graph. arXiv 2101.00919. https://arxiv.org/abs/2101.00919
Gaborit, P., Aguilar-Melchor, C., Aragon, N., Bettaieb, S., Bidoux, L., Blazy, O., Deneuville, J.-C., Persichetti, E., Zémor, G., Bos, J., Dion, A., Lacan, J., Robert, J.-M., Véron, P., Barreto, P. S. L. M., Ghosh, S., Gueron, S., Güneysu, T., Misoczki, R., … Vasseur, V. (2025). HQC: Hamming Quasi-Cyclic. https://pqc-hqc.org/doc/hqc_specifications_2025_08_22.pdf
Jao, D., Azarderakhsh, R., Campagna, M., Costello, C., De Feo, L., Hess, B., Hutchinson, A., Jalali, A., Karabina, K., Koziel, B., LaMacchia, B., Longa, P., Naehrig, M., Pereira, G., Renes, J., Soukharev, V., & Urbanik, D. (2022). Supersingular Isogeny Key Encapsulation. NIST PQC Round 4 final submission, 15 September 2022. https://sike.org/files/SIDH-spec.pdf
Kani, E. (1997). The number of curves of genus two with elliptic differentials. Journal Für Die Reine Und Angewandte Mathematik, 485, 93–121. https://doi.org/10.1515/crll.1997.485.93
Maino, L., & Martindale, C. (2022). An attack on SIDH with arbitrary starting curve. Cryptology ePrint Archive, Paper 2022/1026. https://eprint.iacr.org/2022/1026
National Institute of Standards and Technology. (2024). FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard. Federal Information Processing Standards Publication. https://doi.org/10.6028/NIST.FIPS.203
National Institute of Standards and Technology. (2025). Status Report on the Fourth Round of the NIST Post-Quantum Cryptography Standardization Process (Internal Report NIST IR 8545). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.IR.8545
National Institute of Standards and Technology. (2026). Status Report on the Second Round of the Additional Digital Signature Schemes for the NIST Post-Quantum Cryptography Standardization Process (Internal Report NIST IR 8610). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.IR.8610
Pizer, A. K. (1990). Ramanujan graphs and Hecke operators. Bulletin of the American Mathematical Society, 23(1), 127–137. https://doi.org/10.1090/S0273-0979-1990-15918-X
Robert, D. (2022). Breaking SIDH in polynomial time. Cryptology ePrint Archive, Paper 2022/1038. https://eprint.iacr.org/2022/1038
Rostovtsev, A., & Stolbunov, A. (2006). Public-key cryptosystem based on isogenies. Cryptology ePrint Archive, Paper 2006/145. https://eprint.iacr.org/2006/145
Silverman, J. H. (2009). The Arithmetic of Elliptic Curves (2nd ed., Vol. 106). Springer. https://doi.org/10.1007/978-0-387-09494-6
The SQIsign Team. (2025). SQIsign: Algorithm Specifications and Supporting Documentation (Version 2.0.1). NIST Post-Quantum Cryptography Additional Signatures, Round 2 submission. https://sqisign.org/spec/sqisign-20250707.pdf
The SQIsign Team. (2026). SQIsign: Algorithm Specifications and Supporting Documentation (Version 3.0). NIST Post-Quantum Cryptography Additional Signatures, Round 3 submission. https://sqisign.org/spec/sqisign-20260901.pdf
Vélu, J. (1971). Isogénies entre courbes elliptiques. Comptes Rendus de l’Académie Des Sciences, Série A, 273, 238–241. https://gallica.bnf.fr/ark:/12148/bpt6k56191248/f52
Voight, J. (2021). Quaternion Algebras (Vol. 288). Springer. https://doi.org/10.1007/978-3-030-56694-4
Wesolowski, B. (2026). The supersingular isogeny problem in time and memory p1/3+o(1). Cryptology ePrint Archive, Paper 2026/1486. https://eprint.iacr.org/2026/1486

Last updated: