Đại số tuyến tính
Giới thiệu lại một số khái niệm cơ bản
Không gian Vector
Khái niệm không gian vector là một trong những trụ cột quan trọng của bộ môn đại số tuyến tính. Việc hiểu rõ bản chất của không gian vector giúp ta xây dựng một nền tảng vững chắc phục vụ cho việc tiếp cận các khái niệm cao cấp hơn sau này. Trong bài viết này tác giả sẽ trình bày vắn tắt lại một số kết quả quan trọng liên quan đến không gian vector.
Khái niệm
Ta sẽ quy ước là một trường.
Định nghĩa 1. Không gian vector
Tập hợp được gọi là một không gian vector trên nếu nó được trang bị hai phép toán, phép toán đầu tiên là phép cộng hai vector, kí hiệu là , phép toán thứ hai là phép nhân một vector với vô hướng , kí hiệu là . Cụ thể, như sau
- Phép cộng hai vector : là một ánh xạ , ví dụ
- Phép nhân hai vector: là một ánh xạ , ví dụ : với
Ngoài ra cũng thỏa mãn tính chất là một nhóm Abel đối với phép cộng và các mệnh đề về phân phối, giao hoán tương tự như khi làm việc trên tập số thực. Ở đây ta gọi các phần tử của là các vector còn các phần tử của là các vô hướng.
Độc lập tuyến tính và phụ thuộc tuyến tính
Định nghĩa 2. Tổ hợp tuyến tính, biểu thị tuyến tính
- Một tổ hợp tuyến tính của các vector là một biểu thức có dạng
trong đó là các vô hướng.
- Giả sử thì ta nói rằng vector biểu thị tuyến tính được qua hệ các vector .
Ngoài ra một vector có thể có nhiều biểu thị tuyến tính khác nhau qua các hệ vector phân biệt. Biểu thị tuyến tính cũng có tính chất bắc cầu, tức là với hệ vector , ta nói hệ biểu diễn được qua hệ vector nếu như mỗi vector trong hệ đều biểu thị tuyến tính được qua hệ , và tương tự nếu hệ biểu thị tuyến tính được qua hệ thì khi đó hệ biểu thị tuyến tính được qua hệ .
Định nghĩa 3. Độc lập tuyến tính và phụ thuộc tuyến tính.
Hệ được gọi là độc lập tuyến tính nếu hệ thức
xảy ra khi và chỉ khi
Ngược lại hệ được gọi là phụ thuộc tuyến tính nếu hệ thức trên xảy ra và có ít nhất một giá trị
Cơ sở và số chiều của không gian vector
Định nghĩa 4. Hệ sinh và cơ sở
a/ Một hệ vector của được gọi là một hệ sinh của nếu mọi vector của đều biểu thị tuyến tính được qua hệ đó.
b/ Một hệ vector của được gọi là một cơ sở của nếu mọi vector của đều được biểu thị tuyến tính một cách duy nhất qua hệ đó.
Từ đây ta có các khẳng định sau về hệ sinh và cơ sở là tương đương nhau
i/ là một cơ sở của
ii/ là một hệ sinh độc lập tuyến tính của
iii/ là một hệ vector độc lập tuyến tính cực đại của .
Chứng minh đơn giản như sau:
Từ i/ suy ra ii/ : là một cơ sở của . Với ràng buộc tuyến tính thì vì là một cơ sở của nên biểu diễn trên là duy nhất kéo theo là một hệ sinh độc lập tuyến tính của
Từ ii/ suy ra iii/ : là một hệ sinh độc lập tuyến tính đồng nghĩa với việc mọi vector trong đều được biểu thị tuyến tính qua hệ và nó cũng là một hệ vector độc lập tuyến tính cực đại vì với bất kì thì biểu thị tuyến tính được qua cho nên không còn độc lập tuyến tính
Từ iii/ qua i/ : Một hệ vector độc lập tuyến tính cực đại trong thì với mọi vector khác thuộc đều phải được biểu thị tuyến tính qua hệ này, có nghĩa là hệ này sinh ra và biểu diễn này là duy nhất. Vì giả sử rằng có hai biểu diễn là khác nhau tức là có ít nhất hai hệ số khác nhau giữa hai biểu diễn. Nếu lấy hiệu thì hệ không còn độc lập tuyến tính sinh ra mâu thuẫn.
Định nghĩa 5. Hữu hạn sinh
- Một không gian vector được gọi là hữu hạn sinh nếu như nó có một hệ sinh gồm hữu hạn phần tử.
Định nghĩa 6. Chiều của không gian
- Số phần tử của mỗi cơ sở của -không gian vector hữu hạn sinh được gọi là số chiều của trên trường . Kí hiệu là . Quy ước khi .
Định lí. Giả sử là một không gian vector hữu hạn sinh. Khi đó, có một cơ sở gồm hữu hạn phần tử. Hơn nữa, mọi cơ sở của đều có số phần tử bằng nhau.
Chứng minh.
Trước hết ta có một bổ đề sau: Trong không gian vector , giả sử hệ vector độc lập tuyến tính và biểu thị tuyến tính được qua hệ thì ta có .
Ta chứng minh bổ đề trên như sau: Theo giả thiết ta có một biểu thị tuyến tính như sau
Vì hệ độc lập tuyến tính cho nên ta có thể giả sử và giả sử không mất tính tổng quát rằng . Khi đó có một biểu thị tuyến tính như sau:
Như vậy biểu thị tuyến tính được qua hệ ,và khi đó biểu thị tuyến tính qua còn lại biểu thị tuyến tính được qua cho nên biểu thị tuyến tính được qua . Lí do có biểu diễn trên là vì: ta đã có biểu thị tuyến tính được qua . Tương tự với cũng biểu thị tuyến tính được qua bằng cách lấy mọi vô hướng của các số đều bằng không thì ta có được một biểu thị tuyến tính.
Bây giờ tiếp tục ta sẽ chứng minh được rằng biểu thị tuyến tính được qua trong đó . Ta sẽ quy nạp. Đầu tiên với thì mệnh đề đúng theo điều vừa chỉ ra ở trên. Giả sử nó đúng tới và ta sẽ chứng minh cũng đúng trong trường hợp .
Ta có biểu thị tuyến tính được qua . Như vậy biểu thị tuyến tính được qua cho nên ta có
Ta có hệ là một hệ độc lập tuyến tính cho nên khi đó có ít nhất một giá trị vì nếu không thì biểu thị tuyến tính qua là mẫu thuẫn vì là một hệ độc lập tuyến tính. Tương tự như trên ta cũng suy ra biểu thị tuyến tính được qua hệ . Cho nên biểu thị tuyến tính qua hệ . Suy ra ta có điều phải chứng minh. Bây giờ nếu thì ta suy ra . Khi đó biểu thị tuyến tính qua hệ là mâu thuẫn với giả sử hệ độc lập tuyến tính. Vậy .
Bây giờ ta sẽ quay trở lại chứng minh định lí ban đầu.
Giả sử là một hệ sinh hữu hạn của . Ta gọi là một hệ độc lập tuyến tính trong . Hệ này biểu thị tuyến tính được qua theo định nghĩa của hệ sinh. Suy ra theo bổ đề thì . Ta tiếp tục bổ sung vào trong hệ các vector để sao cho hệ vẫn là hệ độc lập tuyến tính và rõ ràng, quá trình này phải dừng lại sau hữu hạn bước và hệ thu được là hệ vector độc lập tuyến tính cực đại trong . Vì là hệ vector độc lập tuyến tính cực đại nên mỗi vector trong được biểu thị duy nhất qua hệ này. Suy ra ta có điều phải chứng minh.
Cuối cùng là một định lí, có thể coi như là hệ quả của kết quả trên.
Định lí. Giả sử là một không gian vector hữu hạn sinh. Khi đó mọi hệ sinh của đều chứa một cơ sở. Mọi hệ độc lập tuyến tính trong đều có thể được bổ sung để trở thành một cơ sở của . Nếu , thì mọi hệ độc lập tuyến tính gồm vector của đều là một cơ sở.
Ngoài ra người ta cũng nhận xét được rằng, trong một không gian vector vô hạn sinh (tức là không hữu hạn sinh), hai cơ sở bất kì đều có cùng lực lượng. Nhưng một hệ vector độc lập tuyến tính có cùng lực lượng với cơ sở thì không nhất thiết là một cơ sở.
Định nghĩa 7. Tọa độ
Bộ vô hướng xác định bởi điều kiện được gọi là tọa độ của vector trong cơ sở . Vô hướng được gọi là tọa độ thứ của trong cơ sở đó.
Bây giờ ta cùng xem xét tọa độ của một vector trong những cơ sở khác nhau thì có liên quan với nhau như thế nào. Xét hai cơ sở và của không gian vector . Như vậy mỗi vector biểu thị tuyến tính được qua tức là có các số sao cho
Giả sử có tọa độ lần lượt là và trong hai cơ sở và . Thì khi đó
Và do mỗi biểu thị tuyến tính của trong là duy nhất cho nên ta có
Không gian con - Hạng của một hệ vector
Định nghĩa 8. Không gian con
Tập con không rỗng được gọi là một không gian vector con của nếu như cũng đóng kín đối với hai phép toán trên , tức là
Mệnh đề 1. Nếu là một không gian vector con của thì ta cũng có .
Kết quả trên là hệ quả trực tiếp của khái niệm về số chiều của một không gian vector. Cụ thể với mỗi hệ độc lập tuyến tính trong thì cũng độc lập tuyến tính trong . Ngoài ra mỗi phần tử của hệ độc lập tuyến tính đều được biểu thị tuyến tính thông qua cơ sở của cho nên ta có . Dấu bằng xảy ra khi và chỉ khi hay , tức là khi đó mọi cơ sở của cũng là cơ sở của
Mệnh đề 2. Giao của một họ bất kì các không gian vector con của cũng là một không gian vector con của .
Thật vậy, xét một họ các không gian vector con của là thì khi đó cũng thỏa mãn tính đóng đối với phép cộng và nhân.
Định nghĩa 9. Không gian vector sinh bởi
Giả sử là một tập con của không gian vector . Giao của tất cả các không gian vector con của mà chứa được gọi là không gian vector con của sinh bởi và được kí hiệu là .
Từ định nghĩa, ta suy ra được rằng là không gian vector con nhỏ nhất của mà chứa
Khi đó là tập hợp các tổ hợp tuyến tính của các phần tử của . Nói riêng nếu thì
Thật vậy, tập hợp các tổ hợp tuyến tính của chính là một không gian con của mà chứa . Mặt khác, mọi tổ hợp tuyến tính của đều nằm trong mọi không gian vector con của mà chứa . Vì với mỗi không gian vector con mà chứa thì khi đó các phần tử của cũng thỏa mãn tính đóng đối với phép cộng và nhân vô hướng trên . Điều này dẫn tới mọi tổ hợp tuyến tính của cũng nằm trong .
Tiếp theo ta xét về số chiều của .
Nhận xét: Số chiều của không gian được gọi là hạng của tập hoặc hệ vector và được kí hiệu là . Tương tự ta gọi một tập con các vector của là tuyến tính cực đại nếu như ta thêm bất kì vector nào trong vào tập đó thì tập sẽ trở thành tập con phụ thuộc tuyến tính. Như vậy, để tính hạng của ta sẽ đếm số vector của mỗi tập con độc lập tuyến tính cực đại trong
.
Hệ quả: Hai tập con độc lập tuyến tính cực đại trong thì có số phần tử bằng nhau.
Tổng và tổng trực tiếp
Ta giả sử là các không gian vector con của . Khi đó tập hợp
hiển nhiên lập nên một không gian vector con của .
Định nghĩa 10. Tổng của các không gian vector
Không gian vector được gọi là tổng của các không gian vector con .
Khi đó mỗi vector thuộc không gian vector trên có thể được viết dưới dạng
Tuy vậy, cách viết trên không phải là duy nhất. Vì thế ta đi đến một khái niệm chặt hơn như sau.
Định nghĩa 11. Tổng trực tiếp
Nếu mọi vector trong không gian đều được viết duy nhất dưới dạng thì khi đó ta gọi là tổng trực tiếp của các không gian vector và kí hiệu là . Từ định nghĩa trên ta đi đến các tính chất nhằm giúp ta xác định khi nào tổng trên thỏa mãn là một tổng trực tiếp của các không gian vector con.
Định lí. là tổng trực tiếp khi và chỉ khi điều kiện sau đây được thỏa mãn:
Ta có định lí sau đây khi nói về số chiều của tổng các không gian vector:
Định lí. Giả sử và là một không gian vector con của một không gian vector hữu hạn chiều.
Khi đó ta có
Chứng minh.
Giả sử là một cơ sở cho . Nếu thì ta coi như . Ta bổ sung hệ này để có một cơ sở của và một cơ sở của . Ta sẽ chứng minh rằng là một cơ sở của . Rõ ràng là một hệ sinh của .
Với mỗi được viết dưới dạng
Bây giờ, để chứng minh tính độc lập tuyến tính, ta giả sử có một ràng buộc tuyến tính
với . Ta cần chứng minh . Xét đẳng thức sau đây:
Vector bên trái thuộc vào và vector bên phải thuộc vào vì nó là vector sinh bởi cơ sở của . Như vậy vector trên thuộc vào . Do đó nó cũng có một biểu diễn tuyến tính qua như sau
Nhưng do hệ độc lập tuyến tính cho nên . Như vậy thay ngược lại ở trên ta có
Và một lần nữa do là một hệ độc lập tuyến tính cho nên . Vậy ta có điều phải chứng minh. Từ những nhận xét trên ta suy ra là cơ sở của .
Từ định nghĩa về tổng trực tiếp ta cũng suy ra ngay
Định nghĩa 12. Phần bù tuyến tính
Nếu thì được gọi là một phần bù tuyến tính của trong và được gọi là đối chiều của trong . Giả sử . Khi đó mỗi vector trong có thể được biểu diễn duy nhất dưới .
Định nghĩa một ánh xạ
Nó được gọi là phép chiếu từ lên theo phương . Phép chiếu có các tính chất sau:
Không gian thương - Quotient Spaces
Đầu tiên ta có định nghĩa về quan hệ tương đương.
Định nghĩa. Một quan hệ tương đương trên một tập hợp là một mối quan liên hệ giữa các phần tử của thỏa mãn đồng thời các điều kiện sau:
- Tính phản xạ : với mọi
- Tính đối xứng: Nếu thì
- Tính bắc cầu: Nếu và thì
Lattice
Qui ước
Các kí hiệu: Các vector với các phần tử trong được xem như là vector cột và được biểu thị bằng các chữ in thường đậm, ví dụ . Các ma trận được biểu thị bằng các chữ cái in hoa đậm, ví dụ
Chuyển vị của một vector hoặc ma trận được biểu thị bằng hoặc tương ứng. Ngoài ra, chúng ta biểu thị tích trong của hai vector cột theo .
Một số kí hiệu khác:
- : không gian của ma trận với các mục số thực
- : Chuẩn Euclid và chuẩn tối đa
- : Một lưới
- : lưới với các cột của ma trận làm cơ sở.
- : Số nhỏ nhất sao cho có vector độc lập tuyến tính trong mà mỗi vector đó có chuẩn không vượt quá .
- : Hệ số xấp xỉ (gần đúng) trong các bài toán lưới
- : một vector cơ sở lưới
- : một vector trong cơ sở trực giao Gram-Schmidt của một lưới.
Mở đầu
Nhắc lại một số định nghĩa
Một lattice là một không gian vector được sinh bởi các tổ hợp tuyến tính nguyên của các vector độc lập tuyến tính. Tức là
trong đó . Đặt thì , đồng nghĩa với việc là ảnh của dưới ánh xạ tuyến tính .
Ta có thể định nghĩa lưới theo một cách khác. Cho với . Một lưới chiều là một nhóm con cộng rời rạc của chứa tất cả các tổ hợp tuyến tính nguyên của vector độc lập tuyến tính . Đó là với ma trận . Ta gọi là cơ sở của và là rank của lưới. Sau này ta chỉ quan tâm tới các full rank lattice tức là các lattice có hạng bằng với số chiều.

Định thức của một lưới với cơ sở được xác định bởi . Nếu lattice là full rank thì .
Lưu ý rằng cơ sở của một lưới không là duy nhất, hay nói đúng hơn với mỗi ma trận đơn nhất thì cũng là cơ sở cho cùng một lưới. Vì ta có tính chất sau: Với hai cơ sở và cho lưới thì cho nên suy ra .
Một ma trận đơn nhất là một ma trận thỏa mãn . Như vậy nếu ta lấy thì sẽ thỏa mãn định thức hai ma trận trên là như nhau.
Tính chất trên tuy đơn giản nhưng rất quan trọng đối với việc triển khai các hệ mật, vì một số cơ sở dễ xử lí hơn những cơ sở khác.
Mỗi lưới có một lưới đối ngẫu (dual lattice), kí hiệu .
Các bài toán khó
Bài toán tìm vector ngắn nhất - SVP : Cho một cơ sở tùy ý của một lưới có số chiều , hãy tìm một khác không với
Trong đó là độ dài cực tiểu thứ nhất của lưới (first successive minimum). Mà theo định lí thứ nhất của Minkowski thì với một full rank lattice với rank là thì
Hiểu một cách trực quan: là bán kính của một quả cầu có tâm tại gốc tọa độ sao cho trong quả cầu đó tồn tại vector độc lập tuyến tính.
Biến thể của bài SVP là bài uSVP là tìm vector ngắn nhất và duy nhất.

Bài toán vector gần nhất - CVP: Cho một cơ sở tùy ý của một lưới và một vector đích (không nhất thiết thuộc vào ). Tìm một vector thuộc lưới sao cho .

Các thuật toán rút gọn lưới
Các thuật toán rút gọn lưới nhằm mục đích lấy một cơ sở đưa ra của một lưới và biến đổi nó thành một cơ sở khác cho cùng lưới nhưng với các vector ngắn hơn và trực giao hơn. Bằng cách này, một vector lưới ngắn có thể được khôi phục. Các thuật toán thường không được thiết kế để tìm chính xác, mà là để trích xuất một vector ngắn trong một số yếu tố gần đúng, nói cách khác là để giải một số bài toán lưới khó gần đúng.
Thuật toán Gram-Schmidt
Hai vector được gọi là trực giao nếu như tích trong của chúng bằng 0 tức là . Một hệ các vector được gọi là một hệ trực giao nếu như chúng đôi một trực giao. Tương tự, một hệ trực giao các vector đơn vị được gọi là một hệ trực chuẩn.
Ta có thể chứng minh rằng mọi hệ trực chuẩn đều độc lập tuyến tính.
Định lí: Cho là một hệ các vector độc lập tuyến tính của không gian Euclid . Khi đó ta có thể tìm được một hệ trực chuẩn sao cho
Để xây dựng một hệ trực chuẩn thì trước hết ta sẽ xây dựng một hệ trực giao rồi lấy từng vector chia cho chuẩn của chính nó.

from sage.all import *
def gram_schmidt(vectors):
orthogonal_vectors = []
for v in vectors:
for u in orthogonal_vectors:
v -= (v * u) / (u * u) * u
orthogonal_vectors.append(v)
return orthogonal_vectors
v1 = vector([4, 1, 3, -1])
v2 = vector([2, 1, -3, 4])
v3 = vector([1, 0, -2, 7])
v4 = vector([6, 2, 9, -5])
v = [v1,v2,v3,v4]
output = gram_schmidt(v)
print(output)Thuật toán LLL
Thuật toán rút gọn lưới đầu tiên là LLL. Mục tiêu của thuật toán LLL là tìm ra một cơ sở của lưới sao cho các vector Gram-Schmidt không quá giảm về kích thước. Cụ thể, với ta gọi một cơ sở là một cơ sở rút gọn nếu như
- với mọi .
- .

Phân tích: Thuật toán bắt đầu bằng việc tìm một cơ sở trực giao từ một cơ sở cho trước. Trong quá trình trực giao hóa Gram-Schmidt, sẽ có một số hệ số Gram-Schmidt thỏa mãn:
Ta có thể giảm độ lớn của các hệ số này bằng cách lấy . Nếu có hai hàng không thỏa mãn điều kiện thứ hai thì ta sẽ swap 2 hàng này và lặp lại thuật toán. Bằng cách này ta sẽ làm giảm dần các hệ số và thu được một cơ sở rút gọn.

Mọi người có thể xem implement của key-moon tại đây https://github.com/key-moon/pylll/blob/main/pylll/lll.py
Cài đặt flatter Tham khảo tại link sau https://github.com/keeganryan/flatter.
Để cài thì đầu tiên cần clone repo về terminal của mình
git clone https://github.com/keeganryan/flatter.
Tool được build bằng cmake nên cần cài thêm như sau

sudo apt update
sudo apt install build-essential cmakeTiếp theo tạo một thư mục build và chạy cmake
mkdir build
cd build
cmake ..Mình gặp một cái lỗi như này

Lỗi này là do ta chưa cài Eigen, một dependency cần có để build flatter.
Tải nốt bằng lệnh sau:
sudo apt install libeigen3-devRồi sau đó chạy lại toàn bộ quá trình một lần nữa
cd ~/flatter/build
cmake ..
make -j$(nproc)Mọi người thấy thông báo như này hiện ra thì coi như oke
-- Found FPLLL: /usr/include (found suitable version "5.4.1", minimum required is "5.1.0")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/duc112006/flatter/buildCuối cùng là sao chép file thực thi vào /usr/local/bin để chạy trong VSCode
duc112006@LAPTOP-VB45ARKK:~/flatter/build$ sudo cp bin/flatter /usr/local/bin/flatter
duc112006@LAPTOP-VB45ARKK:~/flatter/build$ which flatter
/usr/local/bin/flatter
duc112006@LAPTOP-VB45ARKK:~/flatter/build$Test:
from sage.all import *
from Crypto.Util.number import *
def flatter(M):
import re
from subprocess import check_output
# compile https://github.com/keeganryan/flatter and put it in $PATH
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), list(map(int, re.findall(b"-?\\d+", ret))))
M = [[-2, 2], [-2, 1]]
M = Matrix(ZZ, M)
M = M.transpose()
M = flatter(M)
print(M)
M = [[-2, 2], [-2, 1]]
M = Matrix(ZZ, M)
m = M.transpose()
print(m.LLL())Lưu ý: Do thuật toán LLL thực hiện rút gọn theo cột của ma trận nên trước khi rút gọn cần phải dùng cú pháp M.transpose() để chuyển vị ma trận hoặc nếu ta đã sắp xếp đúng theo định dạng thì không cần phải chuyển vị nữa.
TODO:
Mình có thử implement lại thuật toán LLL như dưới đây:
M = Matrix([[-10 , -8 , 10 , -2],
[ -2 , -6 , -3 , -1],
[ -3, 10 ,-8 , -8],
[ 1, -2, -10, -6]])
def toList(mat):
return [mat[i] for i in range(mat.nrows())]
# pure python + sage matrix supporter
# M = Matrix(ZZ,[[4, 1, 7, -1],[2, 1, -3, 4], [1, 0, -2, 7], [6, 2, 9, -5]])
def gram_schmidt(mat):
u = []
v = toList(mat)
n = mat.nrows()
mu = Matrix(QQ, mat.nrows(), mat.ncols())
n = mat.nrows()
for i in range(n):
vi = v[i]
for j in range(i):
mu[i,j] = (vi * u[j]) / (u[j]*u[j])
vi = vi - mu[i,j] * u[j]
u.append(vi)
mu[0,0] = 1
return Matrix(QQ, u), Matrix(QQ, mu)
def print_mat(mat):
for row in mat:
print(row,'\n')
_, riel = M.gram_schmidt()
_ , mu = gram_schmidt(M)
assert mu[0,0] == riel[0,0]
def my_LLL(mat, delta= 0.99):
mat = Matrix(QQ,mat)
ortho, mu = mat.gram_schmidt()
k = 1
n = ortho.nrows()
while k < n :
for j in range(k-1, -1,-1):
prod = (mu[k,j])
if abs(prod) > 1/2:
mat[k] = mat[k] - round(prod)*mat[j]
ortho , mu = mat.gram_schmidt()
if (ortho[k].dot_product(ortho[k])) >= (delta-mu[k,k-1]**2)*(ortho[k-1].dot_product(ortho[k-1])):
k +=1
else:
mat[k], mat[k-1] = mat[k-1], mat[k]
ortho , mu = mat.gram_schmidt()
k = max(k-1, 1)
return mat
print(flatter(M),"\n")
print(M.LLL(),"\n")
print(my_LLL(M,delta=0.99))
def compare_mat(mat1, mat2):
if mat1.nrows() != mat2.nrows() or mat1.ncols() != mat2.ncols():
return False
for i in range(mat1.nrows()):
if mat1[i] != mat2[i]:
return False
return True
for _ in range(100):
M = Matrix(ZZ,[[randint(-100,100) for _ in range(5)] for _ in range(5)])
res1 = my_LLL(M)
res2 = M.LLL()
if not compare_mat(res1, res2):
print(_)
print("Mismatch found!")
print("Input matrix:")
print(M)
print("Custom LLL result:")
print(res1)
print("Sage LLL result:")
print(res2)
break
else:
print("All tests passed!")Và có 2 vấn đề như sau:
- Đầu tiên là trong sage, nó chạy LLL mặc định với hệ số .
- Thứ hai là trong quá trình chạy Debug, hai ma trận
res1vàres2ra kết quả khác nhau thì thường rơi vào 2 trường hợp. Trường hợp thứ nhất là có một hàng bị đổi dấu so vớiLLLcủa sage. Trường hợp thứ hai là hàng cuối cùng của thuật toán của mình không nhỏ bằng hàng cuối cùng củasage.LLL(). Hiện tại mình vẫn chưa hiểu tại sao lại như vậy.
Mình có tìm thêm các tài liệu khác về LLL. Một bài mà mình tìm thấy ở trên trang CryptoBook như sau: https://cryptohack.gitbook.io/cryptobook/lattices/lll-reduction/lll-reduced-basis
def LLL(B):
d = B.nrows()
i = 1
while i<d:
size_reduce(B)
if swap_condition(B):
i += 1
else:
B[i],B[i-1] = B[i-1],B[i]
i = max(i-1,1)
return BMột số paper nên đọc:
[1] https://eprint.iacr.org/2025/774.pdf
[2]
Note: Kết hợp với yield
Yield là một keyword khá thú vị trong Python.
from sage.all import *
B = matrix(ZZ, [
[1,2,3],
[4,5,6],
[7,8,9]
])
print(B)
def shortest_vector(B):
B_ = B.LLL()
for row in B_:
if not row.is_zero(): # method for ZZ-vector sage
yield row
l = shortest_vector(B)
print(l.__next__())Trong python có hai kiểu object chính đó là iterator object và generator object.
Hiểu đơn giản thì iterator object là những object mà ta có thể dùng hàm for để duyệt qua.
Còn về generator thì nó hoạt động như sau: Khi hàm shortest_vector ở trên được gọi nó sẽ trả về một generator object chứ không thực sự gọi hay thực thi hàm. Chỉ khi ta gọi ta gọi phương thức __next__ của Python thì hàm sẽ bắt đầu chạy cho tới khi gặp yield và giá trị của yield sẽ được trả về cho __next__. Chi tiết hơn về yield và generator mình sẽ trình bày trong một bài khác.
Một số định lí và tính chất
Mục đích ban đầu của thuật toán LLL là phân tích các đa thức trong , điều này thực hiện trong thời gian đa thức. Tuy nhiên trên đường đi, nó khôi phục một vector lưới có độ dài tối đa lần so với vector ngắn nhất trong một số lưới , tức là nó có thể giải được trong thời gian trong đó là độ dài của vector cơ sở đầu vào dài nhất.
Cụ thể ta có các định lí sau đây
Định lí 1. Cho là một cơ sở lattice và là cơ sở sau khi trực giao hóa Gram Schmidt. Khi đó .
Chứng minh.
Xét là vector nhỏ nhất khác 0 và vì khác vector 0 cho nên tồn tại một chỉ số lớn nhất sao cho . Khi đó . Bây giờ xét điểm nguyên thì ta
Mà lại có
Định lí 2. Cho là một cơ sở rút gọn rút gọn. Khi đó .
Định lí này có ý nghĩa rằng: vector đầu tiên sau khi rút gọn LLL chưa chắc là vector ngắn nhất nhưng nó là một vector gần với vector ngắn nhất của mạng.
Từ điều kiện rút gọn lattice ta có
Mà ta có kéo theo
Cho nên
Ngoài ra ta còn có một khái niệm khác gọi là heuristic. Heuristic là các giả định dùng để ước lượng số lượng vector mà lattice có trong một miền nhất định. Hai phương pháp heuristic thường được sử dụng là Gaussian Heuristic và Geometric Series Assumption GSA.
Mọi người có thể xem qua bài viết sau: LLL on the Average. Tác giả có đưa ra một nhận xét như sau:
Giả thuyết: Với mỗi lưới ngẫu nhiên có rank là và cho là một cơ sở rút gọn LLL của nó. Thì khi đó
Gaussian Heuristic là một ước lượng của : Mật đố của các điểm là , vì vậy trong một quả cầu có bán kính là , chúng ta mong đợi sẽ có các điểm lưới, trong đó là thể tính của quả cầu đơn vị chiều. Cụ thể, điều này đưa ra một ước lượng cho thông qua . Vì thể tích của quả cầu đơn vị được tính bởi
GH đưa giả ra định:
Bài toán CVP
Như đã trình bày ở trên thì thuật toán LLL giúp ta giải bài toán SVP và apprSVP. Bài toán tiếp theo mà ta tìm hiểu sẽ là bài toán CVP và apprCVP tức là bài toán tìm vector gần nhất với vector cho trước. Cụ thể với lưới và một target vector , ta sẽ tìm sao cho là nhỏ nhất.
Thuật toán Babai
Đầu tiên để sinh ra một cơ sở thuận tiện cho tính toán thì ta cần làm 2 việc đó là rút gọn LLL cho cơ sở và chuyển cơ sở thành cơ sở trực giao với thuật toán gram schmidt. Chẳng hạn với cơ sở trực giao thì độ dài của mỗi vector trong được tính bởi
Ta xét một siêu phẳng sinh bởi vectors đầu tiên của lưới và đặt trong đó là số nguyên được chọn sao cho siêu phẳng được tịnh tiến bởi gần nhất có thể. sẽ được tính bởi . Tiếp tục làm tương tự cho tới khi thu được tổng và cũng chính là giá trị mà ta cần tìm.
Định nghĩa siêu phẳng: Tập là siêu phẳng trong nếu là không gian affine chiều.
Chi tiêt mọi người có thể xem ở đây https://ecampusontario.pressbooks.pub/daisotuyentinh/chapter/hyperplanes-and-half-spaces/

Implement:
from sage.all import *
from sage.modules.free_module_integer import IntegerLattice
def flatter(M):
import re
from subprocess import check_output
# compile https://github.com/keeganryan/flatter and put it in $PATH
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), list(map(int, re.findall(b"-?\\d+", ret))))
def Babai_CVP(mat,target):
M = flatter(mat)
G = M.gram_schmidt()[0]
diff = target
for i in reversed(range(G.nrows())):
diff -= M[i] * ((diff*G[i]) / (G[i]*G[i])).round()
return target - diff Kannan’s Embedding
Một cách khác để xử lí CVP đó là nhúng vector cần tính vào trong ma trận cơ sở của lưới và chuyển từ bài CVP thành SVP.
Xét là cơ sở của lưới và là target vector. Nghiệm của CVP lúc này sẽ có dạng . Như vậy
trong đó ta có thể coi là sai số nhỏ. Xét một lưới với các cơ sở như sau:
Viết đầy đủ ma trận ra như sau:
Như vậy lưới này sẽ có một vector nhỏ sinh bởi tổ hợp tuyến tính .
Ví dụ:
Giả sử ta có một ma trận như sau:
là một lattice trong . Vector mục tiêu của ta là . Ta chọn trọng số . Xây dựng ma trận mới và làm theo thuật toán ở trên:
M = Matrix(ZZ, [[35,72,-100,0],[-10,0,-25,0],[-20,-279,678,0],[100,100,100,1]])
target = vector(ZZ,[100,100,100])
M = flatter(M)
print(M)
for row in M:
if row[-1] == 1:
vec = target - vector(row[:-1])
print(f"solution là {vec}")Bài toán ACD
Cho các tham số và là một số nguyên lẻ có độ lớn là -bit. Như vậy ta sẽ có
không nhất thiết phải là số nguyên tố và ta sẽ xem xét kĩ hơn trong các biến thể khác của bài toán. Ta xét một tập hợp các mẫu thử như sau:
Trên thực tế ta có giá trị của nhỏ hơn rất nhiều cho nên các mẫu thử từ sẽ gồm các số với xác suất lớn. Hơn nữa với đủ lớn và là các mẫu thử từ thì ta kì vọng rằng sẽ có ít nhất một giá trị sao cho
Lúc này bài toán ACD được phát biểu như sau: Cho một số hữu hạn các giá trị được làm nhiễu từ , hãy tính toán lại giá trị của .
Một biến thể của bài là bài PACD - partial approximate common divisor problem: Tương tự bài ACD nhưng ta được biết được một mẫu “sạch” không bị làm nhiễu là
Ngoài ra ta còn có các decisional versions của bài nhưng trong phạm của bài viết này mình sẽ không đề cập đến (nếu có mình sẽ viết lại trong thư mục Theoretical Cryptography)
Ngoài ra còn một phiên bản khác là CRT-ACD mọi người có thể xem tại đây
Tóm lại, bài toán ACD với tham số là bài toán tìm lại giá trị bí mật từ một tập mẫu thử có dạng trong đó
- có độ lớn bits,
- các số được phân bố đều trong khoảng
- các số được phân bố đều trong khoảng
Simultaneous Diophantine approximation - SDA
Attack này được dùng trong trường hợp giá trị làm nhiễu quá lớn để có thể vét cạn và ta có nhiều mẫu bị nhiễu. Tức là các giá trị lớn và ta được biết nhiều giá trị .
Ý tưởng chính của attack này đó là từ phương trình với thì ta có thể viết lại dưới dạng:
với mỗi . Ta có thể xem như một xấp xỉ Diophante của . Như vậy nếu như ta xác định được giá trị của thì ta có thể giải được bài toán ACD bằng cách tính và có . Thực tế ta không cần giá trị của mẫu để giải bài toán trên.
Ta sẽ xây dựng một lattice có rank là như sau:
Nó sẽ chứa một vector là tổ hợp tuyến tính của như sau:
Vì ta có : với cặp thì
Mặt khác
Vì cho nên chuẩn euclid (norm) của sẽ xấp xỉ
Bây giờ đối với ma trận của ta thì theo Gaussian Heuristic ở trên, nếu như
thì ta có thể kì vọng vector sẽ là vector ngắn nhất sinh ra bởi lưới. Như vậy ta sẽ chạy thuật toán LLL cho lưới trên và lấy phần tử đầu tiên của vector kết quả chia cho . Đây chính là giá trị có thể có của . Sau đó ta sẽ tính và kiểm tra giá trị của bằng cách tính thử xem có “nhỏ” hay không.
Implement:
from sage.all import *
def flatter(M):
from subprocess import check_output
from re import findall
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))
def symmetric_mod(x, m):
return int((x + m + m // 2) % m) - int(m // 2)
def SDA(x, rho):
assert len(x) >= 2
R = 2 ** (rho + 1)
B = matrix(ZZ, len(x), len(x))
B[0, 0] = R
for i in range(1, len(x)):
B[0, i] = x[i]
B[i, i] = -x[0]
B_ = flatter(B)
for v in B_:
if v[0] != 0 and v[0] % R == 0:
q0 = v[0] // R
r0 = symmetric_mod(x[0], q0)
p = abs((x[0] - r0) // q0)
r = [symmetric_mod(xi, p) for xi in x]
if all(-R < ri < R for ri in r):
return int(p), r(Nếu k có flatter thì mọi người có thể dùng LLL của sage =)) mà flatter lâu lâu cx hay lỏ,mn nên thử cả hai và nên cập nhật phiên bản mới của flatter trước khi chạy)
Hidden Number Problem - HNP
Có hai phiên bản khác nhau của HNP, ở đây mình sẽ trình bày lại phiên bản quen thuộc hơn. Phiên bản còn lại mọi người có thể xem ở đây
Hidden Number Problem. Cho là một số nguyên tố và là một số nguyên bí mật mà ta cần tìm với điều kiện rằng ta được biết cặp giá trị thỏa mãn
trong đó các giá trị được ẩn đi và ta chỉ biết rằng với nào đó.
Có hai hướng tiếp cận đó là dựa vào bài toán CVP hoặc SVP.
Đối với thiết lập CVP, ta sẽ xét lattice được xây dựng dựa trên ma trận sau:
Ta viết lại phương trình HNP dưới dạng với mỗi số nguyên . Như vậy ta có một vector được sinh ra bởi tổ hợp tổ tuyến . Đặt và thì ta có trong đó độ dài của (norm) bị chặn trên bởi trong khi định thức của là . Như vậy ta sẽ chuyển sang bài toán CVP với target vector là . Để khôi phục lại ta sẽ thử nhân phần tử cuối của vector với .
Còn đối với SVP, thì ta sẽ xét lattice sinh bởi ma trận sau đây:
Lattice này sẽ chứa một vector ngắn là
sinh bởi tổ hợp tuyến tính . Ta có và dựa vào các giả định ở trên về cơ sở rút gọn LLL thì ta có thể kì vọng rằng sẽ tìm được vector trong số các cơ sở của ma trận mới. Lưu ý thêm rằng, lattice này cũng chứa một vector ngắn là , sinh bởi cho nên có thể rơi vào vector ngắn thứ hai của cơ sở (hoặc nếu không thì ta có thể thử hết =))?)
def hnp(p, T, A, B, lattice_reduction=None, verbose=False):
r"""
Returns the solution of the given hidden number problem instance. i.e. finds
`\alpha \in [1, p - 1]` satisfying
.. MATH::
\beta_i - t_i \alpha + a_i \equiv 0 \pmod p
where the `t_i` and `a_i` are given by ``T`` and ``A``, and the `\beta_i`
are bounded above by ``B``.
The implementation follows the algorithm as described in section 2.5 of [1].
INPUT:
- ``p`` -- The modulus `p` as described above.
- ``T`` -- A list of integers representing the `t_i`.
- ``A`` -- A list of integers representing the `a_i`.
- ``B`` -- The upper bound on the `\beta_i` as described above.
OUTPUT:
The integer `\alpha` which is a solution to the HNP instance. If no solution
could be found, None is returned.
REFERENCES:
[1] Martin R. Albrecht and Nadia Heninger. *On Bounded Distance Decoding with Predicate:
Breaking the “Lattice Barrier” for the Hidden Number Problem.*
In Lecture Notes in Computer Science, p. 528--558. Springer, 2021.
https://link.springer.com/chapter/10.1007/978-3-030-77870-5_19
"""
verbose = (lambda *a: print('[hnp]', *a)) if verbose else lambda *_: None
if len(T) != len(A):
raise ValueError(f'Expected number of t_i to equal number of a_i, but got {len(T)} and {len(A)}.')
m = len(T)
M = p * Matrix.identity(QQ, m)
M = M.stack(vector(T))
M = M.stack(vector(A))
M = M.augment(vector([0] * m + [B / p] + [0]))
M = M.augment(vector([0] * (m + 1) + [B]))
M = M.dense_matrix()
verbose('Lattice dimensions:', M.dimensions())
lattice_reduction_timer = cputime()
if lattice_reduction:
M = lattice_reduction(M)
else:
M = M.LLL()
verbose(f'Lattice reduction took {cputime(lattice_reduction_timer):.3f}s')
for row in M:
if row[-1] == -B:
alpha = (row[-2] * p / B) % p
if all((beta - t * alpha + a) % p == 0 for beta, t, a in zip(row[:m], T, A)):
return alpha
if row[-1] == B:
alpha = (-row[-2] * p / B) % p
if all((beta - t * alpha + a) % p == 0 for beta, t, a in zip(-row[:m], T, A)):
return alpha
return None
Extended Hidden Number Problem - EHNP
EHNP là một biến thể mở rộng của HNP. Cụ thể bài toán được phát biểu như sau:
Extended Hidden Number Problem. Cho là một số nguyên tố và là một số nguyên bí mật thỏa mãn:
trong đó ta biết giá trị của và , và các số nguyên thỏa mãn (ta không được biết ) với cho trước. Bây giờ giả sử ta có phương trình dưới dạng
với và và được biết giá trị. Hơn nữa các giá trị bị chặn bởi và ta không biết giá trị của các số này. Bài toán của ta sẽ là tìm cách khôi phục lại giá trị từ các phương trình trên.
Như vậy, một trường hợp cho bài EHNP sẽ là:
Mọi người để ý rằng ta chỉ biết khoảng chặn của các giá trị bị ẩn, nói đơn giản chính là độ lớn về bit của các số này.
Giải các phương trình tuyến tính
Tiếp theo ta sẽ tìm hiểu xem thuật toán LLL có ứng dụng gì trong việc giải các phương trình tuyến tính.
Ví dụ 1. Xét phương trình
Ta muốn tìm một cặp nghiệm nhỏ . Một ý tưởng đơn giản là tìm cách xây dựng một lattice sao cho cũng là một vector được sinh bởi lattice này.
Ta viết lại phương trình như sau:
Và dựng một ma trận có dạng :
Ta kì vọng sẽ là vector ngắn của lattice này.
Sau khi rút gọn ta được:
sage: M = Matrix([[1,0,3],[0,1,5],[0,0,-2]])
sage: M.LLL()
[-1 1 0]
[ 0 -1 -1]
[-1 0 1]
sage:Ta chú ý tới các vector nào có phần tử cuối cùng là 0 thì đây chính là vector mà ta cần tìm. Như vậy ta có được một nghiệm là . Thử lại:
Nhưng cách build này có một vấn đề đó là ta không kiểm soát được giá trị của . Như vậy sẽ có khả năng trong một số trường hợp ta tìm ra một vector đúng định dạng mà ta mong muốn nhưng giá trị lại không thỏa mãn bằng khiến cho phương trình vô nghiệm.
Một cách xử lí khác đó là dựng ma trận
Bây giờ mình thử xài LLL cho ma trận này xem sao và cố tìm một vector ngắn trong cơ sở rút gọn có dạng . Đối với trường hợp này mình có được ngay:
sage: M = identity_matrix(3).augment(vector([3,5,2]))
sage: M
[1 0 0 3]
[0 1 0 5]
[0 0 1 2]
sage: M.LLL()
[ 1 -1 1 0]
[-1 0 1 -1]
[ 0 0 1 2]Như vậy trong trường hợp này thì ta có một nghiệm .
Nhưng không phải trong trường hợp nào thì thuật toán LLL cũng hoạt động tốt.
Chẳng hạn ta có một phản ví dụ sau đây:
Xét phương trình
Mình thử với ma trận:
sage: M = Matrix([[1,0,33],[0,1,7],[0,0,-11]])
sage: M.LLL()
[-1 0 0]
[ 0 -3 1]
[ 0 -2 -3]
sage:Thì không thu được hàng nào cho kết quả như mong muốn. Mình thử luôn với cách xây dựng ma trận kia xem sao:
sage: M = identity_matrix(3).augment(vector([33,7,-11]))
sage: M.LLL()
[-1 0 -3 0]
[ 1 -3 1 1]
[ 0 -2 -1 -3]
sage: M
[ 1 0 0 33]
[ 0 1 0 7]
[ 0 0 1 -11]
sage:Có 1 hàng có dạng . Mình thay lại thì được: . Mặc dù phương trình thỏa mãn VT = VP nhưng đây không phải là nghiệm ta mong muốn vì ta cần .
Để giải quyết việc này thì mình sẽ đánh “trọng số” vào hàng chứa hệ số của , tức là hàng thứ 3.
Sau khi chạy thì mọi người thấy ở hàng thứ 3 số vẫn giữ nguyên, điều này chứng tỏ .
sage: M = identity_matrix(3).augment(vector([33,7,-11]))
sage: M[2,2] = 1000
sage: M.LLL()
[ 1 -5 0 -2]
[ 1 -4 0 5]
[ 1 -3 1000 1]
sage:Theo cách mình hiểu cho việc đặt hệ số như trên đó là nằm ở cách LLL hoạt động. Ở mỗi bước nó sẽ kiểm tra điều kiện dưới đây:

Tức là nếu như có một hàng có chuẩn (norm) đủ lớn thì nó sẽ bỏ qua và chỉ tính toán đúng 1 lần.
Nhưng rất tiết là, với cách làm trên ta không thu lại được nghiệm cho phương trình ban đầu vì phần tử cuối của vector trên lại khác 0, trong khi ta lại mong muốn nó phải bằng 0.
Để làm được điều này thì ta sẽ nhân cột cuối cùng của ma trận với một số rất lớn khác. Như vậy, khi LLL chạy , nó sẽ cố gắng tìm một vector ngắn theo chuẩn Euclid và vì các vector của ta trong cơ sở ban đầu đều khá lớn cho nên nó sẽ bỏ qua các vector mà có phần tử cuối cùng khác 0 và cố gắng tìm một vector có phần tử cuối cùng bằng 0 (lưu ý rằng ta chỉ đang kì vọng rằng sẽ có một vector như vậy vì LLL không phải lúc nào cũng có thể cho ta nghiệm chính xác được)
sage: M = identity_matrix(3).augment(vector([33,7,-11]))
sage: M[2,2] = 1000
sage: M[:,-1] *= 1000
sage: M
[ 1 0 0 33000]
[ 0 1 0 7000]
[ 0 0 1000 -11000]
sage: M.LLL()
[ 7 -33 0 0]
[ 3 -14 0 1000]
[ -2 11 1000 0]
sage:Như vậy ta đã tìm được một nghiệm cho phương trình đó là .
Tiếp tục ta sẽ tìm hiểu một số phiên bản khác của bài toán giải phương trình/hệ phương trình tuyến tính
Phương trình đồng dư tuyến tính
Trong trường hợp ta muốn giải các phương trình đồng dư có dạng:
Ta sẽ chuyển về dạng phương trình tuyến tính như trên bằng việc thêm hệ số của modulo vào. Cụ thể
Xét lattice sau:
Bước tiếp theo là đánh trọng số cho phù hợp.
Ta muốn nên sẽ lấy cột số 3 nhân với trọng số và ta cũng muốn entry cuối cùng của target vector bằng 0 nên ta nhân cột cuối cùng của lattice với trọng số .

Một nghiệm là ở hàng cuối cùng.
CTF Challenges
Một số bài CTF có ứng dụng Lattice trong lời giải.
IdekCTF 2025 - Diamond Ticket
Source code của bài:
from Crypto.Util.number import *
#Some magic from Willy Wonka
p = 170829625398370252501980763763988409583
a = 164164878498114882034745803752027154293
b = 125172356708896457197207880391835698381
def chocolate_generator(m:int) -> int:
return (pow(a, m, p) + pow(b, m, p)) % p
#The diamond ticket is hiding inside chocolate
diamond_ticket = open("flag.txt", "rb").read()
assert len(diamond_ticket) == 26
assert diamond_ticket[:5] == b"idek{"
assert diamond_ticket[-1:] == b"}"
diamond_ticket = bytes_to_long(diamond_ticket[5:-1])
flag_chocolate = chocolate_generator(diamond_ticket)
chocolate_bag = []
#Willy Wonka are making chocolates
for i in range(1337):
chocolate_bag.append(getRandomRange(1, p))
#And he put the golden ticket at the end
chocolate_bag.append(flag_chocolate)
#Augustus ate lots of chocolates, but he can't eat all cuz he is full now :D
remain = chocolate_bag[-5:]
#Compress all remain chocolates into one
remain_bytes = b"".join([c.to_bytes(p.bit_length()//8, "big") for c in remain])
#The last chocolate is too important, so Willy Wonka did magic again
P = getPrime(512)
Q = getPrime(512)
N = P * Q
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
d = pow(e, -1, (P - 1) * (Q - 1))
c1 = pow(bytes_to_long(remain_bytes), e, N)
c2 = pow(bytes_to_long(remain_bytes), 2, N) # A small gift
#How can you get it ?
print(f"{N = }")
print(f"{c1 = }")
print(f"{c2 = }")
# N = 85494791395295332945307239533692379607357839212287019473638934253301452108522067416218735796494842928689545564411909493378925446256067741352255455231566967041733698260315140928382934156213563527493360928094724419798812564716724034316384416100417243844799045176599197680353109658153148874265234750977838548867
# c1 = 27062074196834458670191422120857456217979308440332928563784961101978948466368298802765973020349433121726736536899260504828388992133435359919764627760887966221328744451867771955587357887373143789000307996739905387064272569624412963289163997701702446706106089751532607059085577031825157942847678226256408018301
# c2 = 30493926769307279620402715377825804330944677680927170388776891152831425786788516825687413453427866619728035923364764078434617853754697076732657422609080720944160407383110441379382589644898380399280520469116924641442283645426172683945640914810778133226061767682464112690072473051344933447823488551784450844649Phân tích: Đầu tiên ta có 3 tham số và hàm chocolate_generator(m). Nó sẽ nhận vào số mũ và trả về .
Tiếp theo cách flag được mã hóa trong bài này như sau:
diamond_ticket = open("flag.txt", "rb").read()
assert len(diamond_ticket) == 26
assert diamond_ticket[:5] == b"idek{"
assert diamond_ticket[-1:] == b"}"
diamond_ticket = bytes_to_long(diamond_ticket[5:-1])
flag_chocolate = chocolate_generator(diamond_ticket)flag_chocolate chính là . Và ta phải giải để tìm lại flag từ thông tin này.
for i in range(1337):
chocolate_bag.append(getRandomRange(1, p))Hàm này tạo một danh sách chocolate_bag gồm các số ngẫu nhiên trong khoảng nhưng sau đó chỉ lấy 5 số cuối trong đó bao gồm flag của ta.
chocolate_bag.append(flag_chocolate)
#Augustus ate lots of chocolates, but he can't eat all cuz he is full now :D
remain = chocolate_bag[-5:]Sau đó nó sẽ nối các phần tử trong remain lại và mã hóa bằng RSA
remain_bytes = b"".join([c.to_bytes(p.bit_length()//8, "big") for c in remain])
#The last chocolate is too important, so Willy Wonka did magic again
P = getPrime(512)
Q = getPrime(512)
N = P * Q
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
d = pow(e, -1, (P - 1) * (Q - 1))
c1 = pow(bytes_to_long(remain_bytes), e, N)
c2 = pow(bytes_to_long(remain_bytes), 2, N) # A small giftTrước tiên mình cần tìm lại remain_bytes từ hai giá trị và .
Với được tạo bởi e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}") là một số lẻ.
Bây giờ giả sử ta có và . Muốn tính lại ta sẽ làm như sau: Ta gọi . Đối với bài này thì . Như vậy theo định lí Bezout tồn tại để .
Sau đó ta sẽ tính và và tính và khôi phục lại được giá trị của ban đầu. Nếu như thì ta có thể lấy nth_root(g) của nó .
Script cho bước này:
from Crypto.Util.number import *
from sage.all import *
N = 85494791395295332945307239533692379607357839212287019473638934253301452108522067416218735796494842928689545564411909493378925446256067741352255455231566967041733698260315140928382934156213563527493360928094724419798812564716724034316384416100417243844799045176599197680353109658153148874265234750977838548867
c1 = 27062074196834458670191422120857456217979308440332928563784961101978948466368298802765973020349433121726736536899260504828388992133435359919764627760887966221328744451867771955587357887373143789000307996739905387064272569624412963289163997701702446706106089751532607059085577031825157942847678226256408018301
c2 = 30493926769307279620402715377825804330944677680927170388776891152831425786788516825687413453427866619728035923364764078434617853754697076732657422609080720944160407383110441379382589644898380399280520469116924641442283645426172683945640914810778133226061767682464112690072473051344933447823488551784450844649
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
def attack(n, e1, c1, e2, c2):
g, u, v = xgcd(e1, e2)
p1 = pow(c1, u, n) if u > 0 else pow(pow(c1, -1, n), -u, n)
p2 = pow(c2, v, n) if v > 0 else pow(pow(c2, -1, n), -v, n)
return int(ZZ(int(p1 * p2) % n).nth_root(g))
remain_bytes = attack(N,e,c1,2,c2)
print(remain_bytes)remain_bytes có len == 80 bytes được tạo thành từ 5 phần, mỗi phần 16 bytes chính là len của số nguyên tố p. Vậy ta tách nó thành 5 phần bằng nhau và lấy phần cuối chính là flag_chocolate của ta.
print(remain_bytes)
remain_bytes = long_to_bytes(remain_bytes)
print(len(remain_bytes))
remain = [
int.from_bytes(remain_bytes[i:i + 16], "big")
for i in range(0, 80, 16)
]
flag_chocolate = remain[-1]
print(flag_chocolate)
# 99584795316725433978492646071734128819Tiếp theo ta sẽ giải DLP.
Bây giờ ta có và .
Kiểm tra order:
sage: p = 170829625398370252501980763763988409583
sage: factor(p-1)
2 * 40841 * 50119 * 51193 * 55823 * 57809 * 61991 * 63097 * 64577
sage:Order của nhóm khá smooth.
Nhưng ta cần tìm order của hai số và .
sage: p = 170829625398370252501980763763988409583
....: a = 164164878498114882034745803752027154293
....: b = 125172356708896457197207880391835698381
sage: K = Zmod(p)
sage: a = K(a)
sage: b = K(b)
sage: print(a.multiplicative_order())
85414812699185126250990381881994204791
sage: print(b.multiplicative_order())
85414812699185126250990381881994204791
sage: order1 = a.multiplicative_order()
sage: order2 = a.multiplicative_order()
sage: assert order1 == order2
sage: order = p-1
sage: print(order/order1)
2
sage:Vậy cấp của và là .
Về cơ bản thì không thể giải được bài toán nếu như giữ nguyên cả hai số và được. Nên mình đã thử tìm một số sao cho coi sao.
sage: k = discrete_log(b,a,order1)
sage: print(k)
73331
sage:Đưa về bài toán .
Bây giờ mình muốn tìm lại nên đã thử dựng đa thức .
Vấn đề là số mũ khá lớn nên để tìm lại nghiệm khá tốn thời gian nên mình xài một trick lỏ học được sau giải MaltaCTF vừa rồi. https://github.com/ctf-mt/maltactf-2025-quals/blob/master/crypto/grammar-nazi/solution/solve.sage
Ta đã biết với là một trường hữu hạn có đặc số nguyên tố thì với .
Ở đây ta đang làm việc với và một nhóm con của nó là nhóm các thặng dư bậc hai vì bậc của là . Nhóm con của một nhóm cyclic cũng là một nhóm cyclic cho nên mình sẽ dựng một đa thức khác là trong đó .
Vấn đề là trong sage khi gọi như vậy thì gặp lỗi
OverflowError: cannot fit 'int' into an index-sized integer
Do số mũ quá lớn nên sage không xử lí được. Để khắc phục thì mình lấy f.gcd(pow(x,r,f)-x).roots(multiplicities=False) là được.
K = Zmod(p)
a = K(a)
b = K(b)
order1 = a.multiplicative_order()
order2 = b.multiplicative_order()
r = (p-1)//2 + 1
print(order1 ,'\n')
print(order2)
# a^m + b^m = c mod p
k = discrete_log(b,a)
# k = 73331
c = K(flag_chocolate)
P = PolynomialRing(K, 'a')
x = P.gen()
f = x + x**k - c
res = f.gcd(pow(x,r,f)-x).roots(multiplicities=False)
print(res)
# 126961729658296101306560858021273501485a_m = K(126961729658296101306560858021273501485)
m0 = discrete_log(a_m,a,order1)
# m0 = 4807895356063327854843653048517090061Bước tiếp theo là tìm lại ban đầu. Ta biết thì . Vấn đề là chỉ có 122 bits trong khi gốc ban đầu có gồm 20 bytes tức là 160 bits ??
Ở bước này thì mình tham khảo Writeup của một bài trong giải SEETF 2023
Ứng dụng vào bài: Từ đề bài ta biết được flag có độ dài 26 bytes nhưng bằng việc bỏ đi 6 kí tự thì nó chỉ còn 20 bytes. Giả sử ban đầu flag của ta là thì sau khi chuyển từ bytes thành long (mặc định hàm bytes_to_long của pycryptodome lấy theo big endian ) nó sẽ có giá trị là:
Bây giờ ta có vừa tìm được chính là tổng trên nhưng được lấy theo modulo . Như vậy
Ta có thể build một lattice có dạng như sau:
Và kì vọng vector là vector ngắn nhất sinh ra bởi lưới.
Nếu để nguyên ma trận như vậy rồi rút gọn LLL thì sẽ không ra được kết quả như mong muốn. Do vậy ta cần chỉnh lại các hệ số trong ma trận một chút.
Như đã làm ở trên trong phần ứng dụng giải hệ phương trình tuyến tính, thì bây giờ ta muốn tạo một cơ sở cho lattice mà sau khi rút gọn LLL sẽ sinh ra một vector có dạng . Thì đầu tiên để cho phần tử cuối cùng của Vector này bằng 0 thì ta cần scale cột cuối cùng của ma trận bằng một số lớn. Ở đây mình chọn scale hệ số là .
Tiếp theo mình sẽ ép các entries trong các vector của cơ sở về một biên cụ thể, ở đây vì đều là các kí tự ASCII nên mình sẽ lấy hàng gần cuối có dạng . Giá trị này mình sẽ brute force để tìm ra sau =)) đại loại thì nó sẽ từ .
Tiếp theo là làm sao để phần tử kế cuối bằng . Thì mục tiêu của ta là scale cột thứ 2, nhưng vấn đề là scale như thế nào? Thì lúc này ta vẫn sẽ scale cột gần cuối bởi một số lớn bất kì nào đó. Nếu như sau khi LLL, giá trị tuyệt đối của cột đó vẫn giữ nguyên thì coi như ta thành công. Chẳng hạn ta đánh trọng số vào cột gần cuối là thì ta sẽ kì vọng thu được một vector có dạng:
Còn về việc chọn trong số bao nhiêu cho hợp lí thì mình bruteforce thôi. Trước tiên thì mình chạy thử coi sao.
Một số lưu ý khác trong việc chọn weight và các kĩ thuật giải quyết tương tự mình sẽ note vào trong các ví dụ khác. Còn đối với bài này thì cách bruteforce như trên vẫn đủ để giải quyết.
Final solve script:
from sage.all import *
from Crypto.Util.number import *
# step 1
N = 85494791395295332945307239533692379607357839212287019473638934253301452108522067416218735796494842928689545564411909493378925446256067741352255455231566967041733698260315140928382934156213563527493360928094724419798812564716724034316384416100417243844799045176599197680353109658153148874265234750977838548867
c1 = 27062074196834458670191422120857456217979308440332928563784961101978948466368298802765973020349433121726736536899260504828388992133435359919764627760887966221328744451867771955587357887373143789000307996739905387064272569624412963289163997701702446706106089751532607059085577031825157942847678226256408018301
c2 = 30493926769307279620402715377825804330944677680927170388776891152831425786788516825687413453427866619728035923364764078434617853754697076732657422609080720944160407383110441379382589644898380399280520469116924641442283645426172683945640914810778133226061767682464112690072473051344933447823488551784450844649
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
g, u, v = xgcd(e,2) # return eu + 2v = 1
# c1 = m^e mod N
# c2 = m^2 mod N
p1 = pow(c1, u, N) if u > 0 else pow(pow(c1,-1,N), -u, N)
p2 = pow(c2, v, N) if v > 0 else pow(pow(c2,-1,N), -v, N)
c = (p1*p2) % N
remain_bytes = int((ZZ(c)).nth_root(g))
# print(remain_bytes)
remain = long_to_bytes(remain_bytes)
# print(len(remain))
chunk = [remain[i*16:(i+1)*16] for i in range(5)]
flag_chocolate = int(bytes_to_long(chunk[-1]))
# print(flag_chocolate)
# step 2
p = 170829625398370252501980763763988409583
a = 164164878498114882034745803752027154293
b = 125172356708896457197207880391835698381
K = GF(p)
# flag_chocolate = a^m + b^m mod p
a = K(a)
b = K(b)
choco = K(99584795316725433978492646071734128819)
# print(a.multiplicative_order())
# print((p-1)//2)
k = b.log(a)
# print(k)
# a^m + (a^m)^k = choco
R = PolynomialRing(K, 'x')
x = R.gen()
f = x + x**k - choco
r = (p-1)//2 + 1
# root = f.gcd(pow(x,r,f)-x).roots(multiplicities = False)
# print(len(root))
# for r in root:
# print(r)
a_pow_m = K(126961729658296101306560858021273501485)
# m = discrete_log(a_pow_m,a, operation='*', ord = (p-1)//2)
# print(m)
m = 4807895356063327854843653048517090061
# step 3
# m = bytes_to_long(flag[5:-1]) % (p-1)//2
modulus = GF(p)(a).multiplicative_order()
for aa in range(80,126):
M = (identity_matrix(20).augment(vector([0]*20)).augment(vector([256**i for i in range(19,-1,-1)]))
.stack(vector([-aa]*20+[-1,-m])).stack(vector([0]*20+[0,-modulus]))
)
M[:,-1] *= 10**6
cand = []
for i in range(100,1000): # precomputed
M_ = copy(M)
M_[:,-2] *= i
cand.append(M_)
for Mat in cand:
for row in Mat.LLL():
if row[-1] != 0:
continue
if row[-2] == i:
row *= -1
if row[-2] == -i:
try:
flag = b"idek{" +bytes([val+ aa for val in row[:-2]]) + b"}"
print(flag)
break
except:
continueFlag: b'idek{tks_f0r_ur_t1ck3t_xD}'
Note: Trong sage thì để copy 2 ma trận ta sẽ dùng cú pháp copy(M). Lưu ý là không được gán kiểu M_ = M vì thực chất lúc này M_ và M sẽ trỏ vào chung một object nên khi M_ thay đổi thì M cũng thay đổi theo.
sage: M = [1,2]
sage: M_ = M
sage: M_.append(3)
sage: M_
[1, 2, 3]
sage: M
[1, 2, 3]
sage:Bigger RSA - ICTF 2025
Source code của bài:
from Crypto.Util.number import getPrime, bytes_to_long
import secrets
n = 32
e = 0x10001
N = 64
flag = b'ictf{REDACTED}'
flag = secrets.token_bytes((n * 63) - len(flag)) + flag
ps = [getPrime(512) for _ in range(n)]
m = 1
for i in ps:
m *= i
nums = [CRT([1 + secrets.randbits(260) for _ in range(n)],ps) for __ in range(N)]
ct = pow(bytes_to_long(flag),e,m)
print(f"ct={ct}")
print(f"m={m}")
print(f"nums={nums}")Tóm tắt
Flag ban đầu được padding thêm với độ dài là
Tiếp theo ta có là danh sách 32 số nguyên tố 512 bits và modulo
nums là danh sách gồm kết quả các lần lấy CRT giữa và một danh sách gồm các số nguyên ngẫu nhiên 260 bits.
Mỗi số như vậy, ta kí hiệu , và nó thỏa mãn
là một danh sách, hay một vector có độ dài là và được sinh ngẫu nhiên. Có 64 số như vậy.
Cuối cùng, flag của ta được mã hóa bởi và như mọi bài RSA khác ta cần tính được hoặc factor được để khôi phục lại được flag.
Ta thử biến đổi một chút giả thiết xem có gì đặc biệt hay không. Đầu tiên thì
với mỗi số nguyên tố thứ trong danh sách còn là phần tử thứ trong vector
Để ý là chỉ có độ lớn 260 bits so với các số nguyên tố là 512 bits. Vậy có gì sus ở đây? Đó là số samples lớn hơn số lượng số nguyên tố. Hơn nữa các giá trị lại quá bé so với .
Bây giờ, lấy là một ước nguyên tố bất kì của . Ta có thể viết lại thành bài toán ACD như sau:
Với mỗi thì
Trong đó là dương. Như vậy là các samples, là số nguyên tố cần tìm, là các giá trị nhiễu.
Ta còn được biết thêm cho nên bài toán trở thành bài toán PACD.
Ta xét ma trận cơ sở sau cho lattice:
Tham khảo paper sau: https://eprint.iacr.org/2024/1125
Thì ta sẽ có một target vector là
Script:
from sage.all import *
from Crypto.Util.number import *
import sys
sys.set_int_max_str_digits(10000)
with open('out.txt', 'r') as f:
data = f.read()
exec(data)
nums = [int(x) for x in nums]
div = [12825661825870628850035560085682551683599110773726524106705997668244622779138663874897188475532877346205351955798732755558280092764135146502129333667531273,8849247080017422968286437560856142727623460763416403577915666822178621608829806605500730083192464088951638255609862020842573930004233720962540782545280917,12636179557635617452330128900423408842203389165865121860715801777216229507326731176068378415443505580234827587398680217811961679156951468691902900103283911,11665745491363584588817758173385553841468540459223034134224520669080487519540459517215245489433604657499780082512713663950711196057044379146579277447799641,10193210740767481473019618865757332045483969049172119373119467546501386913192524524088772310971356251443648842033224837315650991716119025633640697531018367,9860624430169179237101055140804005926260705273640679771060493754118380693166065551828723961325454473971232083913340785881413212684328745162716466341641703,8198765300403592826995984598440010099299153959564583088450304003994567588524986967140022948908468461503336165216389379643683563868592309564288732696196381,9399262846686720121315011141246648648055720745589751334250006699346316301281406786164502055605005070307260703436205524531442672416733277454751993628239631,7497205738581146511103214896532677970457601649900731236571252220143900638616771482674225243713240678251231847312424134189997058636723535861317763367633641,7289439242613403106913459953533062235438046592580050161223546293986062672239122190117370418630708553308438056766522682658538319102870922735502285582346747,12124606354540998387639696647514778171390523966240573154197017585530097111120368848520582154389803842482131244622368481712191458586181046555572124506613251,8105728567181529459394712985955452977924319583512637598854476013170561450113469084263200652258026471761829210987135826986745819766196980387074523097073657,6888700314385525292891778504743903584494529201288456809897688611641727120314651838233357514470910140534188619131620158693746771631555477899637441167477453]
div.append(7726570664077541569500609696487048853838858832232316686907006156699125589812818999760924082971861995437404863431384702397077467392233847841327356802363801)
div.append(8294044843320375342348440529265349310074336151926255303163456989874771309684232815646857160747968956263444065889923214458311650834127524874020718004703063)
div.append(8687188077388195197942412743119551152103076903384085416686479408856483000564526371547623206120647931153257229436717458717575895261026293921157873579347523)
div.append(8490624530961994264444272185306408329363711421693856030812478451335188077181280975487946465857547173006542435443423976275834896724688227217926575962445513)
div.append(9541198062644681818808906411342123600626696583059201220221791584990253737970206989008003268135201906569392496774193646464956405331120128112399213831516337)
div.append(9642162115840667658352928012612798175019852945876463189055031372099012909214031198954415788761783119020007723644518732724519301404234194296600021095310903)
div.append(11066225255954538956086391741274699004347741181044658117167199042247926550120819890539271565072220049729624283496671856280507820187174258310798768129809643)
div.append(10650706826079171733657402315658310046544484471219755815725148141088338415583895923747381442625886083665772937180335995318006736797238782810674274323124133)
div.append(6785761235506713513312376605068746300508567988972967034908692130737909162277214242079951731490834338699742346610832568248780644781764595342608460247910481)
div.append(8407275141814496946434929131996618204666441620022963349926711447735636663875635640548596520101748747468751119077417845036475579129094075130130034331680921)
div.append(13304120223798684507299474307520334491371996458244395318574383542629544993743450154833532639719767107951453068991436133689433290741347655241089642756940277)
div.append(10055208163312818248211905302258271244505754955239681522022788361513189069056199757860548713346899425633159936306160990941381990616768347250997679669878983)
div.append(12988817382193630091431137466820689413789500172268375590891549414964355529275662477376343028415637353009995758639222386125419322028291397443311373577470303)
div.append(10367349082240332209527336924610925419601094897177737276881325800532431393521161527661162528033983877369208872297487119704934757865447474657206102635334471)
div.append(11440156453910579361669220291235374570309164955514078974773040600050290240145655916509977706026037945264964523845742593392070913039518343805824343520807021)
div.append(8952377250397141807169471340795003194902515693028612379693700708755894948874509812090273788737686410835319935781937173371830062801823355403555607449768299)
div.append(11252209285465510687668877327220225314043368189496350631539299840601500088579688774183851797307134519221926482312227783088020515669175292851103664879859359)
div.append(6732242136158511160127027152866595392289544303661945940690114485462693735083507892072393100603357914889898547391329909425237179853498580999515276757335323)
div.append(6884631120492254742857384566667440506378312675186188443493248024590755198358479687940627103876640032346648673787841382544028198056732915127377326737365661)
assert all(is_prime(x) for x in div)
print(len(div))
m = int(m)
for p in div:
assert m % p == 0
# m //= p
# LLL ở đây:
# rho = 260
# t = 64
# M = Matrix(ZZ, t+1, t+1)
# while len(div)<32:
# print("hiện tại tìm được :", len(div))
# M[0,0] = 2**rho
# for i in range(1,t+1):
# M[0,i] = nums[i-1]
# M[i,i] = -m
# M = M.LLL()
# found = False
# for r in M:
# if r[0] % 2**rho == 0:
# q = gcd(int(r[0]//(2**rho)),m)
# p = m//q
# if is_prime(p):
# assert m % p == 0
# print("tìm thêm được 1 ước nguyên tố:", p)
# div.append(p)
# m//=p
phi = 1
for ps in div:
phi *= (ps-1)
d = int(pow(int(0x10001),-1,int(phi)))
flag = long_to_bytes(pow(int(ct),int(d),int(m)))
print(flag)Flag:
ictf{rsa_lattices_and_crt_in_1_challenge_surely_has_real_world_applications_lmao_8719617206370154061234128157781624678026841512064124}
NOTE: Trong quá trình làm bài này có một chỗ khiến mình khá ức chế đó là LLL không cho ra hết được tất cả các ước trong cùng 1 lần chạy. Nếu bị kẹt ở một ước mãi không chạy ra thì mọi người thử xáo trộn lại danh sách các ước nguyên tố div rồi chạy thêm một lần nữa xem sao. Mình phải thêm bớt một chút cái danh sách div rồi chia lại cho m cỡ vài lần nó mới ra (???)
RSARSA - ICTF Round 52
Source code của bài:
from secret import flag
from Crypto.Util.number import bytes_to_long, getPrime
from os import urandom
p, q, P, Q = 1, 1, 1, 1
while p%3 == 1: p = getPrime(1001)
while q%3 == 1: q = getPrime(1001)
while P%3 == 1: P = getPrime(1024)
while Q%3 == 1: Q = getPrime(1024)
n = p*q
N = P*Q
m = bytes_to_long(flag.encode() + urandom(250-len(flag)))
e = 3
c = pow(m,e,n)
C = (pow(p,e,N)*q)%N
with open('out.txt','w') as file:
file.write(f'{n = }\\n')
file.write(f'{c = }\\n')
file.write(f'{N = }\\n')
file.write(f'{C = }\\n')
'''
n = 236165963974549843165116395504697902191713379536628118224442456369682302266394059291805550850454501790983078745877277808465242450967192483721221804260097540207577805062483656523327027127554710603966355779002569448126827875849586909708264874355346913072634179303036432164732044525075631421114547665911909245396864649081324916352615048804238378500443339490430805687717735684234839495464001575301049381927288871113238709183695134250935765261299598697578849137375656662373462784922948497996422774555641898913228531573884546030084889237337034678567704892293682351242487623366576639394595575585737748297628969
c = 70937012742384298918498947273158135897378405817961054208468507698675885078698256087375111837428304573078062065633783770628342991758025350342481268180700230612722899026588541878904025893741429507151056686887872849449670311554735563584135338155830845926213289013822577616362794865255147852918994796746331323556683930502366845200103692080775988066038363189731329287562177600843653604176315077351590599271405298532429144976167210974674215140921019287905291182057378400248332045718636113089603523143042785670709665781815838353423684587315486764254364359269569359888642641186171777475467206816292957014109298
N = 13559643931510231890645252059392928830324878403065777432078047001203751827960606291671049295745925572045928516195747933143730424354729124065331936456182264578676144427400292605461916569434409294456724314100819999479249740665646695282083306225220537019377541170020872360049287615554093874460563300874852415511897525645607710048718121342471391046511248087651223041929052149218448630378746909560110882899655656687984011210519333987281129803478797350285596913131043564417404130060868808451206852147929161483360924558387919617786314342462258102165618528861476250258876654302439406603504196138731002715669914240460335010891
C = 8275456195949033635467084095519107709201520788369843769112612900914126045121100567953979244052722913960259224583894825296560682673296622047075175324016507276388107904876439908187727967560228110789357765846667845045209064071847259841181338077835834748735605960343420564692832127030070311244168918947119031684158691819391344400702542409477570193678230185099108849985093513086193902773249508226511128276263713550182135750315888219451287568184957554794890731479934946953654922694958015237442372049821287406228796371974841586661090969263065535555883597103891939136230373286602239899758144274469967582717851587584634595648
'''Đầu tiên bài cho 4 số trong đó có độ lớn 1001 bits còn thì có độ lớn 1024 bits. Ta được biết thêm thông tin về . Ở đây là mod chứ không phải mod nếu không thì . Bây giờ ta có thể biến đổi
Từ đây, đặt thì ta có
Có thể chuyển về lattice để tính
Target vector của ta sẽ là . Vector này có norm là trong khi đó các cơ sở của lưới có độ lớn khoảng cho nên ta có thể kì vọng hàng đầu tiên sau khi rút gọn sẽ chứa .
Script:
from Crypto.Util.number import *
from sage.all import *
from sage.modules.free_module_integer import IntegerLattice
n =
c =
N =
C =
t = (n*n*pow(C,-1,N)) % N
M = Matrix([[t,1],[N,0]]).LLL()
for row in M:
print(row,'\\n')
for row in M:
if gcd(row[0],n) > 1 :
q = ZZ(int(abs(row[0])))
print(abs(row[0]))
p = n//int(q)
phi = (p-1)*(q-1)
d = pow(3,-1,phi)
m = pow(int(c),int(d),int(n))
print(long_to_bytes(int(m)))
# b'ictf{my_d0u813D_RSA_D!D_n07_Cook}Hệ mật LWE
Trước tiên ta nhắc lại bài toán học với lỗi - Learning With Errrors problem. Bài toán học với lỗi là bài toán yêu cầu ta khôi phục lại một giá trị bí mật bằng việc cho biết một dãy các phương trình tuyến tính xấp xỉ theo . Chẳng hạn
Ý tưởng cơ bản của bài toán là như trên. Nhưng ta cần một định nghĩa chính xác hơn cho nó như sau
Bài toán học với lỗi. Cho là các số nguyên và là phân bố trên . Với có các phần tử được chọn theo phân bố , định nghĩa là phân bố lấy các mẫu và rồi trả về .
- Với và mẫu độc lập từ phân bố , bài toán search LWE là bài toán tìm .
- Với và mẫu độc lập , bài toán LWE quyết định (decisional LWE problem) là bài toán phân biệt với thì hay .
Trong đó phân phối của lỗi (errors ) thường là phân phối Gauss (gaussian distribution) hoặc phân phối đều (uniform sampling).
Private Key Encryption from LWE
Từ ý tưởng trên ta có thể xây dựng một hệ mật như sau:
Một hệ mật khóa bí mật (private-key encryption) sẽ bao gồm 3 thuật toán chính. Đầu tiên là một thuật toán sinh khóa ngẫu nhiên với đầu vào là security parameter và sinh ra một khóa bí mật . Tiếp theo là thuật toán mã hóa với đầu vào là cặp bản rõ - khóa bí mật và output ra ciphertext . Cuối cùng là thuật toán giải mã với đầu vào là và output ra bản rõ ban đầu.
Cả 3 phải đảm bảo tính chất correctness tức là với mỗi key được sinh ra bởi thì
Thuật toán diễn ra như sau:
- KeyGen : Tính và khóa bí mật sẽ là
là một vector có độ dài là trên .
- Encryption : Message space của ta sẽ là . Mỗi messages sẽ được chuyển về dạng bit và mã hóa từng bit. Ciphertext cho message sẽ là
trong đó và
- Decryption : Output 0 nếu như
ngược lại output 1.
Dual lattice Attack
Nhắc lại: Mỗi lưới có một lưới đối ngẫu (dual lattice), kí hiệu .
Nói cách khác, một lưới đối ngẫu của lưới là tập hợp các điểm trong không gian sinh bởi sao cho tích trong của nó với bất kì điểm nào trong cũng là một số nguyên.
Trên thực tế, ta sẽ tìm cách khôi phục lại giá trị bí mật từ nhiều cặp giá trị . Trong đó
Các bộ số này sẽ được gom thành một ma trận và viết lại dưới dạng hệ phương trình tuyến tính trên modulo , trong đó có các hàng là các vector , là một vector cột mà mỗi entry là các giá trị . Tương tự với .
Xét một dual lattice được scaled bởi như sau:
Bài toán giải tìm một vector ngắn trong tương đương với giải bài toán SIS (Shortest Integer Solution) trong lattice .
Bài toán SIS là bài toán được phát biểu như sau:
SIS. Cho và ma trận . Với , tìm một vector thỏa mãn sao cho
Primal Lattice Attack (uSVP)
uSVP là bài toán tìm vector nhỏ nhất và duy nhất trong lưới.
Ta sẽ dựa vào ý tưởng của bài toán này để phá hệ mật LWE.
Đầu tiên ta phát biểu lại bài toán: Với thỏa mãn , ta biết rằng có một vector sao cho là nhỏ.
Nói cách khác ta xác định được rằng có một vector ngắn (target vector) trong mạng được sinh ra bởi q-ary lattice sau:
Giả sử error vector của ta đủ nhỏ thì ta có thể thử tìm lại nó bằng cách xét tổ hợp tuyến tính
Bây giờ ta sẽ đi cụ thể vào cách xây dựng lattice để giải bài toán trên.
Ta đưa ma trận về dạng bậc thang rút gọn với và .
Sau đó xây dựng một ma trận khối như sau:
Implement:
from sage.all import *
import random
q = 37
assert is_prime(q)
F = GF(q)
n = 5
m = n**2
A = Matrix(GF(q),m,n,lambda i,j: randint(0,q-1))
e = vector((0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0),GF(q))
V = VectorSpace(GF(q),n)
S = V.random_element()
print(f"secret key={S}")
b = (A*S) + e
def flatter(M):
import re
from subprocess import check_output
# compile https://github.com/keeganryan/flatter and put it in $PATH
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), list(map(int, re.findall(b"-?\\d+", ret))))
def primal_attack(A,b,fll = False):
m = A.nrows()
n = A.ncols()
A_ = matrix(ZZ, A.T.rref().submatrix(0,n,n,m-n))
b_zz = [int(x) for x in b]
I_n = identity_matrix(ZZ,n)
qAry = q*identity_matrix(ZZ,m-n)
zero_mn_n = zero_matrix(ZZ, m-n, n)
zero_n_1 = zero_matrix(ZZ, n, 1)
zero_mn_1 = zero_matrix(ZZ, m-n, 1)
c1 = matrix(ZZ, 1, n, b_zz[:n])
c2 = matrix(ZZ, 1, m-n, b_zz[n:])
t = matrix(ZZ, 1, 1, [1])
B = block_matrix([
[I_n, A_, zero_n_1],
[zero_mn_n, qAry, zero_mn_1],
[c1,c2,t]
])
if fll == True:
L = flatter(B)
else:
L = B.LLL()
return L
L = primal_attack(A,b, fll = True)
print(L[0][:-1] == e)
S_ = A.solve_right(b-e)
print(f"recover secret key = {S_}")Thực hành
LWE High Bits Message
Source code của bài
# dimension
n = 64
# plaintext modulus
p = 257
# ciphertext modulus
q = 0x10001
# bound for error term
error_bound = int(floor((q/p)/2))
# message scaling factor
delta = int(round(q/p))
V = VectorSpace(GF(q), n)
S = V.random_element()
print("S = ", S, "\n")
m = ?
A = V.random_element()
error = randint(-error_bound, error_bound)
b = A * S + m * delta + error
print("A = ", A)
print("b = ", b)File output.txt mọi người có thể lên trên trang của CryptoHack để theo dõi.
Phân tích bài: Đầu tiên, hệ mật dựa trên LWE mà ta vừa trình bày sẽ thực hiện mã hóa trên từng bit hoặc của plaintext còn đối với bài trên thì nó sẽ thực hiện mã khóa trên không gian plaintext lớn hơn.
Thuật toán đầy đủ của nó sẽ trông như sau:

Trong đó .
Mình sẽ giải thích lại thuật toán trên như sau:
- Các tham số cho trước:
- Một không gian vector có kích thước là
- Ciphertext modulo
- Plaintext modulo , đồng nghĩa với việc ta chỉ có thể mã hóa các message có kích thước nhỏ hơn
- Scaling factor
- Key - Gen: Tương tự với hệ mật LWE truyền thống thì secret key cũng là một vector trên .
- Mã hóa:
Ciphertext sẽ là một cặp giá trị với đầu vào là .
Trong đó là một phần tử ngẫu nhiên thuộc . Error-term sẽ nằm trong khoảng và . - Giải mã:
Để giải mã thì ta sẽ lấy và tính . Trong Python có sẵn hàm round để làm việc này:
Sau khi thực hiện phép trừ thì ta có thể coi như xấp xỉ với nếu như không tính thêm modulo .
Khi ta chia lấy phần nguyên thì ta mong muốn có được
Với thì cho nên ta có được .
Câu hỏi tiếp theo là tại sao ta cần phải chọn tham số thỏa mãn
from sage.all import *
S = (55542, 19411, 34770, 6739, 63198, 63821, 5900, 32164, 51223, 38979, 24459, 10936, 17256, 20215, 35814, 42905, 53656, 17000, 1834, 51682, 43780, 22391, 33012, 61667, 37447, 16404, 58991, 61772, 44888, 43199, 32039, 26885, 17206, 62186, 58387, 57048, 38393, 29306, 58001, 57199, 33472, 56572, 53429, 62593, 14134, 40522, 25106, 34325, 37646, 43688, 14259, 24197, 33427, 43977, 18322, 38877, 55093, 12466, 16869, 25413, 54773, 59532, 62694, 13948)
A = (13759, 12750, 38163, 63722, 39130, 22935, 58866, 48803, 15933, 64995, 60517, 64302, 42432, 32000, 22058, 58123, 53993, 33790, 35783, 61333, 53431, 43016, 60795, 25781, 28091, 11212, 64592, 11385, 24690, 40658, 35307, 63583, 60365, 60359, 32568, 35417, 22078, 38207, 16331, 53636, 28734, 30436, 18170, 15939, 966, 48519, 41621, 36371, 41836, 4026, 33536, 57062, 52428, 59850, 476, 43354, 61614, 32243, 42518, 19733, 63464, 29357, 56039, 15013)
b = 44007
n = 64
p = 257
q = 0x10001
error_bound = int(floor((q/p)/2))
delta = int(round(q/p))
V = VectorSpace(GF(q), n)
S = V(S)
A = V(A)
x = int((b - A*S )% q)
m = int(round(x/delta))
print(m)LWE Low Bits Message
Source code:
# dimension
n = 64
# plaintext modulus
p = 257
# ciphertext modulus
q = 0x10001
# bound for error term
error_bound = int(floor((q/p)/2))
V = VectorSpace(GF(q), n)
S = V.random_element()
print("S = ", S, "\n")
m = ?
A = V.random_element()
error = randint(-error_bound, error_bound)
b = A * S + error * p + m
print("A = ", A)
print("b = ", b)
Code giải:
from sage.all import *
S = (10082, 48747, 17960, 55638, 37012, 51876, 10128, 37750, 7608, 58952, 33296, 25463, 38900, 85, 65248, 42153, 44966, 31594, 40676, 56828, 30325, 38502, 65083, 7497, 2667, 54022, 24029, 32162, 57267, 12253, 6668, 5181, 14906, 51655, 61347, 4722, 22227, 23606, 63183, 52860, 1670, 31085, 42633, 47197, 7255, 16150, 9574, 62956, 26742, 57998, 49467, 31224, 60073, 12730, 41419, 41042, 53032, 16339, 32913, 16351, 34283, 47845, 3617, 35718)
A = (53751, 21252, 55954, 16345, 60990, 2822, 56279, 37048, 36153, 52141, 2121, 56565, 48112, 43755, 12951, 22539, 29478, 28421, 62175, 10265, 36378, 21305, 42402, 26359, 939, 60690, 1161, 65097, 34505, 19777, 29652, 42868, 49148, 38296, 31916, 25606, 18700, 12655, 35631, 64674, 29018, 21021, 14865, 40196, 14036, 40278, 37209, 35585, 34344, 33030, 285, 58536, 56121, 40899, 24262, 62326, 57433, 5765, 24456, 28859, 45170, 14799, 21567, 55484)
b = 11507
n = 64
p = 257
q = 0x10001
error_bound = int(floor((q/p)/2))
V = VectorSpace(GF(q), n)
A = V(A)
S = V(S)
x = b - (A*S)
def symmetric_mod(x, m):
return int((x + m + m // 2) % m) - int(m // 2)
x = symmetric_mod(x,q)
m = int((x%p))
print(m)From Private to Public Key LWE
Noise Free
Source code của bài:
from utils import listener
from sage.all import *
FLAG = b"crypto{????????????????????????}"
# dimension
n = 64
# plaintext modulus
p = 257
# ciphertext modulus
q = 0x10001
V = VectorSpace(GF(q), n)
S = V.random_element()
def encrypt(m):
A = V.random_element()
b = A * S + m
return A, b
class Challenge:
def __init__(self):
self.before_input = "Would you like to encrypt your own message, or see an encryption of a character in the flag?\n"
def challenge(self, your_input):
if 'option' not in your_input:
return {'error': 'You must specify an option'}
if your_input['option'] == 'get_flag':
if "index" not in your_input:
return {"error": "You must provide an index"}
self.exit = True
index = int(your_input["index"])
if index < 0 or index >= len(FLAG) :
return {"error": f"index must be between 0 and {len(FLAG) - 1}"}
self.exit = True
A, b = encrypt(FLAG[index])
return {"A": str(list(A)), "b": str(int(b))}
elif your_input['option'] == 'encrypt':
if "message" not in your_input:
return {"error": "You must provide a message"}
self.exit = True
message = int(your_input["message"])
if message < 0 or message >= p:
return {"error": f"message must be between 0 and {p - 1}"}
self.exit = True
A, b = encrypt(message)
return {"A": str(list(A)), "b": str(int(b))}
return {'error': 'Unknown action'}
import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener
listener.start_server(port=13411)Đối với bài này thì hàm encrypt không cộng thêm errors vào
def encrypt(m):
A = V.random_element()
b = A * S + m
return A, bCho nên ta có 2 cách để lấy lại được secret key và giải flag. Cách đầu tiên là từ việc ta biết trước prefix của flag là crypto{ cho nên ta có thể gọi get_flag(idx) để lấy
Cách thứ hai là gọi server trả về ciphertext của là được.
Solve script:
from pwn import *
from sage.all import *
import json
context.log_level = 'debug'
import ast
r = remote("socket.cryptohack.org",13411)
n = 64
p = 257
q = 0x10001
print(r.recvline())
def send_json(io, obj):
io.sendline(json.dumps(obj).encode())
def recv_json(io):
return json.loads(io.recvline().decode())
V = VectorSpace(GF(q), n)
# S = V.random_element()
# def encrypt(m):
# A = V.random_element()
# b = A * S + m
# return A, b
def get_flag(idx):
send_json(r,{"option":"get_flag","index":str(idx)})
data = recv_json(r)
return data["A"], data["b"]
rows = []
rhs = []
for _ in range(n):
A, b = get_flag(0)
A, b= ast.literal_eval(A), int(b) - 99
A_, b_ = vector(GF(q),A), GF(q)(b)
rows.append(A_)
rhs.append(b_)
F = GF(q)
A_mat = Matrix(F, rows) # n x n
b_vec = vector(F, rhs)
S = A_mat.solve_right(b_vec)
print(S)
def decrypt(A_list,b_int,S):
F = GF(q)
A = vector(F,A_list)
b = F(b_int)
v = int(b-A*S) % q
assert 0<=v<=256
return v
FLAG = b"crypto{????????????????????????}"
m = len(FLAG)
flag = b""
for _ in range(m):
A, b = get_flag(_)
char = decrypt(A,b,S)
flag+=chr(char).encode()
print(flag)
# crypto{linear_algebra_is_useful} Bounded Noise
import random
import json
FLAG = b"crypto{?????????????????????????????????????????}"
def keygen(secret, q, erange=range(2)):
n, m = len(secret), len(secret) ** 2
A = Matrix(GF(q), m, n, lambda i, j: randint(0, q - 1))
e = vector(random.choices(erange, k=m), GF(q))
b = (A * secret) + e
return A, b, e
flag_int = int.from_bytes(FLAG, "big")
q = 0x10001
secret_key = []
while flag_int:
secret_key.append(flag_int % q)
flag_int //= q
secret_key = vector(GF(q), secret_key)
A, b, e = keygen(secret_key, q)
with open("output.txt", "w") as f:
json.dump({"A": str(list(A)), "b": str(b)}, f)Bài này có 2 cách giải.
Cách đầu tiên là dùng Arora–Ge attack và cách thứ hai là dùng LLL. Ở đây mình sẽ trình bày cách sử dụng LLL để thuận tiện làm cho các bài sau.
Có 2 attack chính đối với các hệ mật dựa trên LWE đó là Primal attack và Dual attack.
Đối với bài này thì nhưng khá nhỏ, do nó chỉ có 2 giá trị là hoặc . Secret key của ta là các hệ số của flag trong cơ số .
Ta có thể xem xét các quan hệ sau
Bỏ đi modulo bằng cách thêm vào các hệ số như sau
Ta xét một lattice như sau:
Với tổ hợp tuyến tính thì ta kì vọng sẽ sinh ra một vector ngắn trong lưới đó là . Ta sẽ giải bài toán CVP với target vector là . Sau khi có thì ta sẽ giải tìm lại thỏa mãn .
Ý tưởng là như vậy còn bây giờ mình sẽ thử code xem sao.
from sage.all import *
import ast
import json
from sage.modules.free_module_integer import IntegerLattice
from pwn import *
context.log_level = "debug"
with open("/home/duccorp/CTF/CryptoHack/lattice/lwe/output.txt") as f:
data = json.load(f)
FLAG = b"crypto{?????????????????????????????????????????}"
def flatter(M):
import re
from subprocess import check_output
# compile https://github.com/keeganryan/flatter and put it in $PATH
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), list(map(int, re.findall(b"-?\\d+", ret))))
def Babai_closest_vector(B, target):
# Babai's Nearest Plane algorithm
M = B.LLL()
G = M.gram_schmidt()[0]
small = target
for _ in range(1):
for i in reversed(range(M.nrows())):
c = ((small * G[i]) / (G[i] * G[i])).round()
small -= M[i] * c
return target - small
A = ast.literal_eval(data["A"])
b = ast.literal_eval(data["b"])
q = 0x10001
print(A,"\n")
print(b,"\n")
F = GF(q)
A_mat = Matrix(ZZ,A)
b_vec = vector(ZZ, b)
m = A_mat.nrows()
n = A_mat.ncols()
print(m)
print(n)
B = block_matrix([[A_mat,q*identity_matrix(ZZ,m)]])
B_ = B.transpose()
print(f"Doing LLL")
L = IntegerLattice(B_, lll_reduce=False)
print(f"Done LLL")
B_reduction = L.BKZ(block_size = 20)
print(f"Done BKZ")
res = Babai_closest_vector(B_reduction, b_vec)
print("Closest Vector: {}".format(res))Và nó chạy rất lâu =))). Lí do là vì ma trận của ta có kích thước khá là khủng
Một cách khác đó là xài Primal Attack như sau:
from sage.all import *
import random
import json
import ast
def flatter(M):
import re
from subprocess import check_output
# compile https://github.com/keeganryan/flatter and put it in $PATH
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), list(map(int, re.findall(b"-?\\d+", ret))))
def primal_attack(A,b,fll = False):
m = A.nrows()
n = A.ncols()
A_ = matrix(ZZ, A.T.rref().submatrix(0,n,n,m-n))
print(f"building first block ok")
b_zz = [int(x) for x in b]
I_n = identity_matrix(ZZ,n)
qAry = q*identity_matrix(ZZ,m-n)
zero_mn_n = zero_matrix(ZZ, m-n, n)
zero_n_1 = zero_matrix(ZZ, n, 1)
zero_mn_1 = zero_matrix(ZZ, m-n, 1)
c1 = matrix(ZZ, 1, n, b_zz[:n])
c2 = matrix(ZZ, 1, m-n, b_zz[n:])
t = matrix(ZZ, 1, 1, [1])
B = block_matrix([
[I_n, A_, zero_n_1],
[zero_mn_n, qAry, zero_mn_1],
[c1,c2,t]
])
print(f"done building block")
if fll == True:
L = flatter(B)
else:
L = B.LLL()
print(f"done LLL")
return L
q = 0x10001
F = GF(q)
with open("/home/duccorp/CTF/CryptoHack/lattice/lwe/output.txt") as f:
data = json.load(f)
A = ast.literal_eval(data["A"])
b = ast.literal_eval(data["b"])
A_mat = Matrix(GF(q), A)
b_vec = vector(GF(q), b)
# l = primal_attack(A_mat,b_vec)
# print(l)
# errors = []
# for row in l:
# e_cand = row[:-1]
# norm = e_cand.norm()
# if norm == 0:
# continue
# if all(0<=abs(x)<=3 for x in e_cand):
# errors.append(e_cand)
# print(errors)
e = vector((0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1),GF(q))
secret = matrix(F, A_mat).solve_right(vector(F, b_vec) - vector(F, e))
print(secret)
# (8590, 6465, 17312, 58487, 33027, 37469, 18625, 24033, 57830, 22541, 59008, 51941, 8396, 59708, 60960, 30756, 329, 52261, 21735, 5440, 61891, 54608, 37567, 26919, 99)
from Crypto.Util.number import long_to_bytes
flag = 0
for rem in secret[::-1]:
flag *= q
flag += int(rem)
print(long_to_bytes(flag))Do bài đã quá dài rồi nên mình xin dừng ở đây. Những attacks còn lại mình sẽ viết trong bài tiếp theo.
Lattice Methods - Part 2
Tài liệu tham khảo
[1] Abstract Algebra: Theory and Applications ,Thomas W. Judson, Stephen F. Austin, State University, August 11, 2012
[2] Predicting Lattice Reduction, Nicolas Gama and Phong Q.Nguyen
[3] NGHIÊN CỨU MỘT SỐ HỆ MẬT LATTICE TRONG HỌ MÃ HÓA ĐỒNG CẤU ĐẦY ĐỦ, Khuất Thanh Sơn, Nguyễn Trường Thắng, Lê Phê Đô, Bùi Trọng A Đam
[4] Giáo trình đại số tuyến tính - Ngô Việt Trung, Bộ sách cao học, Viện toán học.
[5] Determinants and Volumes
[6] https://github.com/Giapppp/Lattice-Based-Cryptography
[7] A Gentle Tutorial for Lattice-Based Cryptanalysis, https://eprint.iacr.org/2023/032.pdf
[8] A Course in Computational Algebraic Number Theory, Henri Cohen, Springer
[9] https://magicfrank00.github.io/writeups/posts/lll-to-solve-linear-equations/
[10] https://ur4ndom.dev/static/files/latticetraining/practical_lattice_reductions.pdf
[11] Algorithms for the Approximate Common Divisor Problem
[12] The Learning with Errors Problem - Oded Regev
[13] Attacks on LWE
[14] A Complete Analysis of the BKZ Lattice Reduction Algorithm
[15] A Brief Introduction to Techniques for Solving Lattice-based Quantum-Safe Schemes
[16] Lattice for beginners