Time: 2025.03.29 (online)
Time to learn more about elliptic curve :D
These Walls (5 solves)
https://github.com/soon-haari/my-ctf-challenges/tree/main/2025-codegate/crypto-thesewalls
- chall.py
- output.txt
Our target is to solve DDH/DLP on ECC to get the key
.
Curves Recovery
First we need to recover the elliptic curve from the given points. This can be easily done by groebner_basis
on ZZ.
1 | Ps = [ast.literal_eval(P) for P in open("output.txt", "r").readlines()[2:]] |
1 | n = 102426836944425277819174256940128868455066517869185115488781035394164877927042198980921397582250905694209313963017944880679709788694772632564808279062807699845761214703586660326307989151472431126842544189892251409194318824261554131269698538462346988910692541060196190265045309883144365795763912362099350625483 |
But this is not enough, since we have a composite $n$ here.
For a curve $E_p$ with order $o_p$, we have $o_p \cdot P = (0, 0)$. When it comes to $E_n$ where $p | n$, we have $o_p \cdot P = (k_x \cdot p, k_y \cdot p)$. Multiplication based on this may fail since there’s no inverse element for $k \cdot p$, and we can extract $p$ through the error message.
In short, I just kept run this and get new prime factors with gcd
.
1 | o = 118549579796788515994414649236369265269780996946642393375504713268996543836492572997532134962413661837052222678148616587175464294656866212083879163757595198637277720148049394221272652120067946542481202345536577647854319626518830885330021003068475187962796231844540355486949322214342559093662111580693859480 |
1 |
|
Now we need to solve DDH on each curve $E_{p_i}$.
Part1: Singular Curve
Why are singular “elliptic” curve bad for crypto
For an elliptic curve $E: y^2 = x^3 + a \cdot x + b \pmod{p}$, the curve is singular if $\Delta = 4 a^3 + 27 b^2 = 0 \pmod{p}$, which implies that $f(x) = x^3 + a \cdot x + b \pmod{p}$ has a double/triple root.
It turns out that we have a singular curve $E_{p_0}$. At this point we can transfrom the original DLP into a much easier one.
- $f(x) = (x - \alpha)^3$: There are exacly $p$ non-singular points, and we can map them to the addictive group of $GF_p$ through $(x, y) \rightarrow \frac{x - \alpha}{y}$.
- $f(x) = (x - \alpha)^2 \cdot (x - \beta)$: There are $p-1$ or $p+1$ points, and we can map them to the multiplicative group of $GF_{p^2}$ (with $p^2-1 = (p-1)\cdot(p+1)$ elements) through $(x, y) \rightarrow \frac{y+\sqrt{\alpha - \beta} \cdot (x - \alpha)}{y-\sqrt{\alpha - \beta} \cdot (x - \alpha)}$.
- $f(x) = (x - \alpha) \cdot (x - \beta) \cdot (x - \gamma)$: Nah this is impossible.
In case 2, it’s quite clear that we extend the group with $x^2 = (\alpha - \beta) \pmod{p}$. If there exist the corresponding square roots, we just map the original group to $GF_p$.
We have $ p_0+1 = 2 \cdot 7841 \cdot 240017 \cdot 1190977577 \cdot 79471936121$, and it takes less than 10s to solve one DLP on $GF_{p^2}$. Actually we can just solve one or two of them, and check DDH with the rest.
1 | def singular_attack(p, a, b, P, Q): |
1 | s0 = 1688818121111580066310934554129 |
Part2: p-adic Field
For $(p_1, p_2, p_3)$, we need to solve DLP/DDH on $E_{p^k}$. It turns out that the order is $o_p \cdot p^{k-1}$ where $o_p$ is the order of $E_p$. With p-adic field, we can just solve DLP $\pmod{p^{k-1}}$. This is similar to solve DLP $y = g^x \pmod{p^k}$.
Really fast.
1 | def padic_attack(p, k, a, b, P, Q): |
1 | s1 = 161442084316875579136566655050 |
Part3: MOV
Now we just need to solve DLP/DDH on $E_p$ for $(p_1, p_2, p_3)$. These curves all satisfy $o_p = p+1$, so the embedding degree $k = 2$. This time we can try MOV attack which maps curve points to elements on $GF_{p^k}$ with pairing tech, solving an easier DLP.
It is worth to notice that we can solve DLP more easily only because $GF_{p^k}$ has better structure than $E_p$. The order of the elements on $GF_{p^k}$ is $o_p$ and it’s still hard to solve DLP if $o_p$ has large factors. In this challenge, we can solve completely DLP on $p_1$ since $p_1 + 1 = 2^3 \cdot 3^2 \cdot 5 \cdot 31 \cdot 3517 \cdot 673063 \cdot 139792886025540383$.
It’s said to be possible to solve such DLP on $GF_p^2$ with cado-nfs.
It takes about 20min to solve one, so this time we have to solve one or two answers and then check DDH on the rest.
1 | def mov_attack(p, a, b, P, Q): |
1 | s1 = 860437940168965817900625942259 |
Part4: Distortion Map
Since it’s hard to completely solve DLP for $(p_2, p_3)$, we need to find some luck on DDH itself. An apparent idea is to solve DDH with pairing. For instance, we can check whether $e(P_i, P_{j+1}) = e(P_j, P_{i+1})$ holds.
But this doesn’t work, cuz you will find $e(P_i, P_{i+1}) = 1$ for all $i$. For weil_pairing
, we shall take two points from different subgroups to compute bilinear pairing, otherwise it will just degenerate to 1. I’m not familiar with both weil_pairing
and tate_pairing
but neither of them worked.
Here, both curves on $(p_2, p_3)$ are supersingular since $o_p = p+1$. There exist several special supersingular curves on which we can do something cool. For this challenge, here is what we need:
- Type A Curve: $y^2 = x^3 + a \cdot x$
- Type B Curve: $y^2 = x^3 + b$
One can find a distrotion map $\Phi: E_p \rightarrow E_{p^2}$ on these curves, and construct a nondegenerate bilinear map $e(P, Q) = f(P, \Phi(Q))$ (where $f$ is the original weil_pairing
). You can check for more details in the materials above.
Here we can use both of them to solve DDH on $(p_2, p_3)$.
1 | def type_a_ddh_attack(p, a, b, P0, Q0, Pi, Qi): |
1 | ans2 = [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1] |
Get Flag
Finally.
We need to combine the DDH answers from all subgroups. For any position i, if one 0 appears then the answer is 0. We may mischeck some 0s to 1s in certain subgroups, but the opposite condition will never occur. For the first position, just guess it out.
1 | ans = [ |
Special thanks to @糖醋小鸡块, who helped untangle many of the mathematical knots for me.