Isogenies over C\mathbb{C}

👻

Table of Contents

This is a writeup of my recent challenge for Imaginary CTF called Complex Curve Crypto.

 1from sage.all import *
 2phi = classical_modular_polynomial(l:=3)
 3flag = ZZ.from_bytes(os.environ.get('FLAG', 'jctf{fakeflag}').encode())
 4j = jp = ComplexField(1337)(1337)
 5for d in flag.digits(l):
 6    roots = phi(X=j).univariate_polynomial().roots(multiplicities=False)
 7    roots.sort(key=jp.dist)
 8    del roots[0] # no turning back
 9    roots.sort(key=arg)
10    j, jp = roots[d], j
11print(j)
12# -6380.29156755903034687704660000282270448709570700984709657263854520138341881550084848649839410922876936678108641559731906139407589458824525200739437427675429842545943862350406898342718600643307574207936681432539822432021043321858999100455988010088297166788530685045590301521392833041157761230553808660966639987227996254364651040683587985017755013287627806460764447918220597797227073500295101888328945378 - 40214.6672669145274363733990190234269396915010860735171342583778810557492152506583335099397606096949655915384682940536670398063862158837139959936293817852537495478495004482851179789975853315750442943613549504080182872285152393975211173312274904654221951964258004673123691758298620284682558712820558193891787303533093197420998108945366704043133890680849252774739771188873183182185315971489373696493547256*I

The last line there is the output of the script when it was ran with the real flag as input, which we have to use to somehow recover the flag.

The majority of this post is about explaining some of the machinery behind the solution, even though it’s not strictly needed to solve.

Isomorphism to Complex Tori

The unit circle

Here we will be drawing many parallels between the paramaterization of elliptic curves and the unit circle. There will be some handwaving and basically no proper proofs, what I aim to deliver is some intuition.

The defining equation of the unit circle is

S1:x2+y2=1 S^1: x^2 + y^2 = 1

We can parameterize the unit circle by

r(θ)=(sin(θ),sin(θ))=(sin(θ),cos(θ))r(\theta) = (\sin(\theta), \sin'(\theta)) = (\sin(\theta), \cos(\theta))

and you can verify that for any angle θ\theta we have sin2(θ)+cos2(θ)=1\sin^2(\theta) + \cos^2(\theta) = 1 according to the Pythagorean identity.

In fact, f(x)=sin(x+C)f(x) = \sin(x+C) is the general solution of the differential equation f(x)2+f(x)2=1f(x)^2 + f'(x)^2 = 1, which suggests that this is a kind of “natural” parameterization.

This parameterization is periodic with a period of 2π2\pi, since

(cos(θ),sin(θ))=(cos(θ+2πn),sin(θ+2πn))     r(θ)=r(θ+2πn) (\cos(\theta), \sin(\theta)) = (\cos(\theta + 2\pi n), \sin(\theta + 2\pi n))\ \implies r(\theta) = r(\theta + 2\pi n)

for all nZn \in \mathbb{Z}.

We can define a group on the unit circle by defining a binary operation

:S1×S1S1r(θ1)r(θ2)=r(θ1+θ2) \begin{align*} \oplus: S^1 \times S^1 &\to S^1\\ r(\theta_1) \oplus r(\theta_2) &= r(\theta_1 + \theta_2) \end{align*}

which simply sums angles. Now because r(θ)=r(θ+2πn)r(\theta) = r(\theta + 2\pi n), θ\theta is ambigous for a given point up to integer multiples of 2π2\pi, i.e up to elements of 2πZ2 \pi \mathbb{Z}. We can explicitly encode this ambiguity as a quotient group which tells us that, with respect to the group operation \oplus, S1R/2πZS^1 \cong \mathbb{R}/2\pi \mathbb{Z}; the real numbers modulo 2π2\pi.

An elliptic curve

A general elliptic curve EE over C\mathbb{C} can, after a change of variables, be written in short Weierstrass form:

E:y2=x3+ax+b E: y^2 = x^3 + ax + b

Drawing from the previous section, it’s natural to ask if there is a similar parameterization of elliptic curves, perhaps one which also arises from a differential equation. Indeed, there is such a parameterization, which is given by the Weierstrass \wp function, defined over :CC\wp: \mathbb{C} \to \mathbb{C}.

Due to historical reasons it is a solution to the differential equation

(z)2=4(z)3g2(z)g3 \wp'(z)^2 = 4\wp(z)^3 - g_2\wp(z) - g_3

where g2g_2 and g3g_3 define the shape of the curve. We can use this to parameterize our familiar short Weierstrass form by letting (x,y)=((z),12(z))(x, y) = (\wp(z), \frac{1}{2} \wp'(z)) which satisfies the equation

((z)2)2=(z)3g24(z)g34 \left(\frac{\wp'(z)}{2}\right)^2 = \wp(z)^3 - \frac{g_2}{4}\wp(z) - \frac{g_3}{4}

which aligns with our definition of EE above if we let a=g24a = -\frac{g_2}{4} and b=g34b = -\frac{g_3}{4}. So to be clear, there is not one Weierstrass \wp function, but rather a family of them, parameterized by the coefficients g2g_2 and g3g_3. If they are evident from the context we often omit these parameters, otherwise we write (z;g2,g3)\wp(z; g_2, g_3).

An important property of (z)\wp(z) is that it is doubly periodic. sin(z)\sin(z) is, over the complex plane, only a singly periodic function; it repeats along the real axis, but not along the imaginary axis.

sin⁡(z)\sin(z)sin(z) plotted over C\mathbb{C}C using Samuel J. Li’s complex function plotter.

sin(z)\sin(z) plotted over C\mathbb{C} using Samuel J. Li’s complex function plotter.

(z)\wp(z) on the other hand has two different periods, ω1,ω2C\omega_1, \omega_2 \in \mathbb{C}. (z)\wp(z) is invariant under all integer linear combinations of these two periods, i.e for the lattice Λ\Lambda spanned by ω1\omega_1 and ω2\omega_2

Λ={mω1+nω2m,nZ} \Lambda = \{ m\omega_1 + n\omega_2 \mid m, n \in \mathbb{Z} \}

it holds that

(z)=(z+λ)λΛ \wp(z) = \wp(z + \lambda) \quad \forall \lambda \in \Lambda

For example, in the below figure we can make out the lattice points at which the function repeats.

℘(z)\wp(z)℘(z) with (ω1,ω2)=(1,12+i)(\omega_1, \omega_2) = (1, \frac{1}{2}+i)(ω1​,ω2​)=(1,21​+i) plotted over C\mathbb{C}C using Samuel J. Li’s complex function plotter

(z)\wp(z) with (ω1,ω2)=(1,12+i)(\omega_1, \omega_2) = (1, \frac{1}{2}+i) plotted over C\mathbb{C} using Samuel J. Li’s complex function plotter

The extremely important and cool property of (z)\wp(z) is that addition (in the normal C\mathbb{C}-sense) of the input parameters corresponds to addition of elliptic curve points, i.e

((z1),12(z1))+((z2),12(z2))=((z1+z2),12(z1+z2)) (\wp(z_1), \frac{1}{2} \wp'(z_1)) + (\wp(z_2), \frac{1}{2} \wp'(z_2)) = (\wp(z_1 + z_2), \frac{1}{2} \wp'(z_1 + z_2))

By a similar argument to previously, since (z)\wp(z) is ambigous up to elements of Λ\Lambda, we can with some handwaving conclude that E(C)C/ΛE(\mathbb{C}) \cong \mathbb{C}/\Lambda, also known as a complex torus.

The modulus

We say that two complex lattices Λ\Lambda and Λ\Lambda' are homothetic if there exists some cC×c \in \mathbb{C}^\times such that Λ=cΛ\Lambda' = c\Lambda. This means that they are equivalent up to a scaling factor, and it is equivalent with C/ΛC/Λ\mathbb{C}/\Lambda \cong \mathbb{C}/\Lambda' (proof as per usual omitted). This should not be very surprising; multiplication by a complex number is equivalent to a scaling and a rotation which does not meaningfully change the shape of the lattice.

We will only be interested in lattices up to homothety, we can therefore rescale a lattice basis ω1,ω2\langle\omega_1, \omega_2\rangle by 1ω1\frac{1}{\omega_1} to obtain a new basis τ,1\langle \tau, 1 \rangle where τ=ω2ω1\tau = \frac{\omega_2}{\omega_1}. By convention we restrict ourselves to Im(τ)>0\text{Im}(\tau) > 0, if that is not the case we can instead rescale by 1ω2\frac{1}{\omega_2}. This lets us represent this basis by a single number τ\tau, which we call the modulus, which is part of the upper half plane H={zCIm(z)>0}\mathbb{H} = \{ z \in \mathbb{C} \mid \text{Im}(z) > 0 \}.

The modulus is however not invariant under choice of basis for Λ\Lambda, let’s try to properly describe this.

If a lattice Λ\Lambda is defined by a basis τ,1\langle \tau, 1 \rangle then we can describe a change-of-basis by a matrix γ\gamma

γ[τ1]=[abcd][τ1]=[aτ+bcτ+d] \gamma \begin{bmatrix} \tau \\ 1 \end{bmatrix} = \begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} \tau \\ 1 \end{bmatrix} = \begin{bmatrix} a \tau + b \\ c \tau + d \end{bmatrix}

A classical result stemming from ordinary lattices in Rn\mathbb{R}^n implies that this new basis spans the same lattice if and only if a,b,c,dZa, b, c, d \in \mathbb{Z} and det(γ)=±1\det(\gamma) = \pm 1, which means γGL2(Z)\gamma \in \mathrm{GL}_2 (\mathbb{Z}), the 2×22\times 2 general linear group over Z\mathbb{Z}.

After this transformation we can then homothetically rescale by 1cτ+d\frac{1}{c\tau+d} to get back to our simplified form τ,1\langle \tau', 1 \rangle with τ=aτ+bcτ+d\tau' = \frac{a \tau + b}{c \tau + d}. With this transformation in mind we define the action of a matrix γZ2×2\gamma \in \mathbb{Z}^{2 \times 2} on τC\tau \in \mathbb{C} as

γτ=[abcd]τ=aτ+bcτ+d \gamma \cdot \tau = \begin{bmatrix} a & b \\ c & d \end{bmatrix} \cdot \tau = \frac{a\tau + b}{c\tau + d}

This is known as a Möbius transformation. As concluded previously the new lattice is only homothetic if det(γ)=±1\det(\gamma)=\pm 1. We further restrict ourselves to det(γ)=1\det(\gamma)=1 to avoid Im(τ)\mathrm{Im}(\tau) becoming negative, which means that γSL2(Z)\gamma \in \mathrm{SL}_2(\mathbb{Z}), the 2×22 \times 2 special linear group over Z\mathbb{Z}.

Furthermore γτ=(γ)τ\gamma \cdot \tau = (-\gamma) \cdot \tau, so we can quotient out by this ambiguity and consider γ\gamma to be in PSL2(Z)=SL2(Z)/{±I}\mathrm{PSL}_2(\mathbb{Z}) = \mathrm{SL}_2(\mathbb{Z}) / \{\pm I\}, also known as the modular group.

We can therfore finally say that τ\tau is defined up to the left action of PSL2(Z)\mathrm{PSL}_2(\mathbb{Z}) on H\mathbb{H}, in other words τPSL2(Z)H\tau \in \mathrm{PSL}_2(\mathbb{Z}) \setminus \mathbb{H}.

The j-invariant

If you are used to working with elliptic curves you might know that the jj-invariant of an elliptic curve EE with short Weierstrass coefficients (a,b)(a, b) is given by

j(E)=17284a34a3+27b2 j(E) = 1728 \frac{4a^3}{4a^3 + 27b^2}

and that if two elliptic curves E1E_1 and E2E_2 are isomorphic then they neccesarily have the same jj-invariant, i.e E1E2    j(E1)=j(E2)E_1 \cong E_2 \implies j(E_1) = j(E_2). Over an algebraically closed field like C\mathbb{C} this is an equivalence, i.e

j(E1)=j(E2)    E1E2j(E_1) = j(E_2) \iff E_1 \cong E_2

Going back to complex lattices, the jj-invariant can similarly be defined as function j:HCj: \mathbb{H} \to \mathbb{C} which is invariant under the action of PSL2(Z)\mathrm{PSL}_2(\mathbb{Z}) on H\mathbb{H}, which as we remember is the same as homothety of lattices, which is the same as isomorphism of elliptic curves. Since we have classified the ambiguity of τ\tau this is actually a bijection if we modify the domain to be PSL2(Z)H\mathrm{PSL}_2(\mathbb{Z}) \setminus \mathbb{H}, i.e

j:PSL2(Z)HC j: \mathrm{PSL}_2(\mathbb{Z}) \setminus \mathbb{H} \longleftrightarrow \mathbb{C}

You might have heard the term modular function before, and in fact this invariance under the modular group (together with some other properties) makes jj into a modular function of weight 00, but we won’t go down that rabbit hole here.

j(τ)j(\tau)j(τ) plotted over H\mathbb{H}H using Samuel J. Li’s complex function plotter. Notice the many different symmetries.

j(τ)j(\tau) plotted over H\mathbb{H} using Samuel J. Li’s complex function plotter. Notice the many different symmetries.

Isogenies

I won’t recap the definition of an isogeny here. The important thing to know is that an isogeny is a map ϕ:EE\phi: E \to E' between two elliptic curves which is a group homomorphism:

ϕ(P+Q)=ϕ(P)+ϕ(Q)P,QE \phi(P + Q) = \phi(P) + \phi(Q) \quad \forall P, Q \in E

The degree of an isogeny ϕ\phi is the size of the kernel #ker(ϕ)\# \ker(\phi) and ker(ϕ)\ker(\phi) is a finite subgroup of EE, so an isogeny of degree is 11 is simply an isomorphism because 00 has to be in the kernel.

Let’s restrict ourselves to \ell being prime. Then if deg(ϕ)=\deg(\phi)=\ell we have that ker(ϕ)\ker(\phi) is a cyclic subgroup of EE, and more specifically ker(ϕ)E[]\ker(\phi) \subseteq E[\ell]: the \ell-torsion subgroup of EE which consist of all points PEP \in E such that P=0\ell P = 0.

If we let Λ=τ,1\Lambda = \langle \tau, 1 \rangle be the lattice associated with EE then E[]E[\ell] corresponds to points zCz \in \mathbb{C} such that z0modΛ    zΛ\ell z \equiv 0 \mod \Lambda \iff \ell z \in \Lambda , more precisely

E[]{aτ+bmodΛa,bZ/Z}(Z/Z)2 E[\ell] \cong \left\{ \frac{a\tau + b}{\ell} \mod \Lambda \mid a, b \in \mathbb{Z}/\ell\mathbb{Z} \right\} \cong (\mathbb{Z}/\ell\mathbb{Z})^2

You might have seen this stated for elliptic curves over finite fields, this is really where that comes from. Let’s represent each point in E[]E[\ell] by (a,b)(Z/Z)2(a, b) \in (\mathbb{Z}/\ell\mathbb{Z})^2 according to the above construction. Then we can describe a cyclic subgroup by a generator (a,b)E[]\langle (a, b) \rangle \subseteq E[\ell]. Notice however that multiple generators can generate the same subgroup, since (a,b)=(ka,kb)\langle (a, b) \rangle = \langle (ka, kb) \rangle for k(Z/Z)×k \in (\mathbb{Z}/\ell\mathbb{Z})^\times, we therefore need to classify these subgroups up to scaling.

We do this by quotienting out by the relation (a,b)(ka,kb)(a, b) \sim (ka, kb), which yields

{[a:b](a,b)(0,0)}/P1(Z/Z) \left\{ [a : b] \mid (a, b) \neq (0, 0) \right\}/\sim \quad \cong \mathbb{P}^1(\mathbb{Z}/\ell\mathbb{Z})

which is the projective line over the ring Z/Z\mathbb{Z}/\ell\mathbb{Z}. The size of this set is +1\ell + 1 for prime \ell, and more generally it is ψ()\psi(\ell) where ψ\psi is the Dedekind psi function. What this all tells us is that there are +1\ell + 1 distinct cyclic subgroups of E[]E[\ell], and thus +1\ell + 1 distinct cyclic isogenies of degree \ell from EE. Furthermore the generator of the kernel looks like aτ+b\frac{a\tau + b}{\ell} for aa and bb as described above.

For prime \ell we have that

P1(Z/Z)={[1:k]kZ/Z}{[0:1]} \mathbb{P}^1(\mathbb{Z}/\ell\mathbb{Z}) = \left\{ [1: k] \mid k \in \mathbb{Z}/\ell\mathbb{Z} \right\} \cup \left\{ [0: 1] \right\}

So what does this tell us about the codomain of ϕ\phi, i.e the curve EE' and its lattice Λ=τ,1\Lambda' = \langle \tau', 1 \rangle? One can show that ϕ\phi, when defined as a map ϕ:C/ΛC/Λ\phi: \mathbb{C}/\Lambda \to \mathbb{C}/\Lambda' between the complex tori, is a complex linear map of the form ϕ(z)=cz\phi(z) = c z for some cC×c \in \mathbb{C}^\times. Since this is a group homomorphism we must have that ϕ(0)=0\phi(0) = 0, which means that all points of Λ\Lambda must be mapped to points of Λ\Lambda'. As a consequence we have that cΛΛc \Lambda \subseteq \Lambda'. Furthermore we have specified a subgroup which should also be mapped to 00, we must therefore also have that our kernel, generated by aτ+bmodΛ\frac{a\tau+b}{\ell} \mod \Lambda, is mapped to 00. All in all this means the full set of kernel points is generated by τ,aτ+b,1\langle \tau, \frac{a\tau + b}{\ell}, 1 \rangle. We can reduce this to only two generators, with two different cases:

τ,aτ+b,1={(a,b)=(1,k),aτ+b,1(a,b)=(0,1),τ,1} \langle \tau, \frac{a\tau + b}{\ell}, 1 \rangle = \left\{\begin{array}{lr} (a, b) = (1, k),& \langle \frac{a\tau + b}{\ell}, 1\rangle\\ (a, b) = (0, 1),& \langle \ell \tau, 1 \rangle \end{array}\right\}

The second case is from rescaling by \ell. With some handwaving we conclude that this will be homothetic to the lattice of the codomain, Λ\Lambda'. Written in terms of matrices and Möbius transformations we have that

τ=γτγS \tau' = \gamma \cdot \tau \quad \gamma \in S_\ell

where SS_\ell is the set of matrices which correspond to the transformations above:

S={[1k0]kZ/Z}{[001]} S_\ell = \left\{ \begin{bmatrix} 1 & k \\ 0 & \ell \end{bmatrix} \mid k \in \mathbb{Z}/\ell\mathbb{Z} \right\} \cup \left\{ \begin{bmatrix} \ell & 0 \\ 0 & 1 \end{bmatrix} \right\}

The challenge

The challenge, in a somewhat obscured way, is performing an isogeny walk starting from j0=1337j_0=1337 and choosing each next step based on the contents of the flag. The goal is thus to find the path between the given jendj_\text{end} and j0j_0.

The program uses a classsical modular polynomial to achieve this. A classical modular polynomial Φ(X,Y)\Phi_\ell(X, Y) is a symmetric polynomial such that Φ(j1,j2)=0\Phi_\ell(j_1, j_2) = 0 if and only if there exists a cyclic isogeny of degree \ell between the elliptic curves with jj-invariants j1j_1 and j2j_2. We can thus find curves which are 33-isogenous to a given jj-invariant by finding the roots of the univariate polynomial Φ3(j,Y)\Phi_3(j, Y). This univariate polynomial has degree 44, which aligns with our findings previously that there should be +1=4\ell+1=4 distinct cyclic isogenies of degree \ell from a given elliptic curve.

Based on the previous section, what do we know about the relation between j0j_0 and jendj_\text{end}? We have talked a lot about the action of isogenies on their corresponding lattices, so let’s start by calculating them.

You can do this manually using some complex AGM shenanigans, but since the year is 2025 it is already implemented in SageMath (make sure to use a recent version, otherwise .period_lattice() won’t be implemented for general fields).

1sage: j0 = CC(1337)
2sage: EllipticCurve(j=j0).period_lattice().tau()
30.131756389015516 + 0.991282126316011*I

This gives us exactly the τ\tau we have been working with from before. Recall however that τ\tau is ambigous for a given jj-invariant up to action of Möbius transformations of PSL2(Z)\mathrm{PSL}_2(\mathbb{Z}). Let’s take τ0=j1(j0)\tau_0 = j^{-1}(j_0) to be canonical. Then we have that τend=γτ0\tau_\text{end} = \gamma \cdot \tau_0 for some integer matrix γ\gamma. More specifically, it is a sequence of isogenies, i.e matrices from SS_\ell from before, and finally a PSL2(Z)\mathrm{PSL}_2(\mathbb{Z}) transformation which accounts for the ambiguity of the final τend\tau_\text{end}. We can thus write

τend=[abcd]τ0=(γi=1dsi)τ0γPSL2(Z),siS3 \tau_\text{end} = \begin{bmatrix} a & b\\ c & d\end{bmatrix} \cdot \tau_0 = \left(\gamma \prod_{i=1}^d s_i \right) \cdot \tau_0 \quad \gamma \in \mathrm{PSL}_2(\mathbb{Z}), s_i \in S_3

where dd is the number of base 33 digits in the flag. We want to recover the matrix of this transformation. Per definition of the Möbius transformation we have that

τend=aτ0+bcτ0+d    τend(cτ0+d)=aτ0+b    τ0a+bτ0τendcτendd=0 \begin{align*} \tau_\text{end} = \frac{a \tau_0 + b}{c \tau_0 + d} \implies \tau_\text{end} (c \tau_0 + d) = a \tau_0 + b\\ \implies \tau_0 a + b - \tau_0 \tau_\text{end} c - \tau_\text{end} d = 0 \end{align*}

This is a linear equation in the variables a,b,c,dZa, b, c, d \in \mathbb{Z} which we want to find a close solution to (due to precision issues it won’t be exact). Since they are complex numbers this becomes two real-valued equations, saying that both the imaginary and real part should be zero. This is a perfect application of lattice reduction, let’s therefore reduce the lattice spanned by the rows of the following matrix

[Re(τ0)Im(τ0)1000100100Re(τ0τend)Im(τ0τend)0010Re(τend)Im(τ0τend)0001][W000000W0000001000000100000010000001] \begin{bmatrix} \mathrm{Re}(\tau_0) & \mathrm{Im}(\tau_0) & 1 & 0 & 0 & 0 \\ 1 & 0 & 0 & 1 & 0 & 0 \\ \mathrm{Re}(-\tau_0 \tau_\text{end}) & \mathrm{Im}(-\tau_0 \tau_\text{end}) & 0 & 0 & 1 & 0 \\ \mathrm{Re}(-\tau_\text{end}) & \mathrm{Im}(-\tau_0 \tau_\text{end}) & 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} W & 0 & 0 & 0 & 0 & 0 \\ 0 & W & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 \end{bmatrix}

Where WW is sufficiently large, 213372^{1337} works since 13371337 is the bit-precision of the given numbers. We thereby hope to find the following vector

[00abcd] \begin{bmatrix} \approx 0 & \approx 0 & a & b & c & d \end{bmatrix}

Which works. There are now (at least) two different ways to proceed.

Let’s calculate the determinant of the recovered transformation:

det(γi=1dsi)=det(γ)i=1ddet(si)=3d \det\left(\gamma \prod_{i=1}^d s_i\right) = \det(\gamma) \cdot \prod_{i=1}^d \det(s_i) = 3^d

This means that we can tell at any given jj-invariant how far away we are from j0j_0 by calculating the determinant of this transform. From there you can simply try all neighbors of jendj_\text{end} and check if they get closer or further away from j0j_0. Repeating this you can walk backwards and recover the path of jj-invariants, which yields the flag digits.

Less simple method: Factor the transformation

I thought it would be fun to try and factor the transformation directly, without using the jj-invariants for guidance. I am however too lazy to write this up, so I will just leave the code here in case anyone is interested in understanding it.

 1from sage.all import *
 2from sage.geometry.hyperbolic_space.hyperbolic_isometry import moebius_transform
 3
 4def normalize_Sn(M):
 5    # make upper-triangular
 6    c, d = matrix([[M[0,0], M[1,0]]]).right_kernel_matrix()[0] 
 7    g, a, b = xgcd(d, -c)
 8    assert g == 1
 9    M = matrix([[a, b], [c, d]]) * M
10
11    # positive diagonal
12    assert M[0,0].sign() == M[1,1].sign()
13    if M[0,0] < 0:
14        M = -M
15
16    # make 0 <= b < d
17    M[0, 1] %= M[1, 1]
18    return M
19
20def product_candidates(M, pathlen, path=()):
21    M = M.change_ring(ZZ)
22    if M.is_one():
23        yield path
24        return
25    if len(path) == pathlen:
26        return
27
28    if M[0, 0] % l == M[0, 1] % l == 0:
29        T = matrix([[l, 0], [0, 1]])
30        yield from product_candidates(T**-1*M, pathlen, path + (T,))
31    
32    if M[1, 1] % l == 0:
33        b = M[0,1]//(M[1,1]//l)
34        T = matrix([[1, b], [0, l]])
35        yield from product_candidates(T**-1*M, pathlen, path + (T,))
36
37def inv_j(j):
38    return EllipticCurve(j=j).period_lattice().tau()
39
40C = ComplexField(prec:=1337)
41j0 = C(1337)
42j = C('...') # the given output
43
44phi = classical_modular_polynomial(l:=3)
45tau0, tauA = inv_j(j0), inv_j(j)
46
47M = matrix([[tau0, 1, -tau0*tauA, -tauA]]).T
48L = (M.apply_map(real)
49     .augment(M.apply_map(imag))
50     .change_ring(QQ)*2**prec).augment(identity_matrix(M.nrows()))
51M = matrix(ZZ, 2, 2, L.LLL()[0][2:])
52ndig = M.det().valuation(l)
53assert M.det() == l**ndig
54
55for path in product_candidates(normalize_Sn(M), ndig):
56    digits = []
57    j, tau = j0, tau0
58    jp = j
59    for mat in path[::-1]:
60        roots = phi(X=j).univariate_polynomial().roots(multiplicities=False)
61        roots.sort(key=jp.dist)
62        del roots[0]
63        roots.sort(key=arg)
64
65        tau = moebius_transform(mat, tau)
66        j, jp = elliptic_j(tau), j
67        d = min(range(len(roots)), key=lambda i: j.dist(roots[i]))
68        digits.append(d)
69    print(ZZ(digits, l).to_bytes(32).lstrip(b'\x00').decode())