WannaGame Championship 2023
Table of Contents
WannaGame Championship 2023
I played WannaGame Championship 2023 with my team @1337%_Yogurt and ended up with 8th place. To get a better rank in next year, I should learn more :((. Here is my writeup for 2 challenges i solved in the competition: cossin and ezcurve
Cossin
In this challenge, we have ans = sin(flag)*cos(flag)
and ans
has 1337
bits after decimal point. With a little trigonometry, we will have:
$$2 * flag + small = arcsin(2*ans) + k * 2\pi$$
(We have $small$ due to floating-point)
Now, we can use LLL to find flag with this basis: $$\begin{bmatrix} \arcsin(2*ans) & 0 & 1 \newline -1 & 1 & 0 \newline \pi & 0 & 0\end{bmatrix}$$
and we should have a vector $v = (small, 2 * flag, 1)$
We also need to make the number bigger to make lattice reduction can produce the vector we want. The matrix i use in this challenge is: $$\begin{bmatrix} [(\arcsin(2*ans))*2^{1200}] & 0 & 2^{480} \newline -2^{1200} & 1 & 0 \newline \pi * 2^{1200}& 0 & 0\end{bmatrix}$$
So we will have a small vector $v = (a, 2 * flag * {a \over b}, b )$ due to scale matrix and we will have flag
solve.sage
from Crypto.Util.number import *
RR = RealField(4000)
x = RR("-0.4852...")
ax = asin(RR(2*x))
rpi = RR(pi)
M = Matrix(ZZ, [[(ax * 2**1200).round(), 0, 2**480],
[-2**1200, 1, 0 ],
[(rpi * 2**1200).round(), 0, 0],])
print(M.LLL())
for r in M.LLL().rows():
if r[-1]:
flag = int(r[1] * M[0,2] // r[-1])
print(long_to_bytes(abs(flag)//2))
flag: W1{B4by_m4th_f0r_LLL_0dbb94edb18d7cba7b2bb20f9e}
The idea for this challenge maybe come from imaginaryCTF 2023 - Tan And I have a first blood in this challenge <3
ezcurve
chall.py
from sage.all import *
from Crypto.Util.number import bytes_to_long
from secrets import SystemRandom
from secret import flag
class Point:
def __init__(self, x, y) -> None:
self.x = int(x)
self.y = int(y)
def __str__(self) -> str:
return f"({self.x},{self.y})"
class Curve:
def __init__(self) -> None:
self.generate_parameters()
def generate_parameters(self) -> None:
rng = SystemRandom()
self.p = random_prime(2**512-1, False, 2**511)
self.k = rng.randint(1, self.p - 1)
while True:
x = rng.randint(1, self.p - 1)
D = (self.k + (1 - self.k)*x**2) % self.p
if legendre_symbol(D, self.p) == 1:
r = rng.choice(GF(self.p)(D).nth_root(2, all=True))
y = (1 + rng.choice([-1, 1])*r) * inverse_mod(x * (self.k - 1), self.p) % self.p
self.G = Point(x, y)
break
def get_parameters(self):
return self.G, self.k, self.p
def add(self, P: Point, Q: Point) -> Point:
x = ((1 + P.x*P.y + Q.x*Q.y)*inverse_mod(P.x*Q.x, self.p) + (1 + self.k)*P.y*Q.y) % self.p
y = (P.y*(inverse_mod(Q.x, self.p) + Q.y) + (inverse_mod(P.x, self.p) + P.y)*Q.y) % self.p
# so weirddd :<
return Point(inverse_mod(x - y, self.p), y)
def mult(self, G: Point, k: int) -> Point:
R = Point(1, 0)
while k:
if k&1:
R = self.add(R, G)
G = self.add(G, G)
k >>= 1
return R
curve = Curve()
G, k, p = curve.get_parameters()
print(f"G = {G}")
print(f"k = {k}")
print(f"p = {p}")
print(f"H = {curve.mult(G, bytes_to_long(flag))}")
We’ll see that the group we are dealing with in the challenge is the set of points on some hyperbola over $\mathbb{F}_p$. When dealing with this kind of challenge, we should know what hyperbola is used, and finding an isomorphism to solve the DLP. In this writeup, I will use operator $*$ instead of add
to denote the addition, and $.$ instead of mult
for multiplication
Finding the hyperbola
Now we should look to generate_parameters
and see how the point $G(x,y)$ is generated. Look at the equation of $y$ and notice that we are working in $\mathbb{F}_p$:
$$\begin{aligned} D &= k + (1 - k)x^2 \newline y &= {1 + \sqrt{k + (1 - k)x^2} \over x*(k-1)}\end{aligned}$$
($y$ also can be equal to $1 - \sqrt{k + (1 - k)x^2} \over x*(k-1)$, but it doesn’t affect too much)
With some algebra, we can lead to the equation: $$(y + {1 \over x})^2 - k*y^2 = 1 \mod p \ (1)$$
And this one look like Pell’s equation!
Finding the group order
Working with Pell’s equation is a good choice to find the group order. We will call set of all points $G$ that satisfy $(1)$ is $\mathcal{H} \subset \mathbb{F}_p \times \mathbb{F}_p$.
In this challenge, ${k \choose p} = -1$, so $\mathcal{H} \cong \mathcal{S}$ where $\mathcal{S} \le \mathbb{F}_{p^2}$
is the cyclic subgroup of $\mathbb{F}_{p^2}$ of order $p+1$
Let $f(W) = W^2 - k$, note that ${k \choose p} = -1$ so $f(W)$ is irreducible over $\mathbb{F}_p$.
Therefore, $\mathbb{F}_{p^2} \cong \mathbb{F}_p[W]/f(W)$. Let $\alpha \in \mathcal{S}$, we will have $\alpha^{p+1} = 1$ (since $\mathcal{S}$ has order $p + 1$). But we can write $\alpha = r + sW$ for $r, s \in \mathbb{F}_p$. So: $$\begin{aligned} \alpha^{p+1} &= (r + sW)^{p}(r + sW) \newline &= (r^p + s^pW^p)(r + sW) \ \text{(Freshman’s Dream)} \newline &= (r - sW)(r+sW) \ \text{(Fermat’s little theorem)} \newline &= r^2 - ks^2 \ (W^2 = k)\end{aligned}$$
$(W^p = W * W^{p-1} = W * W^{2 * \frac{p-1}{2}}=W*(-k)^{\frac{p-1}{2}}=-W)$
So, if we take $r = {1 \over x} + y$, $s = y$, we will have $\alpha^{p + 1} = 1 = (y + {1 \over x})^2 - k*y^2$. Therefore, we have the bijection $\varphi:\mathcal{H} \to \mathcal{S}, (x, y) \mapsto (y + {1 \over x}) + yW$. To see that this is an isomorphism, let $(x_1, y_1), (x_2, y_2) \in \mathcal{H}$, then:
$$\begin{aligned} \varphi((x_1, y_1)) * \varphi((x_2, y_2)) &= (y_1 + {1 \over x_1} + y_1W)(y_2 + {1 \over x_2} + y_2W) \newline &= ((y_1 + {1 \over x_1})(y_2 + {1 \over x_2}) + ky_1y_2) + (({1 \over x_1}+y_1)*y_2 + ({1 \over x_2}+y_2)*y_1)W \end{aligned}$$
$$\begin{aligned} \varphi((x_1, y_1)*(x_2, y_2)) &= \varphi(({1 \over X-Y}, Y)) \newline &= X + YW \newline &= ((y_1 + {1 \over x_1})(y_2 + {1 \over x_2}) + ky_1y_2) + (({1 \over x_1}+y_1)*y_2 + ({1 \over x_2}+y_2)*y_1)W \end{aligned}$$
With $X, Y$ are using in add
function.
So we will have $\varphi((x_1, y_1)*(x_2, y_2)) = \varphi((x_1, y_1)) * \varphi((x_2, y_2))$.
Therefore $\varphi$ is isomorphism, and we have $\mathcal{H} \cong \mathcal{S}$.
Solving DLP
We can convert from solving DLP over $\mathcal{H}: H = flag.G$ to solving DLP over $\mathcal{S}: \varphi(H) = \varphi(G)^{flag} \mod p+1$. In this challenge, $p+1$ is smooth, so we can use Pohlig-Hellman algorithm and BSGS to get the flag. The script takes about 5 minutes to run
solve.sage
from Crypto.Util.number import *
from sage.all import *
from tqdm import tqdm
G = (1607839310176493294577353252762003557221546105757870506381340801134253239376966744205427298664590763576740881572639607384799487951979779696144987477886421,1546749547518777239980509968598193601633396498557542394168100420202891576769741394183857889526837213932582824502124357549905169585573747409557599621838564)
k = 175806172363518431677991045199437670764180356876889297661164788697180717989119773022617665480637013518977586886854217110565695010685506018921813356910662
p = 5338840643981528656349879965316693353037572951085003676090767888721759017683550934207418173641013210787674901617992492337769850068363888321691530955051293
H = (552851895574391510222065278797325133695471300278396325740646639459534389667059010831628455078831526160904926692351570571639949524130986399437322683710262,695603652246255799710910441634342904537480443249396978184960366135064878749959654747187361028053230898467803441940859671759866661306050316241278578859544)
assert legendre_symbol(k, p) == -1
F.<x> = GF(p)[]
R.<W> = GF(p**2, modulus=x**2-k)
def convert(G): #Isomorphism
x, y = G
r = (y + inverse_mod(x, p)) % p
s = y % p
return (r - s * W)
g = convert(G)
ng = convert(H)
primes_list = list(factor(p+1))
#Because flag is always odd
dlogs = [1]
mods = [2]
def bsgs(g, h, p):
N = ceil(sqrt(p))
tbl = {g**i:i for i in range(N)}
c = g**(N * (p-1))
for j in range(N):
y = h * c**j
if y in tbl:
return j * N + tbl[y]
for prime, _ in primes_list[1:]:
t = (p + 1)//prime
gg = g**t
ngg = ng**t
dlog = bsgs(gg, ngg, prime)
print(dlog, prime)
dlogs.append(dlog)
mods.append(prime)
from functools import reduce
def chinese_remainder(n, a):
sum = 0
prod = reduce(lambda a, b: a*b, n)
for n_i, a_i in zip(n, a):
p = prod // n_i
sum += a_i * inverse_mod(p, n_i) * p
return sum % prod
flag = chinese_remainder(mods, dlogs)
print(long_to_bytes(flag))
flag: W1{P3ll_Curv3_1s_fun_r1ght?_532e3a90d802f4a3e3ce25b5f72d93d4}
P/s. This challenge took me nearly 12 hours to solve :((
And it isn’t a game.