The service allowed only three actions: encrypt, decrypt, and submit. Encryption multiplied every plaintext coefficient by one hidden scalar before CKKS encoding. Decryption then exposed the first coefficients with large—but strictly bounded—additive noise.
That was enough. The task was not to remove CKKS noise directly; it was to make consecutive leaks constrain one another so tightly that only one modular lift remained possible.
Shape the plaintext
For a plaintext modulus q, we chose coefficients of the form:
coefficient[i] = q // 2 + 5**iSubtracting the shared baseline from each decrypted coefficient left observations equivalent to:
z[i] = 5^i * delta + error[i] (mod q)The baseline kept every submitted coefficient inside the service's accepted range. The powers of five created the relation needed for lifting.
Lift through the noise
Suppose L[i] is the integer lift chosen for z[i]. The next true value is approximately five times the previous one:
L[i + 1] ≈ 5 * L[i]Every residue has infinitely many representatives separated by q. For each new sample, we selected the representative congruent to z[i + 1] that was nearest to 5 * L[i].
The published noise bound made this choice unique: the accumulated difference between adjacent samples stayed far below half the modulus. Repeating the step across enough coefficients grew 5^i * delta beyond the noise floor.
Recover and verify
At the final sample, ordinary rounding recovered the hidden scalar:
delta = round(lifted[-1] / 5**last_index)We verified it against every leaked coefficient before submitting it. The service accepted the recovered value.
Takeaway
Approximate encryption does not make structured leakage harmless. A bounded error term becomes exploitable when an attacker controls related plaintexts and the service reveals enough correlated coefficients.
Event page: R3CTF 2026 on CTFtime.
