Có một nghịch lý trong bảo mật DeFi mà ít ai dám nói thẳng: Một giao thức càng được audit kỹ, càng dễ sinh ra những lỗ hổng chết người mà không auditor nào nhìn thấy.
Năm 2017, tôi 35 tuổi, ngồi lọ mọ với codebase 0x Protocol v2. Đó là thời kỳ ICO điên cuồng, ai cũng muốn ra token kiếm tiền nhanh. 0x, với tham vọng trở thành lớp thanh khoản cho toàn bộ Ethereum, là một trong số ít dự án có code thực sự nghiêm túc. Tôi dành ba tháng chỉ để đọc. Ròng rã. 12 tiếng một ngày.
Tôi tìm ra 7 lỗ hổng. 6 cái đầu là những bug 'kinh điển' — overflow, access control lỏng lẻo, reentrancy phụ. Dự án fix nhanh, cảm ơn, xong. Chuyện bình thường với một auditor kỳ cựu.

Nhưng cái thứ 7... nó không phải là một 'lỗi' theo nghĩa thông thường. Nó là một lỗi trong thiết kế triết học của giao thức. Một điểm mù trong logic kinh doanh được mã hóa.

Đây là bài viết về cái lỗ hổng thứ 7 đó. Và tại sao nó dạy tôi rằng, trong thế giới zero-knowledge và DeFi hiện đại, những kẻ tấn công nguy hiểm nhất không khai thác bug code — chúng khai thác bug trong tư duy của người tạo ra code.
Context: Khi 'không cần tin tưởng' trở thành 'mù quáng'
0x Protocol v2 về cơ bản là một sàn giao dịch peer-to-peer trên chain. Người dùng tạo order (lệnh), ký nó off-chain, gửi cho 'relayer' (người trung gian). Relayer tìm người mua phù hợp, rồi submit order đó lên smart contract để khớp lệnh.
Cốt lõi của thiết kế này là off-chain order book + on-chain settlement. Nó hiệu quả, tiết kiệm gas, và quan trọng nhất, nó được quảng cáo là 'không cần tin tưởng' (trustless) — bạn không cần tin relayer, vì order đã được ký và có thể kiểm tra.
Nghe có vẻ perfect.
Nhưng vấn đề nằm ở một chi tiết đơn giản, nằm gọn trong một function nhỏ tên fillOrder. Function này nhận vào một bytes memory signature và uint256 takerAssetFillAmount. Nó kiểm tra chữ ký, kiểm tra số dư, rồi chuyển token.
Đoạn code dưới đây, tôi vẫn nhớ như in, là thứ khiến tôi phải dừng lại:
function fillOrder(
Order memory order,
bytes memory signature,
uint256 takerAssetFillAmount
)
public
returns (uint256)
{