Arkav 2025
very cool final
Chall name : Guess God
Tags : hard, Arkavidia Finals 2025
Okay, i fixed it now, clearly the problem are the elliptic curves! Now, your best odds is probably by guessing
Author: Etynso
TL; DR Predictable RNG via Forced Constant Seed
Key Insight:
The RNG's next seed is the x-coordinate of
(current_seed * G1)in Variety1.By setting
b1=0and choosingG1=(1,1)in Variety1, scalar multiplication (e.g.,seed * G1) always results in an x-coordinate of 1. This forces the seed to become 1 after the first iteration, making subsequent outputs constant.
Exploit Steps:
Set
b1=0in Variety1 to simplify addition/multiplication formulas.Choose
G1=(1,1)so all scalar multiples ofG1have x=1 (seed locked to 1).Predict Output: The RNG's output is
(G2.x >> 12)(G2's x-coordinate from server, right-shifted by 12 bits).
Constraints Met:
G1’s coordinates (1,1) are non-zero.Large scalar multiples of
G1won’t be the identity (y-coordinate isn’t 0).
Script Flow:
Connect to server, read parameters (
a1,Gx, etc.).Send
{"b1": 0, "x": 1, "y": 1}to defineG1.Compute
predicted = Gx >> 12.Send
{"future": predicted}repeatedly to win the flag.
Result: All RNG outputs become predictable as Gx >> 12, granting the flag after enough correct guesses.
start solving
files given :
so this challenge tasked us to predict a custom RNG where every time we predict correctly we will get more aura. Reaching a certain amount of aura rewards us with the flag because we are so sigma 😎.
the challenges uses 2 algebraic structures from Variety1.py and Variety2.py and we were given the initial seed from the challenge, we also have some control over the parameters in Variety1
Understanding the challenge :
so the number p provided that is
is a large prime that will be the mod for all Variety instructions
variety 1
the Variety1 and Variety1Element this class will define a mathematical group like structure where elements are pairs of coordinates.
G1 = E1(x, y)
we will create a point on this variety and choose the x and y for G1
for Variety1 the identity element for addition is (1, 0). This means P + (1,0) = P
Given p0 = (x0, y0) and p1 = (x1, y1) the sum p_new = (x_new, y_new) is:
The crucial part here is how b1 that we can control changes x_new.
k * P is equivalent to adding P to itself k times
result = P + P + ..... + P (k times)
the implementation uses the standard double-and-add algorithm starting with the identity element (1,0)
variety2
similar to Variety1 this is another custom algebraic structure
the server defines E2 = Variety2(a2, b2) and a point G2 = E2(Gx, Gy) on it using parameters a2, b2, Gx, Gy that it provides to us
for Variety2, the identity element is (inverse(a*b, p), 0)
the exact formulas for addition and multiplication in Variety2 are more complex but follow similar principles of combining coordinates
the key for us is that G2 is fixed from our perspective
RNG
this is the important part of the challenge
it will initialize the RNG via
rand = RNG(initial_seed, P, Q)
the seed is a random integer chosen by the server.
P: This is our G1 # this is a Variety1Element
Q: This is the servers G2 # this is a Variety2Element
number generation (in RNG)
this is scalar multiplication of self.P (our G1) by the current seed t
the result is a point on Variety1. s is the x coordinate of this resulting point
This is scalar multiplication of self.Q (the servers G2) by the new seed s
The result is a point on Variety2. r is the x coordinate of this resulting point
return (int(r)) >> 12
The final output is the integer part of r, right-shifted by 12 bits (equivalent to floor(r / 2^12))
server constraints
the server provides a1 (Variety1), and a2, b2, Gx, Gy (Variety2, defining G2)
we must send back b1 and the coordinates (x, y) for our point G1 = Variety1(a1, b1)(x, y).
the server imposes constraints on our choice of G1:
(195306067165045895827288868805553560 * G1).list() != [1, 0]: our point G1, when multiplied by a large constant, must not result in the identity element (1,0) of Variety1
x1 != 0 and y1 != 0
the coordinates of our chosen G1 must be non-zero
The Vulnerability
the goal is to make the output of RNG.next() predictable
output = (( ( (old_seed * G1).x ) * G2).x ) >> 12
and the new seed is (old_seed * G1).x
If we can make (t * G1).x (which is s, the new seed) a constant value, then:
The RNG.self.seed will become this constant value after the first call to next().
In all subsequent calls, t (the seed) will be this constant.
s will remain this constant: (constant_seed * G1).x.
Then r = (constant_s * G2).x will also be a constant value.
Consequently, r >> 12 will be a constant, predictable future.
Exploitation
the strategy is we will try to force a constant seed
How can we make s = (t * G1).x a constant specifically 1?
Recall Variety1Element.add's x-coordinate formula:
X_new = (x0x1 - b1y0*y1) % p
the choice of b1 (for Variety1(a1, b1))
If we choose b1 = 0 the formula for the x-coordinate of a sum simplifies dramatically
x_new = (x0x1 - 0y0y1) % p = (x0x1) % p
the choice of x for our point G1 = (x_g1, y_g1) If we set b1 = 0 AND choose x_g1 = 1 for our point G1
Let G1 = (1, y_g1) consider G1 + G1 (which is 2G1) x0=1, y0=y_g1 and x1=1, y1=y_g1 x_new = (1 * 1) % p = 1 the y-coordinate will change, but the x-coordinate remains 1 so 2G1 = (1, some_new_y) by induction for any scalar k > 0, the x-coordinate of k * G1 (when b1=0 and x_G1=1) will be 1^k % p = 1
applying this to the RNG's seed generation The RNG calculates s = (t * G1).x. If we choose b1=0 and x_G1=1 then no matter what the current seed t is (t > 0), the x-coordinate of the point t * G1 will be 1
then s = 1
the RNG.self.seed will become 1 after the very first call to next()
In all subsequent calls to next():
t = self.seed will be 1.
s = (1 * G1).x. Since 1*G1 is just G1, and G1 has an x-coordinate of 1, s will again be 1.
the RNG.self.seed will remain fixed at 1.
prediction
once RNG.self.seed is fixed at 1 (which happens when s=1) the calculation of r becomes
r = (s * G2).x = (1 * G2).x
scalar multiplication by 1 means 1 * G2 = G2 so r = G2.x G2 is the point Variety2(a2, b2)(Gx, Gy) where Gx and Gy are given by the server thus r will always be Gx (the x-coordinate of the server's G2 point) the final predicted future will be Gx >> 12. This value is constant and calculable by us as soon as the server gives us Gx
solver

what i learned :
The challenge highlighted how custom algebraic operations (in Variety1/Variety2) can introduce vulnerabilities
The RNG's reliance on the x-coordinate of scalar-multiplied points as the next seed was a critical weakness by forcing the seed to a constant value (1) through parameter manipulation (b1=0, G1=(1,1)) I made all future outputs predictable
Last updated