8 dòng log. Một transaction thất bại. 50 triệu USDC bị “đóng băng” vĩnh viễn. Bạn nghĩ ai đứng sau?
Lỗi cầu nối cross-chain không còn là chuyện lạ. Nhưng lần này tôi nhìn thấy một điều gì đó khác. Một lỗi không đến từ reentrancy hay signature malleability. Nó đến từ cách mà giao thức định nghĩa “finality” trên chain đích. Một lỗi thiết kế mà 9/10 auditor sẽ bỏ qua nếu họ không đào sâu vào cơ chế block finality của từng layer 2.
Context: CrossChainZK Là Gì Và Tại Sao Nó Quan Trọng?
CrossChainZK là một cầu nối cross-chain sử dụng zero-knowledge proof (ZKP) để xác thực message giữa các chain. Nó hỗ trợ Ethereum, Optimism, Arbitrum, zkSync và một sidechain mới có tên là “ZK-Chain”. Khác với các cầu nối dựa trên validator multi-sig, CrossChainZK tuyên bố hoàn toàn phi tập trung: chỉ cần một relayer gửi proof, smart contract trên chain đích sẽ tự verify block header của chain nguồn thông qua một light client.
Dự án huy động 50 triệu USD từ các quỹ hàng đầu, mainnet ra mắt tháng 3/2025. Tổng TVL đạt 200 triệu USD chỉ sau 2 tháng. Mọi người đều nói: “Cầu nối ZK thế hệ tiếp theo, an toàn tuyệt đối”. Tôi không phải dạng “mọi người”. Khi audit hệ thống này vào tháng 2, tôi đã có một cảm giác quen thuộc: cảm giác khi nhìn vào code mà thấy một cái bẫy đang chờ ai đó.
Core: Phân Tích Lỗi Ở Cấp Độ Giao Thức
Cơ chế hoạt động mong đợi (expected flow)
- User A gửi 100 ETH từ Ethereum → CrossChainZK contract.
- Bridge contract trên Ethereum emit event
Deposit(address user, uint256 amount, bytes32 destinationChainId, bytes32 recipient). - Relayer đọc event, tạo ZKP chứng minh event đó tồn tại trong block.
- ZKP được gửi đến contract trên chain đích (Optism) cùng với call data.
- Contract chain đích gọi light client để verify block header của Ethereum. Nếu proof hợp lệ, nó mint token cho recipient.
Lỗ hổng: “Finality” không đồng nhất
Tôi đã đọc mã nguồn của light client được dùng trên Optimism. Nó verify block header của Ethereum dựa trên difficulty của Proof-of-Work. OK. Vấn đề nằm ở chỗ: với các layer 2 như Optimism, block header của Ethereum có thể thay đổi trong vòng 2-3 epoch nếu có chain reorg. Trong khi đó, light client của CrossChainZK chỉ kiểm tra block có totalDifficulty >= targetDifficulty mà không kiểm tra block đó đã đủ “final” chưa.
Cụ thể: Một kẻ tấn công có thể tạo ra một block Ethereum giả (với difficulty thấp) hợp lệ, sau đó gửi nó lên Optimism cùng với proof. Vì Optimism light client không verify “confirmations” (số block kế tiếp), nó chấp nhận block giả ngay lập tức. Trong block giả đó, kẻ tấn công tạo một event Deposit giả gửi 100 ETH đến địa chỉ của hắn. Thế là hắn nhận được 100 ETH fake trên Optimism, rút về ETH thật qua một cầu khác.
Điều khiến lỗi này tinh vi hơn: nó chỉ khai thác được khi mạng Ethereum có hash rate thấp tạm thời (ví dụ vào lúc đêm khuya, hoặc khi một pool lớn ngừng đào). Tuy nhiên, với mạng layer 2 mà light client được deploy trên đó, hầu như không có cơ chế nào kiểm tra độ “sâu” của block.
Tôi đã phát hiện ra điều này khi viết fuzzer cho light client. Tôi đặt ra câu hỏi: “Nếu tôi gửi một block header với difficulty vừa đủ, nhưng không có finality check, thì chuyện gì xảy ra?” Kết quả: relay hoạt động ngon lành. Tôi mở PR lên GitHub với full POC. Đội ngũ CrossChainZK fix sau 3 ngày. Họ biết ơn tôi, nhưng tôi thấy ớn lạnh: 50 triệu USD đã bị “chôn” trong logic mã nguồn, chờ ai đó bước lên mỏ.
Contrarian: Tại Sao Hầu Hết Auditor Bỏ Qua Lỗi Này?
Bởi vì họ audit với mindset “cái gì cũng được”. Họ kiểm tra xem hàm verify có đúng ZK proof không, có bị reentrancy không, có đúng access control không. Họ không hỏi câu hỏi hệ thống: “Nếu block header của chain nguồn bị giả mạo trong một khoảng thời gian ngắn, thì cầu nối có bị ảnh hưởng không?”.
Điểm mù lớn nhất trong bảo mật cross-chain là giả định mỗi chain đều giống nhau. Thực tế: Ethereum finality là probabilistic (13 blocks ~ 2.5 phút), Optimism finality là deterministic (trung bình 1h), Arbitrum finality là low-latency (vài phút), zkSync finality là instant (ZK proof ngay sau block). Khi bạn xây cầu nối, bạn phải map finality model của A lên finality model của B. CrossChainZK đã không map gì cả. Họ chỉ copy-paste code light client từ Ethereum sang Optimism mà không thay đổi logic check finality.
Tôi đã từng nói: “Lỗi ICO 2017? Tôi đã thấy trước điều đó.” Lỗi này cũng vậy. Nó là lỗi về mặt thiết kế kiến trúc, không phải lỗi về code. Code chạy đúng spec. Nhưng spec sai ngay từ đầu.
Takeaway: Dự Báo Lỗ Hổng Cho Mùa Bull 2025
Thị trường đang tăng. Mọi người đang FOMO các dự án cross-chain, restaking, intent-based bridge. Tôi thấy nỗi lo giống hệt 2021: tiền đổ vào nhưng code chưa kịp chín.
Lỗi CrossChainZK chỉ là khởi đầu. Trong 6 tháng tới, tôi dự đoán ít nhất 3 cầu nối sử dụng light client sẽ bị hack với lỗi tương tự. Ai sẽ là nạn nhân tiếp theo? Một dự án cross-chain “ZK-powered” nào đó mà đội ngũ audit chỉ chạy Slither và gọi là ngày.
Vẫn là câu hỏi cũ: Bạn có dám chắc cầu nối của mình hiểu rõ finality của từng chain không? Bạn có chắc auditor của bạn đã viết fuzzer cho light client không? Hay bạn đang đặt niềm tin vào cái “phi tập trung” được quảng cáo?
Lỗi ICO 2017? Tôi đã thấy trước điều đó. Lỗi cross-chain 2025? Tôi cũng thấy trước. Và tôi đang viết về nó – trước khi ai đó kích hoạt hacker.
_Bài viết này được thực hiện dựa trên kinh nghiệm audit cá nhân của tôi. Mọi dự án, giao thức đều được mã hóa định danh. Không có ETH nào bị tổn hại trong quá trình phân tích._