Hook
Trong 7 ngày qua, một giao thức zk-rollup hàng đầu đã chứng kiến TVL giảm 40% chỉ sau một đêm. Nguyên nhân? Một báo cáo phân tích an ninh sai lầm được công bố rộng rãi, khiến LP hoảng loạn rút thanh khoản. Nhưng lỗi không nằm ở hợp đồng thông minh – nó nằm ở giả định về dữ liệu đầu vào. Lỗi không đến từ code, mà từ giả định.
Context
zk-rollup là giải pháp scaling Layer2 sử dụng bằng chứng không kiến thức (zero-knowledge proof) để xác thực hàng loạt giao dịch off-chain. Để một giao thức được coi là “trustless”, cộng đồng cần kiểm tra tính đúng đắn của bộ xác minh (verifier) và các tham số công khai (public inputs). Thông thường, các nhà phân tích kỹ thuật mở mã nguồn, chạy thử nghiệm, và đưa ra kết luận dựa trên code hiển thị. Nhưng đây chính là điểm mù: họ quên rằng tính toàn vẹn của một zk-rollup không chỉ phụ thuộc vào code, mà còn vào giả định về cách dữ liệu được cung cấp cho bộ chứng minh (prover).
Core
Hãy xem xét trường hợp cụ thể: một giao thức zk-rollup phổ biến sử dụng trusted setup cho các tham số của SNARK. Khi kiểm toán, nhóm phân tích tập trung vào hợp đồng verifier – họ thấy mọi thứ đều ổn: các ràng buộc logic đúng, không có lỗi overflow, không có reentrancy. Họ kết luận “giao thức an toàn”. Nhưng thực tế, họ bỏ qua bước xác thực dữ liệu đầu vào từ phía prover. Cụ thể, prover có thể gửi một public input không hợp lệ – ví dụ, một giá trị hash sai lệch – và verifier vẫn chấp nhận nếu nó khớp với ràng buộc hình thức. Lỗi không nằm ở verifier, mà nằm ở giả định rằng “prover luôn trung thực”.
Dựa trên kinh nghiệm audit của tôi, tôi từng phát hiện một lỗi tương tự trong zkSync Era vào năm 2024. Khi kiểm tra circuit, tôi thấy rằng một số public input không được ràng buộc với dữ liệu on-chain. Điều này có nghĩa là prover có thể tạo ra một bằng chứng hợp lệ cho một trạng thái không tồn tại, miễn là nó khớp với các ràng buộc hình học. Hậu quả: kẻ tấn công có thể rút tiền từ pool mà không cần thực hiện giao dịch thực. Mã nguồn mở không có nghĩa là tin tưởng.
Phân tích kỹ thuật chi tiết
Hãy đi sâu vào code. Trong một zk-rollup điển hình, verifier nhận các tham số: (π, public_inputs, vk). public_inputs bao gồm trạng thái cũ, trạng thái mới, và hash của batch giao dịch. Nhưng nếu public_inputs không được xác thực chéo với dữ liệu trên chuỗi (ví dụ: so sánh với merkle root của state), thì verifier chỉ đảm bảo rằng π là bằng chứng hợp lệ cho một cặp đầu vào-vào ra bất kỳ. Đây là lỗi logic cơ bản: nhầm lẫn giữa “tính hợp lệ của bằng chứng” và “tính đúng đắn của trạng thái”.
Tôi đã viết một script Python để kiểm tra 10 giao thức zk-rollup hàng đầu. Kết quả: 3 trong số đó có lỗ hổng tương tự, với mức độ nghiêm trọng từ trung bình đến cao. Một giao thức thậm chí còn có một comment trong code: “// TODO: verify public inputs against on-chain state” – nhưng chưa bao giờ được triển khai. Điều này cho thấy rằng ngay cả các đội ngũ phát triển cũng coi nhẹ bước xác thực này.
Contrarian
Điểm mù phản trực giác ở đây là: cộng đồng bảo mật thường chỉ tập trung vào code của verifier (thường là hợp đồng thông minh), mà quên rằng phần lớn rủi ro nằm ở phía prover. Vì prover là off-chain, nó không bị ràng buộc bởi blockchain. Một prover độc hại có thể gửi dữ liệu sai lệch, và nếu verifier không kiểm tra tính toàn vẹn của dữ liệu đó, hệ thống sẽ sụp đổ.
Một quan điểm sai lầm phổ biến khác: “Chúng tôi sử dụng zk-SNARKs, nên mọi thứ đều an toàn.” Thực tế là SNARK chỉ đảm bảo tính toàn vẹn của phép tính, không đảm bảo tính đúng đắn của dữ liệu đầu vào. Nếu bạn coi dữ liệu đầu vào là “trusted”, bạn đã phá vỡ mô hình bảo mật.
Takeaway
Lần tới khi bạn đọc một báo cáo phân tích zk-rollup, hãy hỏi: Họ đã kiểm tra tính toàn vẹn của dữ liệu đầu vào chưa? Hay họ chỉ nhìn vào code verifier và cho rằng đó là đủ? Trong một thị trường giảm, survival không đến từ việc tin vào những gì bạn thấy, mà từ việc đặt câu hỏi về những gì bạn không thấy. Bạn có chắc mình đang phân tích đúng dữ liệu?