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=0 and choosing G1=(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:

  1. Set b1=0 in Variety1 to simplify addition/multiplication formulas.

  2. Choose G1=(1,1) so all scalar multiples of G1 have x=1 (seed locked to 1).

  3. 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 G1 won’t be the identity (y-coordinate isn’t 0).

Script Flow:

  1. Connect to server, read parameters (a1, Gx, etc.).

  2. Send {"b1": 0, "x": 1, "y": 1} to define G1.

  3. Compute predicted = Gx >> 12.

  4. 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:

  1. The RNG.self.seed will become this constant value after the first call to next().

  2. In all subsequent calls, t (the seed) will be this constant.

  3. s will remain this constant: (constant_seed * G1).x.

  4. Then r = (constant_s * G2).x will also be a constant value.

  5. 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():

  1. t = self.seed will be 1.

  2. s = (1 * G1).x. Since 1*G1 is just G1, and G1 has an x-coordinate of 1, s will again be 1.

  3. 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 :

  1. The challenge highlighted how custom algebraic operations (in Variety1/Variety2) can introduce vulnerabilities

  2. 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