After the competition ended, I found out through the Discord channel that the code was an implementation of Eaglesign(not exactly same), and that there is a paper on attacks related to it.
Most of the solvers, aside from myself, seem to have either referenced the paper or figured out a similar method on their own to solve it.
Even though I only added a very short explanation along with my solver code on Discord channel, I sincerely thank @vishiswoz and @Neobeo for sharing their interpretations of my solution.
[@vishiswoz's interpretation](https://hackmd.io/@vishiswoz/BkgeMCH3A)<br>
[@Neobeo's interpretation](https://gist.github.com/AdibSurani/4e5596991a316ef6319ddb2c8a764e9e)
# Challenge
- N = 1024, q = 12289
- Key generation
- Private keys ($G, D$) are randomly generated polynomial on Quotient Ring $Z_q[x]/x^N + 1$
- coefficients must be {-1, 0, 1}.
- $G$ must be invertible.
- Public key $A$ is randomly generated polynomial on same Quotient Ring
- Public key $E = (A + D)G^{-1}$
- Sign
- Generate $Y1, Y2$ randomly
- $Y1$ has 140 nonzero coefficients, which are -1 or 1
- $Y2$ has bounded coefficients which absolute max are 64
- $P = AY1 + Y2$, $r = H(P)$
- Generate $C$ with given message and $r$
- $C$ has 38 nonzero coefficients, which are -1 or 1
- $U = Y1 + C$
- $V = GU$, $W = Y2 - DU$
- Signature is $(r, V, W)$
- Verify
- Generate $C$ with given message and $r$ (same as Sign)
- $Z = EV - AC + W$
- $EV - AC + W = EGU - AC + W = (A + D)U - AC + Y2 - DU = A(U - C) + Y2 = P$
- Check $r$ equals $H(Z)$
- Keys are generated and public Keys are given when code starts.
- We can sign for any message with no limit, and we need $D$ for get the flag.
# My approach
- $G^{-1} = (Y1 + C)V^{-1}$ ($\because V = GU = G(Y1 + C)$)
- So, $(Y1_i + C_i)V_i^{-1} = (Y1_0 + C_0)V_0^{-1}$ for every signature.
- We can express every $Y1_i$ as form $A_i(Y1_0) + B_i$
- $A_i = V_iV_0^{-1}$
- $B_i = A_iC_0 - C_i$
## Building equation
- Let $A_i = \sum\limits_{k=0}^{N - 1}a_{i, k}x^k$, $B_i = \sum\limits_{k=0}^{N - 1}b_{i, k}x^k$, $Y1_i = \sum\limits_{k=0}^{N - 1}c_{i, k}x^k$
- $x^jA_i = \sum\limits_{k=0}^{j - 1}-a_{i, N - j + k}x^{k} + \sum\limits_{k=j}^{N - 1}a_{i, k - j}x^{k}$
- $Y1_i = A_iY1_0 + B_i = \sum\limits_{j=0}^{N - 1}c_{0, j}(x^jA_i) + B_i$
- Therefore, we can express it as Matrix equation below:
$$ \begin{pmatrix}
a_{i,0} & -a_{i, N - 1} & -a_{i, N - 2} & \cdots & -a_{i,1} \\
a_{i,1} & a_{i,0} & -a_{i, N - 1} & \cdots & -a_{i,2} \\
a_{i,2} & a_{i,1} & a_{i,0} & \cdots & -a_{i,3} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
a_{i,N - 1} & a_{i,N - 2} & a_{i, N - 3} & \cdots & a_{i,0} \\
\end{pmatrix}\begin{pmatrix}
c_{0, 0} \\
c_{0, 1} \\
c_{0, 2} \\
\vdots \\
c_{0, N - 1} \end{pmatrix} + \begin{pmatrix}
b_{0, 0} \\
b_{0, 1} \\
b_{0, 2} \\
\vdots \\
b_{0, N - 1} \end{pmatrix} = \begin{pmatrix}
c_{i, 0} \\
c_{i, 1} \\
c_{i, 2} \\
\vdots \\
c_{i, N - 1} \end{pmatrix}$$
- Let matrix $M_i = \begin{pmatrix}
a_{i,0} & -a_{i, N - 1} & -a_{i, N - 2} & \cdots & -a_{i,1} \\
a_{i,1} & a_{i,0} & -a_{i, N - 1} & \cdots & -a_{i,2} \\
a_{i,2} & a_{i,1} & a_{i,0} & \cdots & -a_{i,3} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
a_{i,N - 1} & a_{i,N - 2} & a_{i, N - 3} & \cdots & a_{i,0} \\
\end{pmatrix} = \begin{pmatrix}
\vert & \vert & \vert & & \vert \\
\vec{v_{i, 0}} & \vec{v_{i, 1}} & \vec{v_{i, 2}} & \cdots & \vec{v_{i, N - 1}} \\
\vert & \vert & \vert & & \vert \\
\end{pmatrix}$
- Let vectors $\vec{y_i} = \begin{pmatrix}
c_{i, 0} \\
c_{i, 1} \\
c_{i, 2} \\
\vdots \\
c_{i, N - 1} \end{pmatrix}$, $\vec{b_i} = \begin{pmatrix}
b_{0, 0} \\
b_{0, 1} \\
b_{0, 2} \\
\vdots \\
b_{0, N - 1} \end{pmatrix}$
- So we can express equation above as below:
$$ \sum\limits_{j=0}^{N - 1}c_{0, j} \vec{v_{i, j}} + \vec{b_i} = \vec{y_i}$$
- Since Y1 has 140 nonzero coefficients which are -1 or 1, sum of square of coefficients equals 140, so $\vec{y_i} \cdot \vec{y_i} = 140$. It leads to equation below:
$$ \vec{y_i} \cdot \vec{y_i} = (\sum\limits_{j=0}^{N - 1}c_{0, j} \vec{v_{i, j}} + \vec{b_i})(\sum\limits_{j=0}^{N - 1}c_{0, j} \vec{v_{i, j}} + \vec{b_i}) $$
$$= \sum\limits_{0 \le j, k < N }(\vec{v_{i,j}} \cdot \vec{v_{i,k}})c_{0,j}c_{0,k} + 2\sum\limits_{j=0}^{N - 1}(\vec{v_{i,j}} \cdot \vec{b_i})c_{0,j} + (\vec{b_i} \cdot \vec{b_i}) = 140 $$
## Linearization & reduce unknown variables
- By linearization, we can treat equation above as linear equation of $(\binom{N}{2} + N) + N = N(N + 3) / 2$ unknown variables.
- But we can reduce $\binom{N}{2} + N$ unknown variables into $N$, because they shares same coefficients!
- $\vec{v_{i,k}}$ expresses coefficients of $x^jA_i$ as we announced ealier.
- Let $R = \begin{pmatrix}
0 & 0 & \cdots & 0& -1 \\
1 & 0 & \cdots & 0& 0 \\
0 & 1 & \cdots & 0& 0 \\
\vdots & \vdots & \ddots & \vdots& \vdots \\
0 & 0 & \cdots & 1& 0 \\
\end{pmatrix}$ then $R^TR = I$, $\vec{v_{i,j}} = R^j\vec{v_{i,0}}$
- be used to express the multiplication of $x$.
- $\vec{v_{i,j}} \cdot \vec{v_{i,k}} = (R^j\vec{v_{i,0}})^T(R^j\vec{v_{i,k - j}}) = \vec{v_{i,0}}^T (R^j)^T(R^j)\vec{v_{i,k - j}} = \vec{v_{i,0}} \cdot \vec{v_{i,k - j}}$
- So, a lot of terms sharing same coefficients $(\vec{v_{i,0}}\cdot \vec{v_{i,j}})$.
- Therefore, we can express equation above as below:
$$ \vec{y_i} \cdot \vec{y_i} = \sum\limits_{j=0}^{N - 1}(\vec{v_{i,0}} \cdot \vec{v_{i,j}})c_{0,j}' + 2\sum\limits_{j=0}^{N - 1}(\vec{v_{i,j}} \cdot \vec{b_i})c_{0,j} + (\vec{b_i} \cdot \vec{b_i}) = 140 $$
- We can treat equation above as linear equation of $N + N = 2N$ unknown variables
## Efficient calculation
- If $f = \sum\limits_{i=0}^{N - 1}a_ix^i$, let $f^* = \sum\limits_{i=0}^{N - 1}a_{N - 1- i}x^i$
- Let $f = \sum\limits_{i=0}^{N - 1}a_ix^i$, $g = \sum\limits_{i=0}^{N - 1}b_ix^i$. Then $\sum\limits_{i=0}^{N - 1}a_ib_i$ equals coefficient of term $x^{N-1}$ in $fg^*$
- $\vec{v_{i, j}} \cdot \vec{v_{i,0}}$ equals coefficient of term $x^{N -1}$ in $x^jA_iA_i^*$, which is also coefficient of term $x^{N - 1 - j}$ in $A_iA_i^*$
- As same reason, $\vec{v_{i, j}} \cdot \vec{b_i}$ equals coefficient of term $x^{N - 1 - j}$ in $A_iB_i^*$
- Therefore, we can $2N$ coefficients for linear equation by calculating $A_iA_i^*$ and $A_iB_i^*$
- It reduces time for running solution $O(N^3)$ to $O(N^2\log N)$
## Implementation
```py
import os;os.environ["TERM"] = 'linux'
from pwn import *
from ntt import NTTDomain, NTTPoints
import hashlib
from tqdm import trange
#r = process(["python", "challenge.py"])
r = remote("54.78.163.105", "30859")
N, Q, W, P = 1024, 12289, 4324, 9389
PR.<x_> = PolynomialRing(GF(Q))
QR.<x> = PR.quotient(x_^N + 1)
def l2p(A):
t = []
while A:
t.append(A % Q)
A //= Q
return QR(t)
def HashBall(m: bytes, tau: int) -> list:
""" Deterministically generates sparse polynomials with weight tau. """
if isinstance(m, str):
m = m.encode()
h = hashlib.sha256(m).digest()
c = N * [0]
for i in range(N - tau, N):
hi = int(hashlib.sha256(h + i.to_bytes(2, 'big')).hexdigest(), 16)
hi //= i; j = hi % i; hi //= i
hi //= 2; k = hi % 2; hi //= 2
c[i] = c[j]
c[j] = (1 - 2 * k) % Q
return c
def ntt(c):
c = QR(c).lift()
return QR([c(P * pow(W, i, Q)) for i in range(N)])
def intt(c):
c = QR(c).lift()
c = [c(pow(W, -i, Q)) * pow(N, -1, Q) for i in range(N)]
c = [c[i] * pow(P, -i, Q) for i in range(N)]
return QR(c)
r.recvuntil(b'= ')
data = eval(r.recvline().decode())
A = intt(l2p(int(data["A"], 16)))
E = intt(l2p(int(data["E"], 16)))
def get_sig(msg):
r.recvuntil(b'| Sig = ')
sig = eval(r.recvline()[:-1].decode())
sig_r = bytes.fromhex(sig["r"])
sig_V = intt(l2p(int(sig["V"], 16)))
sig_W = intt(l2p(int(sig["W"], 16)))
sig_C = QR(HashBall(msg + sig_r, 38))
sig_P = E * sig_V - A * sig_C + sig_W
sig_Pint = sum([ZZ(j) * Q ** i for i,j in enumerate(ntt(sig_P).list())])
sig_Pbyt = sig_Pint.to_bytes(-(-len(bin(sig_Pint)[2:]) // 8), 'big')
assert sig_r == hashlib.sha256(sig_Pbyt.hex().encode()).digest()
return sig_r, sig_V, sig_W, sig_C, sig_P
def send_n(msg, n):
payload = b'S\n' + msg + b'\n'
r.send(payload * n)
rs, Vs, Ws, Cs, Ps = [], [], [], [], []
m = 2 * N + 100
send_n(b'asdf', m)
for i in trange(m):
r0, V0, W0, C0, P0 = get_sig(b'asdf')
rs.append(r0)
Vs.append(V0)
Ws.append(W0)
Cs.append(C0)
Ps.append(P0)
As, Bs = [QR(1)], [QR(0)]
for i in trange(1, m):
As.append(Vs[i] / Vs[0])
Bs.append(Vs[i] / Vs[0] * Cs[0] - Cs[i])
M = []
for i in trange(m):
Av = list(As[i]) + list(-As[i])
Bv = list(Bs[i])
coeff = list(As[i] * QR(list(As[i])[::-1]))[::-1]
coeff += list(As[i] * QR(list(Bs[i])[::-1]))[::-1]
#coeff.append(vector(Bs[i]) * vector(Bs[i]))
M.append(coeff)
M = Matrix(GF(Q), M)
target_v = vector(GF(Q), [140 - vector(Bs[i]) * vector(Bs[i]) for i in trange(m)])
x = M.solve_right(target_v)
Y1_0 = QR(list(x[N:])) / 2
U_0 = Y1_0 + Cs[0]
G = Vs[0] / U_0
D = E * G - A
sig_Dint = sum([ZZ(j) * Q ** i for i,j in enumerate(ntt(D).list())])
sig_Dbyt = sig_Dint.to_bytes(-(-len(bin(sig_Dint)[2:]) // 8), 'big')
r.sendlineafter(b'> ', b'c')
r.sendlineafter(b') ', sig_Dbyt.hex().encode())
r.interactive()
```