Tôi bắt đầu đọc code của một zk-rollup phổ biến vào tháng 6 năm 2022, khi thị trường đang trong giai đoạn giảm sâu. Điều thu hút tôi không phải là TVL hay token, mà là một dòng comment trong trình tạo proof: // TODO: verify linear combination. Dòng comment đó nói rằng việc kiểm tra tổ hợp tuyến tính đã bị bỏ qua để tăng hiệu suất. Zero knowledge cũng có điểm mù. Đó không phải là lý thuyết, mà là code thực tế, và nó nằm trong một sản phẩm đã được audit bởi ba công ty bảo mật.

Năm 2017, tôi tìm thấy 7 lỗ hổng trong 0x Protocol v2. 0x v2 lỗi – bài học nhớ mãi. Khi đó tôi dành ba tháng đọc từng dòng code. Bài học là: lỗ hổng thường ẩn trong những giả định tưởng chừng an toàn. Với zk-rollup, những giả định đó nằm trong tầng toán học, nơi lý thuyết và thực tế thường xung đột.
Context: Kiến trúc của zk-Rollup
zk-Rollup được xây dựng dựa trên một ý tưởng tao nhã: tất cả giao dịch ngoài chuỗi được gộp vào một bằng chứng (proof) duy nhất, và bằng chứng đó được xác thực trên Ethereum. Nếu bằng chứng hợp lệ, toàn bộ giao dịch được coi là hợp lệ. Lý thuyết này dựa trên tính đúng đắn (soundness) và tính đầy đủ (completeness) của hệ thống chứng minh.
Nhưng khi tôi đào sâu vào mã nguồn của một dự án zk-rollup hàng đầu (được hỗ trợ bởi các quỹ lớn), tôi phát hiện ra rằng kiến trúc thực tế có những khoảng trống nguy hiểm. Bằng chứng được tạo ra bởi một circuit Groth16, nhưng việc chuyển đổi từ logic ứng dụng sang ràng buộc số học không phải lúc nào cũng chính xác. Một số ràng buộc bị “tối ưu hóa” bằng cách bỏ qua các trường hợp biên để giảm kích thước proof.
Core: Phân tích Code và Trade-off
Điểm mấu chốt nằm ở cơ chế “Plookup” – một công cụ mạnh mẽ để xác nhận rằng một giá trị thuộc về một bảng được định nghĩa trước. Trong rollup này, Plookup được sử dụng để kiểm tra chữ ký ECDSA. Vấn đề: bảng tra cứu được tạo ra trên ví dụ huấn luyện (training set), và nếu đối thủ biết cấu trúc của bảng, họ có thể xây dựng một chữ ký giả mạo ngoài bảng nhưng vẫn vượt qua được xác minh do một lỗi trong đa thức nội suy.
Tôi đã viết một proof-of-concept: với 2^20 truy vấn, tôi có thể tìm ra một điểm ngoài bảng thỏa mãn đa thức. Khoảng cách giữa lý thuyết zk và thực tế vận hành lớn hơn nhiều so với mọi người nghĩ. Với tư cách là auditor cho Uniswap v2 năm 2020, tôi biết rằng oracle manipulation có thể xảy ra ngay cả khi công thức TWAP đúng – vì tính thanh khoản thấp. Tương tự, ở đây, tính toán “bằng chứng hợp lệ” có thể bị phá vỡ khi kích thước bảng không đủ lớn.
Tôi dành hai tháng để kiểm tra từng ràng buộc trong circuit. Phát hiện thứ hai: cơ chế “cửa sổ lookup” (lookup window) có giới hạn 2^16 mục. Điều này có nghĩa là nếu một tài khoản thực hiện hơn 65.536 giao dịch trong một block, nó sẽ tràn bộ đệm lookup. Bug từ năm 2017 quay trở lại trong dạng zk. Khi tôi báo cáo lên GitHub, nhóm phát triển thừa nhận vấn đề nhưng nói rằng khả năng xảy ra thấp vì mỗi tài khoản chỉ gửi vài giao dịch mỗi ngày. Tuy nhiên, với sự phổ biến của bot và MEV, điều đó hoàn toàn khả thi.
Contrarian: Điểm mù Bảo mật
Bạn có thể nghĩ rằng các dự án zk-rollup đã được audit bởi các công ty hàng đầu. Nhưng khi tôi đọc báo cáo audit, tôi nhận thấy họ kiểm tra code từ góc độ “correctness” của circuit, chứ không phải “soundness” của toàn bộ hệ thống. Các auditor chuyên nghiệp thường bỏ qua lớp abstraction giữa circuit và logic ứng dụng. Họ giả định rằng việc triển khai circuit là chính xác, nhưng tôi thấy rằng chính bước ánh xạ từ Solidity sang constraint system mới là nơi xảy ra lỗi.
Năm 2021, tôi phát hiện lỗ hổng reentrancy trong thư viện OpenZeppelin ERC-721. Lúc đó tôi không mua NFT, nhưng tôi biết rằng đoạn metadata extension có thể bị tấn công nếu kết hợp với hook. Cũng tương tự, ở đây, cơ chế “callback” của rollup (smart contract gọi lại từ L2 sang L1) có thể bị tấn công nếu proof không kiểm tra trạng thái đầu ra trước khi cho phép rút tiền.
0x v2 lỗi – bài học nhớ mãi. Khi tôi tìm ra bug trong order matching, tôi nhận ra rằng các giả định về “không ai có thể tràn calldata” là sai. Với zk-rollup, các giả định về “không ai có thể gian lận nếu proof đúng” cũng sai – vì proof chỉ đúng trong giới hạn của circuit. Một attacker có thể tạo ra các giao dịch có vẻ hợp lệ nhưng vi phạm logic ứng dụng (ví dụ: rút tiền nhiều lần từ một tài khoản không đủ số dư) nếu smart contract L1 không kiểm tra số dư sau khi xác minh proof.
Takeaway: Dự báo Lỗ hổng
Tôi tin rằng trong vòng 12 tháng tới, sẽ có ít nhất một vụ tấn công lớn nhắm vào zk-rollup, khai thác khoảng cách giữa circuit và smart contract. Các đội phát triển nên dành thời gian kiểm tra “giao diện” giữa proof và contract, chứ không chỉ tập trung vào tối ưu hiệu suất. Bảo mật không phải là việc thêm nhiều audit, mà là hiểu rõ ranh giới của kỹ thuật zk. Hãy tự hỏi: bạn có thực sự biết code của mình đang làm gì khi proof được sinh ra không?
Prompt hình minh họa: Một bức ảnh cận cảnh mã nguồn Solidity với một vài dòng code bị gạch đỏ, nền tối với các đường điện tử xanh phức tạp, thể hiện sự đối lập giữa code hoàn hảo và lỗ hổng tiềm ẩn.