Overview
A SageMath generator and its output:
1rbit = 444
2pbit = 512
3p = bytes_to_long(open('flag', 'rb').read())
4xs = [p * getPrime(pbit) + random.randint(1, 2**rbit) for _ in range(10)]
So each sample is
x_i = p * q_i + r_i
with a shared secret p (the flag as a big-endian integer, about 503 bits), a
random 512-bit prime q_i, and small noise r_i < 2**444. Ten samples, one
secret. This is the problem.
Solution: Howgrave-Graham AGCD lattice
Build the (t+1) x (t+1) lattice
[ K x_1 x_2 ... x_t ]
[ 0 -x_0 0 ... 0 ]
B = [ 0 0 -x_0 ... 0 ]
[ ... ]
[ 0 0 0 ... -x_0 ]
with K = 2**440, slightly above the noise bound. The vector (q_0, q_1, ..., q_t)
maps under B to
v = (q_0 * K, q_0*r_1 - q_1*r_0, ..., q_0*r_t - q_t*r_0)
because the p * q terms cancel. Each later entry has norm around
2**512 * 2**444 = 2**956, far below the lattice determinant, so finds v.
Pitfall
The scale K is the whole game. Too large and LLL misses the target vector;
too small and the target is not short enough to be first. K = 2**440 fits this
parameter set (rbit = 444, pbit = 512); other noise sizes need a different
exponent.
Recover p from the first reduced vector:
1# SageMath
2x0 = xs[0]; t = 9; K = 2^440
3M = matrix(ZZ, t + 1, t + 1)
4M[0, 0] = K
5for j in range(1, t + 1):
6 M[0, j] = xs[j]
7 M[j, j] = -x0
8
9v = M.LLL()[0]
10q0 = abs(v[0]) // K
11p = x0 // q0
12print(int(p).to_bytes((p.bit_length() + 7) // 8, 'big').decode())
$sage solve.sage TFCCTF{nice_crypto_skillz_kid_you_will_be_great_one_day_af56c3}
Verification: for every i, x_i mod p lies in [1, 2**444) and
(x_i - r_i) / p is a 512-bit number.
Flag
TFCCTF{nice_crypto_skillz_kid_you_will_be_great_one_day_af56c3}