ADP
630 words
3 minutes
picoCTF - Cryptography

Flag - printer#

import galois
import numpy as np
MOD = 7514777789

points = []

for line in open('encoded.txt', 'r').read().strip().split('\n'):
    x, y = line.split(' ')
    points.append((int(x), int(y)))

GF = galois.GF(MOD)

matrix = []
solution = []
for point in points:
    x, y = point
    solution.append(GF(y % MOD))

    row = []
    for i in range(len(points)):
        row.append(GF((x ** i) % MOD))
    
    matrix.append(GF(row))

open('output.bmp', 'wb').write(bytearray(np.linalg.solve(GF(matrix), GF(solution)).tolist()[:-1]))

Solution#

Ta thấy rằng file encoded.txt bao gồm tập hợp các điểm với xx nằm từ 0 —> 1769610 và giá trị y random. Và code mẫu đề bài cho là thực hiện giải ma trận dưới đây, tuy nhiên nó chạy rất chậm và độ phức tạp là O(n3)O(n^3)

[a0a1a2a3an][000102030n101112131n202122232n303132333nn0n1n2n3nn]=[y0y1y2y3yn]\begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \\ \vdots \\ a_n \\ \end{bmatrix} \begin{bmatrix} 0^0 & 0^1 & 0^2 & 0^3 & \dots & 0^n \\ 1^0 & 1^1 & 1^2 & 1^3 & \dots & 1^n \\ 2^0 & 2^1 & 2^2 & 2^3 & \dots & 2^n \\ 3^0 & 3^1 & 3^2 & 3^3 & \dots & 3^n \\ \vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\ n^0 & n^1 & n^2 & n^3 & \dots & n^n \\ \end{bmatrix} = \begin{bmatrix} y_0 \\ y_1 \\ y_2 \\ y_3 \\ \vdots \\ y_n \\ \end{bmatrix}

Sau một lúc osint thì ta nhận thấy rằng đây là ma trận Vandermonde và nó liên quan đến Larange interpolation (nội suy larange). Và ta có được công thức:

f(x)=i=1n+1f(xi)ji j=1n+1xxjxixjf(x)=\sum_{i=1}^{n+1} f(x_i) * \prod_{j\neq i \ j = 1}^{n+1} \frac{x-x_j}{x_i-x_j}

Cùng với đó ta cũng tìm thấy được một blog: https://codeforces.com/blog/entry/94143

Đầu tiên ta sẽ giảm độ phức tạp của việc tính ji(xixj)\prod_{j\neq i}(x_i-x_j)

Thay vì với mỗi vòng lặp ta sẽ chạy lại để tính thì ta có thể làm như sau:

ji(xixj)\prod_{j\neq i}(x_i-x_j) = limxxij=1n+1(xxj)xxi\lim_{x\to x_i}\frac{\prod_{j=1}^{n+1} (x-x_j)}{x-x_i}

Ở đây to có thể nhân hết vào và chia đi xixix_i - x_i. Điều tuyệt vời là ta có thể sử dụng lim để xử lý việc xixi=0x_i - x_i = 0

limxxij=1n+1(xxj)xxi=limxxiddx(i=1n+1xxj)\lim_{x\to x_i}\frac{\prod_{j=1}^{n+1} (x-x_j)}{x-x_i}=\lim_{x\to x_i}\frac{d}{dx}(\prod_{i=1}^{n+1} x-x_j)

Thế nên việc của ta là chỉ cần tìm P(x)P'(x) với P(x)=i=1n+1xxjP(x) = \prod_{i=1}^{n+1} x-x_j rồi thế từng xix_i vô.

Tiếp theo ta sẽ đến giảm độ phức tạp của việc nhân nhiều đa thức

Ta biết rằng nhân đa thức trong sage có O(n log(n))O(n \ log(n))

Giả sử có m đa thức thì nếu nhân từ từ từng đa thức lại với nhau ta sẽ mất O(mnlog(n))O(m * n log(n))

image

Còn nếu ta sử dụng chia để trị để nhân đa thức thì ta sẽ mất (nlog(n)log(m))(nlog(n) * log(m))

image

Giờ việc cuối cùng của ta là là thiết lập công thức

Đặt vi=yiP(xi)v_i=\frac{y_i}{P'(x_i)}

—> f(x)=i=1n+1viji(xxj)f(x)=\sum_{i=1}^{n+1} v_i\prod_{j\neq i}(x-x_j)

f(x)=f0(x)P1(x)+f1(x)P0(x)f(x)=f_0(x)P_1(x)+f_1(x)P_0(x) với:

  • f0(x)=i=1n+12viji,1jn+12(xxj)f_0(x)=\sum_{i=1}^{\lfloor\frac{n+1}{2}\rfloor} v_i\prod_{j\neq i,1\leq j\leq \lfloor\frac{n+1}{2}\rfloor}(x-x_j)
  • f1(x)=i=n+12+1n+1viji,n+12jn+1(xxj)f_1(x)=\sum_{i=\lfloor\frac{n+1}{2}+1\rfloor}^{n+1} v_i\prod_{j\neq i,\lfloor\frac{n+1}{2}\rfloor\leq j\leq n+1}(x-x_j)
  • P0(x)=1jn+12(xxj)P_0(x)=\prod_{1\leq j\leq \lfloor\frac{n+1}{2}\rfloor}(x-x_j)
  • P1(x)=n+12jn+1(xxj)P_1(x)=\prod_{\lfloor\frac{n+1}{2}\rfloor\leq j\leq n+1}(x-x_j)

Code dưới đây đã mất tầm 6 - 7 tiếng để chạy và mình đã để chạy qua đêm để ra FLAG. (huhu)

from tqdm import tqdm

MOD = 7514777789

points = []

for line in open('encoded.txt', 'r').read().strip().split('\n'):
    x, y = line.split(' ')
    points.append((int(x), int(y)))

F = GF(MOD)
R.<x> = F['x']

def multiply_poly_list(poly_list):
    if len(poly_list) == 1:
        return poly_list[0]
    n = len(poly_list) // 2
    return multiply_poly_list(poly_list[:n]) * multiply_poly_list(poly_list[n:])

P = multiply_poly_list([x - i for i in range(len(points))])
d_P = derivative(P)

def calc_poly(points, start_index, bar):
    bar.update(int(1))
    n = len(points)
    if n == 1:
        x_i = points[0][0]
        y_i = points[0][1]
        v = y_i / d_P(x_i)
        return v


    f0 = calc_poly(points[:n//2], start_index, bar)
    f1 = calc_poly(points[n//2:], start_index + n//2, bar)

    p0 = multiply_poly_list([x - (i + start_index) for i in range(n//2)])
    p1 = multiply_poly_list([x - (i + start_index) for i in range(n//2, n)])
    return f0 * p1 + f1 * p0

n = 2 * len(points) - 1
bar = tqdm(total=int(n))
poly = calc_poly(points, 0, bar)
coeffs = poly.numerator().list()

with open('out.txt', 'w') as f:
    f.write('\n'.join([str(i) for i in coeffs]))

print("Finished")
listarr = [int(_) for _ in open("out.txt", "r").read().strip().split('\n')]

print(listarr)

open('output.bmp', 'wb').write(bytearray(listarr[:-1]))

image

picoCTF - Cryptography
https://dphuoc.github.io/posts/picoctf/
Author
ADP
Published at
2024-03-20