Once in a while, there comes a problem that makes you more conscious about your insignificant intelligence... Today, it comes in the form of a CTF challenge :'<<
Basically, there's this challenge:
20000156
terms in , where 20000159
,FLAG
XORed with the sha512()
hash of 's coefficients array.Since it's hard to crack sha512()
, or reverse XOR function with 0 knowleges about one of its inputs, our only route is to recover from its outputs.
So, if you're a noob like me, your first thought will probably be:
"Well, this is a piece of cake.
I'll just use the Lagrange Interpolation. They have this fancy math that computes a polynomial's coefficients from its evaluations.
We meet all the requirements needed to carry out the computation."
So you gleefully copy an implementation from the Internet, adding inputs, modify stuffs, typing, thinking to yourself: "EZ".
You run the code and wonder why it takes so long. Maybe the output array is too big and needed some time to load? No, it had already been done! You throw a tqdm
bar into the main loop of the code and see that it estimates the algorithm will be running for another 7000
hours.
Shit 💩.
You dig into the implementation of Lagrange Interpolation, and realize that given a polynomial with terms:
An runtime...
This algorithm works fast for small polynomial sizes, like 2
or 3
and some numbers close to it like 10000
, but not 20000156
. This is when you GASP: "This is not Crypto, this is some Codeforces obsure 200+ IQ optimization problem!!" You know you suck, but you still want try solving a coding optimization stuffs anyways, to see where it goes.
From your experiences of watching math videos on Youtube, feeling smart then remember like 1% about it the next day, you realize that you still don't know what to do now and feel like shit (meanwhile, your teammate is saying something about vandermonde) maybe sometimes we need to derail to some random ideas to get to smart one, so that's what you did.
So you definitely did look at this wiki page and did not have an insight on your own:
The evaluation of at can be represented as a dot product: .
Or maybe, as a multiplication of 1xN
matrix with another Nx1
matrix:
And you see that you can extend the 1xN
matrix on the left so that on the right side you can compute not only , but also and so on:
The NxN
matrix on the left side seems like a special type of matrix. Maybe you discovered a new thing 😱 (and not something already been discovered and called the Vandermonde Matrix)? Anyways, you decide to name this thing the Vandermonde Matrix, because it sounds right somehow...
Let's rewrite the equation into: , where:
Which leads to . As long as , is invertible. Because we have and want to find , this shifts our focus into...
A couple of Google searches leads us to this website:
You have a hunch that this route sucks. Matrix multiplication with a vector still takes as far as you know, and at the same time, EggRoll
, from your team says this:
Others seem to brush off his idea, and to be honest, they seem tired and confused at that point, so it's understandable. But you remember from the 1% of knowledge you watched in some Youtube video, that FFT is some algorithm that can be done in , which is something way better than , is probably exactly what we need right now!!
Hmm... But you forget what it does...
Fast Fourier Transform, or FFT for short, is an algorithm that also can do the convert coefficients -> values thingy. However, this algorithm has a restriction:
The inputs to must be , instead of arbitrary values , where is any -th root of unity (except 1
to avoid ).
Hmm.. Actually that's what I understand about this...
The actual purpose of FFT seems to be some values -> frequency thingy conversion stuffs. But in this case, it still true (?) idk... math is weird... Maybe I'm wrong on something @.@
But if you want to know the actual definition of FFT, you can read it here: Wiki
To understand why FFT does this, let's formulize things.
Same as before, this relationship between coefficients and values can be represented as the product of a Vandermonde matrix with the coefficient vector :
Let's rewrite the equation into: , where:
Same as before, we want to convert values in -> coefficients , which is esentially the inverse process of FFT.
We have: , where is invertible since . It turns out that the inverse of a DFT matrix is another DFT matrix scaled down by :
Since is also just another root of unity & multiply with a DFT matrix is just doing FFT, this means that the inverse process of FFT (IFFT) is just another FFT with an extra step of scaling down all the outputs by , which is an step.
What you can take away from this is that IFFT is as fast as FFT.
If you look at the other methods we found to convert values -> coefficients, the difference it has with the backward process involves an additional process of inverting a really big matrix: A very complex process taking a lot of CPU & RAM. So this seems like a huge hidden win that we just discover blindly :-3
Also, this means that if you can somehow make FFT fast, you can do FFT back-and-forth with ease.
And it turns out there's an optimized version of FFT that has runtime, called the Radix-2 FFT! For more details, please consider watching this video by Reducible, this guy is really good :>
So now it seems like we've found our path! However, if you've watched the video like me, you'd notice there are a few problems...
A lot of oh-noes...
In order for this optimized version to work, must be a power of 2 (that's why this is called "Radix-2 FFT"), and 20000156
, well, is not. But we can sort of "cheat" by "pretending" that has more than 20000156
terms, with the leading coefficients are all s.
So why not, say, think that has the number of coefficients = the closet power of two 20000156
, idk, like ?
But then that would mean we need more than 20000156
outputs!! We might want to use Lagrange Interpolation to recover more values. But as we analyzed from the Lagrange Interpolation section, it takes for each input to produce the output!!
Also, in this CTF challenge, we can only get 3
more outputs, because , so is just ... And none of the numbers from 20000156
to 20000159
are powers of 2.
Note from Mistsu in the future:
It turns out, in this particular CTF challenge, we can AND just need to recover , where each calculation takes due to some very clever parameter choices, while still can do FFT in !
but we're ahead of ourselves at this point heheheh :-)
Say, we're able to extend the length of to . By then we still wouldn't be able to do IFFT: the algorithm requires a -th root of unity, and no such thing exists in .
Because of number theory and group theory stuffs, any subgroup of the multiplicative group must have order dividing . This means good luck trying to find such that when ... You can start with 3
to experiment 😘
The only prime factors of are 2
, 10000079
, that means only has 2
, 10000079
, or 20000158
-th roots of unity! But apperantly that means we can only work with cases where = 2
, 10000079
or 20000158
!?
Since we can only "pretend" it has more coefficients than 20000156
, we're locked to the choice of 20000158
. Since 20000158
is not a power of 2, it seems like we can't do FFT in .
Note from Mistsu in the closer future:
we can :-)
...You might be tempted to extend the current working field to , where you can find subgroups of with order dividing . However, up until , is divisible by at best. And you might want to stop here, because going further means that your CPU & RAM goes bye bye computing those elements. And you also have to do Larange Interpolation to have outputs of in that field...
From this point, I keep exhaust myself until the end of the CTF, trying out ideas, but nothing works. If it works, then the runtime is often where is the biggest prime of ... And then, the author publish their solution in the Discord Server,
... to which Genni
responds:
After reading this, my mind feels even more clueless... So this post should stop here. Part 2 will be focusing on how to understand the solution :v