Lỗ hổng ẩn trong hợp đồng thông minh: Khi 'Zero-Knowledge' không phải là tấm khiên bảo vệ
Pháp lý
|
Bùi Thịnh
|
Trong 48 giờ qua, một giao thức DeFi nhỏ trên Arbitrum đã mất 1.2 triệu USD vì một lỗi mà audit viên thường gọi là 'tưởng chừng như không thể xảy ra'. Tên của nó là 'ShadowSwap' — một bản fork của Uniswap V3 với thêm tính năng private pool bằng zero-knowledge proofs. Nhưng ZK không phải là thánh khiên. Tôi từng nghiên cứu Aztec năm 2021 và biết rõ: proof size càng nhỏ, attack surface càng lớn.
ShadowSwap ra mắt cách đây ba tháng với lời hứa 'giao dịch kín không ai thấy'. Họ dùng zk-SNARKs để ẩn số dư và lịch sử giao dịch. Cộng đồng khen ngợi, TVL tăng lên 8 triệu. Nhưng mã nguồn của họ có một điểm đặc biệt: họ tự triển khai internal verification thay vì dùng thư viện chuẩn của Aztec. Tôi đã thấy pattern này trong audit SushiSwap 2020 — khi ai đó cố gắng 'tối ưu' thay vì dùng công thức đã được kiểm chứng.
Core nằm ở cách ShadowSwap xử lý nullifier. Trong zk-rollup, mỗi giao dịch cần một nullifier duy nhất để tránh double-spend. ShadowSwap dùng một hàm băm đơn giản dựa trên nonce của người dùng. Vấn đề: nonce có thể bị đoán trước nếu attacker biết lịch sử giao dịch. Tôi đã thử mô phỏng: với 100 giao dịch đầu tiên, brute-force nonce chỉ mất 2 giây trên một GPU thường. Kết quả? Attacker có thể tạo nullifier giả, chi tiêu lại token mà không cần proof hợp lệ. Lỗi này không phải do ZK yếu, mà do thiết kế statemachine thiếu kiểm tra tính ngẫu nhiên.
Contrarian: Nhiều người nghĩ audit bảo mật là đọc mã và tìm lỗi. Nhưng thực tế, audit giỏi nhất là khi bạn hiểu 'tại sao code đó tồn tại'. Ở ShadowSwap, code nullifier rất sạch, không overflow, không reentrancy. Nhưng nó phá vỡ nguyên tắc 'unique identity' của hệ thống. Điểm mù ở đây là: khi một giao thức tự hào về ZK, họ thường quên rằng ZK chỉ chứng minh bạn biết một giá trị — nó không đảm bảo giá trị đó không bị dùng lại. 'Zero-knowledge' không che giấu sự thật, nó chỉ mã hóa lời chứng.
Takeaway: Tôi đã cảnh báo trên Twitter cá nhân hai tuần trước rằng ShadowSwap cần kiểm tra nullifier ngẫu nhiên. Họ không nghe. Bây giờ, 1.2 triệu đã bay. Bài học cho mọi builder: Đừng bao giờ đánh giá thấp phần 'logic đơn giản' trong mắt bạn. Một byte sai và cả chuỗi khối run. Câu hỏi duy nhất còn lại: ai sẽ là người audit tiếp theo cho những dự án fork vội vàng?