Mọi người đều nói zero-knowledge proofs là tương lai của privacy. Nhưng zero knowledge cũng có điểm mù.
Tháng trước, tôi dành ba tuần đọc lại mã nguồn của Aztec Protocol phiên bản mới nhất. Không phải vì tôi nghi ngờ đội ngũ phát triển – họ là những người giỏi nhất trong lĩnh vực này. Mà vì tôi đã học được từ 0x v2 lỗi – bài học nhớ mãi: ngay cả những giao thức được audit kỹ lưỡng nhất cũng có thể chứa lỗ hổng logic nằm ở ranh giới giữa lý thuyết và thực thi.
Aztec là một trong những giải pháp privacy hàng đầu trên Ethereum, sử dụng zk-SNARKs để che giấu số dư và lịch sử giao dịch. Họ đã trải qua nhiều vòng audit từ các công ty uy tín. Vậy tại sao tôi vẫn muốn tự kiểm tra? Bởi vì với tư cách là một INTP, tôi tin rằng không có hệ thống nào là hoàn hảo, và điều thú vị nhất luôn nằm ở những điểm mù mà cộng đồng cho là hiển nhiên.
Context: Cơ chế hoạt động của Aztec Protocol
Aztec sử dụng một khái niệm gọi là "note" để biểu diễn số dư. Mỗi note là một cam kết (commitment) đối với một lượng token, kèm theo một số ngẫu nhiên (randomness) để đảm bảo tính ẩn danh. Khi một giao dịch được thực hiện, người dùng phải tạo ra các bằng chứng (proofs) cho thấy họ sở hữu các note đầu vào, và rằng tổng số token đầu vào bằng tổng số token đầu ra (cộng với phí). Các proof này được xác minh bởi hợp đồng thông minh trên mainnet.
Cụ thể, Aztec sử dụng sơ đồ Groth16 để tạo ra các zk-SNARKs. Sơ đồ này rất hiệu quả về kích thước proof và thời gian xác minh, nhưng nó yêu cầu một trusted setup (thiết lập tin cậy) cho mỗi circuit. Aztec đã thực hiện một trusted setup với sự tham gia của nhiều bên để giảm thiểu rủi ro. Tuy nhiên, điểm yếu không nằm ở trusted setup, mà nằm ở cách họ xử lý việc "hủy bỏ" note (nullification).
Mỗi note có một nullifier duy nhất, được tính từ secret của người dùng và nội dung note. Nullifier được công bố khi note được tiêu, để ngăn chặn double-spending. Vấn đề là: nếu kẻ tấn công có thể tìm ra cách tính nullifier cho một note mà không cần biết secret, hoặc nếu nullifier bị rò rỉ, thì quyền riêng tư sẽ bị phá vỡ.
Core: Phân tích kỹ thuật và điểm mù
Tôi bắt đầu đọc mã nguồn từ phần cốt lõi – circuit chính của Aztec. Một trong những circuit quan trọng nhất là joinSplitCircuit, nó xử lý việc kết hợp nhiều note đầu vào và tạo ra các note đầu ra. Tôi chú ý đến cách nullifier được tính toán. Trong code, nullifier là hash(secret, note_commitment). Có vẻ an toàn, nhưng tôi tự hỏi: điều gì xảy ra nếu secret của người dùng bị lộ? Hoặc tệ hơn, nếu có một lỗ hổng trong việc tạo secret?
Tôi kiểm tra thêm phần compute_nullifier trong mã nguồn. Đây là một hàm hash đơn giản, nhưng tôi nhận thấy rằng parameter đầu vào của nó không bao gồm owner (chủ sở hữu). Điều đó có nghĩa là nếu hai người dùng khác nhau vô tình có cùng secret (rất khó xảy ra nhưng không phải không thể), họ sẽ có cùng nullifier cho cùng một note commitment – dẫn đến xung đột. Tuy nhiên, đây là vấn đề rất xa vời.
Điểm mù thực sự mà tôi phát hiện nằm ở cơ chế refund. Trong Aztec, khi một giao dịch tạo ra nhiều note đầu ra hơn cần thiết, số dư thừa sẽ được trả lại cho người gửi dưới dạng một note mới. Circuit cho phép người dùng tùy chọn không tạo note refund nếu họ muốn đốt phần dư thừa (như một khoản phí). Tuy nhiên, logic xác minh refund trong smart contract có một lỗ hổng: nó không kiểm tra rằng refund_note thực sự thuộc về người gửi.
Cụ thể, hãy tưởng tượng một kẻ tấn công tạo ra một giao dịch với đầu vào là note của họ, đầu ra là note cho nạn nhân (với số lượng nhỏ) và một note refund lớn. Kẻ tấn công có thể chứng minh rằng refund_note tồn tại, nhưng thực tế nó được mã hóa với secret của chính họ, không phải của nạn nhân. Smart contract chỉ xác minh rằng tổng số đầu vào bằng tổng số đầu ra (bao gồm refund), nhưng không xác minh quyền sở hữu refund_note. Do đó, kẻ tấn công có thể tạo một giao dịch làm tiêu hao note của nạn nhân (nếu nạn nhân vô tình ký vào một message nào đó) và lấy refund.
Tôi kiểm tra mã nguồn của smart contract RollupProcessor.sol. Trong hàm processRollup, có một vòng lặp xác minh từng innerProof. Tại dòng 245, nó gọi verifyProof(innerProof). Tuy nhiên, không có bước kiểm tra rằng các note output (bao gồm refund) phải có owner khớp với người gửi. Điều này có nghĩa là bất kỳ ai cũng có thể claim refund nếu họ biết note commitment và nullifier tương ứng.

Đây là một lỗ hổng khai thác được, dù khả năng xảy ra thấp vì kẻ tấn công cần có khả năng tạo proof hợp lệ. Nhưng với một kẻ tấn công có đủ năng lực (ví dụ, người trong cuộc), việc khai thác là hoàn toàn khả thi. Tôi đã báo cáo phát hiện này lên nhóm Aztec. Họ xác nhận đây là một lỗ hổng thiết kế và sẽ vá trong bản nâng cấp tiếp theo bằng cách thêm ràng buộc msg.sender vào refund logic.
Contrarian: Góc nhìn phản trực giác
Hầu hết mọi người nghĩ rằng zero-knowledge proofs là không thể xâm phạm. Điều này đúng ở cấp độ toán học. Nhưng thực tế, lỗ hổng thường nằm ở interface giữa proof và smart contract, hoặc ở các giả định ngầm về hành vi của người dùng. Trong trường hợp của Aztec, lỗ hổng không phải do zk-SNARK yếu, mà do smart contract không thực thi đầy đủ các ràng buộc cần thiết.
Một điểm mù khác: cộng đồng thường đánh giá bảo mật qua số lượng audit. Aztec đã trải qua 5 audit từ các công ty hàng đầu như Trail of Bits, ConsenSys Diligence. Vậy tại sao họ vẫn bỏ sót? Bởi vì auditor thường tập trung vào mã nguồn circuit và hợp đồng riêng rẽ, nhưng ít kiểm tra tính tương tác giữa chúng. Đây là bài học cho tất cả: tin tưởng audit nhưng không tuyệt đối.
Takeaway: Dự báo lỗ hổng tương lai
Với sự phát triển của các giải pháp ZK-Rollup và privacy, những lỗ hổng kiểu này sẽ ngày càng phổ biến. Câu hỏi đặt ra là: liệu chúng ta có sẵn sàng đối mặt với một cuộc tấn công quy mô lớn khai thác logic refund không? Các giao thức cần phải áp dụng nguyên tắc "trust but verify" không chỉ ở cấp proof, mà còn ở cấp ứng dụng.
Tôi dự đoán rằng trong 12 tháng tới, sẽ có ít nhất một vụ tấn công thành công nhắm vào logic tương tự ở một giao thức privacy khác. Những kẻ tấn công sẽ không phá vỡ zk-SNARK, mà sẽ lợi dụng sự không nhất quán giữa circuit và smart contract.
Đây không phải là lời cảnh báo giật gân. Đây là bài học tôi đã rút ra sau 28 năm quan sát ngành: công nghệ mới luôn đi kèm với những điểm mù mới. Và nhiệm vụ của chúng ta không phải là sợ hãi, mà là đào sâu để phát hiện chúng.
Hãy nhớ: zero knowledge cũng có điểm mù. Và điểm mù đó thường nằm ở nơi ít ai ngờ tới: ranh giới giữa toán học thuần túy và thực thi phần mềm.