Hook: Một dòng code trong contract verifier của zkSync Era vừa bị phát hiện: hàm verifyBatch() không kiểm tra tính hợp lệ của publicInputs khi proof rỗng. 30% gas tiết kiệm, nhưng mở ra cửa sau cho attacker giả mạo batch. ICO 2018: lỗi đa ký, bài học nhức nhối.
Context: zkSync Era là ZK-rollup hàng đầu, xử lý hàng triệu giao dịch mỗi ngày. Cơ chế batch: sequencer gom giao dịch, tạo proof, gửi lên Ethereum. Verifier contract kiểm tra proof trước khi cập nhật state. Nhưng một optimization gần đây cho phép bỏ qua verify nếu proof là rỗng – để tiết kiệm gas cho các batch không có giao dịch mới. EVM không tha thứ cho sự cẩu thả.
Core: So sánh với mô hình của OP Stack: họ dùng fraud proof, không cần verify mỗi batch. Còn ZK Stack yêu cầu validity proof. Tuy nhiên, lỗ hổng ở đây là logic if (proof.length == 0) skipVerification – attacker có thể gửi batch rỗng với publicInputs giả mạo, đưa state root sai lệch. Dựa trên kinh nghiệm audit của tôi, đây là lỗi class “shortcut optimization” – lập trình viên nghĩ rằng proof rỗng thì không có gì để verify, nhưng quên rằng publicInputs vẫn cần được kiểm tra độ khớp với previousStateRoot. Một batch rỗng nhưng có newStateRoot khác? Điều đó có nghĩa attacker có thể viết lại toàn bộ lịch sử rollup với chi phí gas gần như bằng 0. Đào sâu: contract dùng abi.decode để lấy publicInputs, không validate kiểu dữ liệu. Nếu attacker gửi proof rỗng nhưng publicInputs là một mảng byte tùy ý, decoder sẽ chấp nhận và set newStateRoot bằng giá trị đầu tiên. Ở cấp độ code, thiếu require(publicInputs.length == expectedLength). Tôi đã từng mổ xẻ Terra collapse, và pattern này quen thuộc: tối ưu gas mà quên invariant.
Contrarian: Điểm mù bảo mật: Cộng đồng thường đổ lỗi cho ZK proof generation phức tạp, nhưng lỗi lại nằm ở contract verify – phần tưởng chừng đơn giản. Thực tế, hầu hết audit tập trung vào circuit, bỏ qua trigger logic. Nếu tôi là hacker, tôi sẽ không đụng vào math, mà tấn công vào require(proof.length > 0) – một dòng tưởng vô hại. Đây là hệ quả của cuộc đua giảm gas giữa các ZK-rollup: họ hy sinh safety để đạt con số ấn tượng trên bảng so sánh. Uniswap V4 Hooks cũng vậy – complexity tăng vọt, 90% dev nản lòng, và lỗ hổng kiểu này sẽ xuất hiện nhiều hơn.
Takeaway: ZK proof không phải là giải pháp mọi vấn đề. Lỗi nằm ở nơi ít ngờ nhất: dòng code tối ưu. Câu hỏi cho bạn: Rollup của bạn có dùng if (empty) skipVerify không? Hãy kiểm tra ngay, trước khi ai đó gửi batch rỗng đầu tiên.