# Google CTF 2025 writeup
這次和星爆牛炒竹狐一起打,只寫出一題密碼學
## FILTERMAZE
這提示一個 LWE 問題,具體來說描述是這樣:
$A$ 是一個 $n\times m$ 的矩陣,$b$ 和 $e$ 是長度為 $n$ 的向量,$s$ 是長度為 $m$ 的向量,滿足 $b=(As+e)\mod{q}$
首先,他有給的資訊是 $A$、$b$、$q$,想要獲得的是 $s$,並且我們可以用一些方式得到一些有關 $e$ 的資訊。
```python
class PathChecker:
def __init__(
self,
secret_path,
graph_data,
lwe_error_mags,
):
self.secret_path = secret_path
self.graph = graph_data
self.lwe_error_mags = lwe_error_mags
self.path_len = len(self.secret_path)
def check(self, candidate_segment):
seg_len = len(candidate_segment)
if seg_len > self.path_len:
return False
for i, node in enumerate(candidate_segment):
if node != self.secret_path[i]: # Node mismatch
return False
if i > 0:
prev_node = candidate_segment[i - 1]
neighbors = self.graph.get(prev_node)
if neighbors is None or node not in neighbors:
return False
if seg_len == self.path_len:
error_magnitudes = [int(abs(err_val)) for err_val in self.lwe_error_mags]
return error_magnitudes
else:
return True
```
這個類別的 `check` 函式會檢查我們輸入的路徑前綴是否符合答案,並且會告訴我們答案,當我們完全的把路徑猜出來後就會給我們 $e$ 的絕對值(向量中所有數字都取絕對值)。而因為他是檢查前綴,所以可以直接枚舉,於是我們獲得 $e$ 的絕對值了。
但是有 $e$ 的絕對值有什麼用呢?觀察一下以下這件事:
$$
\left[
\begin{array}{ccc}
e_1 \\
e_2 \\
... \\
e_n
\end{array}
\right] \\
= \left[
\begin{array}{ccc}
|e_1| & 0 & ... & 0\\
0 & |e_2| & ... & 0\\
... & ...& ... & ...\\
0 & 0 & ... & |e_n|\\
\end{array}
\right]
\left[
\begin{array}{ccc}
(-1)^{x_1} \\
(-1)^{x_2} \\
... \\
(-1)^{x_n}
\end{array}
\right]
$$
照上面這個關係,我方便起見寫成下面這種形式:$e=E_{abs}X$
於是我們可以把 LWE 問題通過同乘 $E_{abs}^{-1}$ 變成以下形式:
$$E_{abs}^{-1}b=(E_{abs}^{-1}As+X)\mod{q}$$
注意到 $X$ 的係數都是 $1$ 或 $-1$,這就是一個經典的 LWE 問題了,直接當腳本小子去解它,然後就解出 $s$ 了:
```python
# https://github.com/maple3142/lll_cvp
import json
from sage.all import *
from lll_cvp import *
e_abs = [265, 622, 38, 716, 722, 308, 996, 799, 742, 337, 927, 698, 626, 969, 330, 126, 321, 20, 271, 839, 175, 399, 752, 989, 666, 629, 271, 400, 311, 840, 821, 821, 17, 978, 488, 781, 74, 818, 849, 903, 776, 142, 505, 951, 582, 638, 222, 872, 427, 165, 307, 209, 475, 970, 748, 814, 69, 213, 27, 742, 744, 566, 262, 852, 740, 309, 997, 502, 995, 434, 405, 193, 257, 953, 924, 678, 232, 226, 560, 414, 584, 579, 767, 810, 51, 894, 446, 281, 761, 908, 715, 787, 722, 270, 94, 169, 474, 431, 292, 346]
with open("lwe_pub_params.json", "r") as f:
data = json.loads(f.read())
# print(data)
n = 50
m = 100
q = 1009
A = data["A"]
b = data["b"]
b = [b[i] * pow(e_abs[i], -1, q) % q for i in range(m)]
A = [[A[i][j] * pow(e_abs[i], -1, q) % q for j in range(n)] for i in range(m)]
A = matrix(GF(q), A)
b = vector(GF(q), b)
ATR = reduce_mod_p(A.T, q, reduction=lambda x: x)
s_rec = A.solve_right(kannan_cvp(ATR, b))
print(s_rec)
```