$ cat writeup.md…
$ cat writeup.md…
tjctf
Task: broken decode script with Chebyshev polynomial recurrence mod secp256k1 prime, naive loop of 10^25 iterations. Solution: fix Python bug, replace naive loop with matrix exponentiation O(log n), cycle 32-byte keystream to cover 35-byte flag.
why does my decode script not work :(
Given two files: flag.enc (35 bytes of encrypted flag data) and decode.py (a broken Python decode script). The goal is to fix the script and decrypt the flag.
The provided script implements a Chebyshev polynomial of the first kind recurrence modulo the secp256k1 prime:
from pathlib import Path from Crypto.Util.number import long_to_bytes M = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f def encrypt(val, key): amp = val p = M prev = 1 current = amp for _ in range(key - 1): new_val = (2 * amp * current - prev) % p prev = current current = new_val return current flag_bytes = ("flag.enc").read_bytes() base_val = 0x1337C0DE frequency_key = 10**25 secret = encrypt(base_val, frequency_key) encrypted = bytes([a ^ b for a, b in zip(flag_bytes, long_to_bytes(secret))]) print(encrypted)
The recurrence relation is:
This is the Chebyshev polynomial of the first kind evaluated at val, computed modulo the secp256k1 prime M.
Python bug: ("flag.enc").read_bytes() calls .read_bytes() on a bare string, which has no such method. It should be Path("flag.enc").read_bytes().
Performance bug: The naive loop iterates frequency_key = 10^25 times. At even 10^9 iterations per second, this would take ~10^16 seconds (300 million years). This is computationally infeasible.
The secret is computed modulo the 32-byte secp256k1 prime, producing at most 32 bytes. But the flag is 35 bytes long. The original encryption must have cycled the keystream — the zip in the decode script would silently truncate to 32 bytes, losing the last 3 bytes of the flag.
The Chebyshev recurrence is a second-order linear recurrence that can be expressed in matrix form:
[T(n+1)] [2·val -1] ^(n-1) [val]
[T(n) ] = [1 0] × [1 ]
Using fast matrix exponentiation (repeated squaring), we compute T(10^25) in O(log₂(10^25)) ≈ 83 matrix multiplications instead of 10^25 loop iterations.
To handle the 35-byte flag with a 32-byte secret, we pad the secret to 35 bytes (cycling the keystream) before XORing.
from Crypto.Util.number import long_to_bytes M = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f def encrypt_fast(val, key): """Compute Chebyshev-like recurrence using matrix exponentiation. The recurrence is: T(0) = 1 T(1) = val T(n) = 2*val*T(n-1) - T(n-2) mod M Matrix form: [T(n), T(n-1)] = [[2*val, -1], [1, 0]]^(n-1) * [val, 1] """ amp = val p = M if key == 0: return 1 if key == 1: return amp % p def mat_mul(A, B, mod): return [ [(A[0][0]*B[0][0] + A[0][1]*B[1][0]) % mod, (A[0][0]*B[0][1] + A[0][1]*B[1][1]) % mod], [(A[1][0]*B[0][0] + A[1][1]*B[1][0]) % mod, (A[1][0]*B[0][1] + A[1][1]*B[1][1]) % mod], ] def mat_pow(M_mat, n, mod): result = [[1, 0], [0, 1]] base = M_mat while n > 0: if n % 2 == 1: result = mat_mul(result, base, mod) base = mat_mul(base, base, mod) n //= 2 return result transition = [[(2 * amp) % p, (-1) % p], [1, 0]] result_mat = mat_pow(transition, key - 1, p) current = (result_mat[0][0] * amp + result_mat[0][1] * 1) % p return current # Read encrypted flag with open("flag.enc", "rb") as f: flag_bytes = f.read() base_val = 0x1337C0DE frequency_key = 10**25 secret = encrypt_fast(base_val, frequency_key) secret_bytes = long_to_bytes(secret, 35) # Pad to flag length # XOR to decrypt decrypted = bytes([a ^ b for a, b in zip(flag_bytes, secret_bytes)]) print(decrypted) # b'tjctf{REDACTED}'
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar