Mình tự xây một mini zk-Rollups - phần 1
Góp nhặt từ những thứ mình đọc được…
Zero-Knowledge Rollups (zk-Rollups) là một giải pháp mở rộng quy mô Layer 2 trên blockchain. Chúng giúp giảm tải cho mạng chính (Layer 1) bằng cách thực hiện các giao dịch ngoài chuỗi, sau đó sử dụng bằng chứng không tiết lộ tri thức (Zero-Knowledge Proofs - ZKP) để xác minh các giao dịch này trên chuỗi chính, mà không cần phải đưa tất cả dữ liệu giao dịch lên mạng chính.
Theo như mình hiểu (và tưởng tượng) thì hoạt động của zk-Rollups sẽ thế này:
Có một nhóm người muốn giao dịch với nhau, nhưng vì một số vấn đề như tốc độ hoặc phí giao dịch, … của mạng blockchain public (Layer 1) như Ethereum, Solana,… họ quyết định tổ chức giao dịch trên một network riêng (Layer 2).
Ai muốn tham gia kênh giao dịch thì phải có một số tiền deposit sẵn trên Layer 1, rồi sau đó, sang Layer 2 claim lại tiền và giao dịch trên đó. Layer 2 sẽ lưu lại tất cả các giao dịch đã xảy ra giữa nhóm người này.
Để bảo vệ tính toàn vẹn dữ liệu, gom tất cả các giao dịch trên Layer 2 rồi bằng một cách nào đó “nén“ cho thật gọn lại, rồi định kỳ submit dữ liệu đã thu gọn lên Layer 1 làm bằng chứng. (*)
Lúc nào một người muốn ngừng chơi, họ thông báo với Layer 2. Layer 2 sẽ trả về cho họ một bằng chứng (giống kiểu receipt) để verify bên Layer 1, rồi claim tiền còn lại trên đó về.
Các vấn đề phải giải quyết
- Các giả định
Để giải quyết bài toán, mình đặt ra một số giả định như sau:
Layer 1 là một public blockchain, có hỗ trợ smart contract và các công cụ, thư viện mật mã như: chữ ký số, hash, …
Layer 2 có thể là một private blockchain hoặc một hệ thống centralize:
Toàn bộ giao dịch có thể công khai
Không có vấn đề về mặt lưu trữ và chi phí lưu trữ
Có thể che giấu một phần dữ liệu cần thiết
Có bộ sinh số ngẫu nhiên
- Khởi tạo
Theo đề cập ở trên, mọi giao dịch sẽ được thực hiện trên Layer 2, Layer 1 sẽ chỉ là nơi lưu lại các bằng chứng thu gọn. Như vậy, trên Layer 1 phải có 1 smart contract S để thực hiện:
Những người tham gia deposit tiền vào đó. Khi đã deposit tiền vào, cần một chứng nhận để mang qua Layer 2 để claim tiền mới có thể dùng được. (**)
Lưu trạng thái ban đầu (số tiền) của từng người.
Cập nhật các trạng thái tiếp theo và kèm với bằng chứng.
Verify các claim request của người muốn rời khỏi giao dịch (***)
- Chứng nhận claim tiền (**) là gì?
Mình có thể dùng chữ ký số để giải quyết.
Ví dụ: userA gọi function deposit(addr_l2, amount) của smart contract S và nhận về cặp [message, signature]. Sau đó, anh ta có thể mang [message, signature] vừa nhận được để qua Layer 2 đổi tiền. Layer 2 sẽ verify và release tiền cho user A thông qua địa chỉ addr_l2 nếu đúng.
- Nén (*) thế nào, cập nhật gì lên Layer 1?
Trên các hệ thống “xịn“, người ta dùng zk-SNARK (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge) hoặc zk-STARK (Zero-Knowledge Scalable Transparent Argument). Ở đây mình không dùng mấy món ấy vì chúng yêu cầu kỹ thuật cặp ghép (pairings) trên đường cong elliptic (bạn có thể đọc lại ở đây, và đây), mấy món ấy quá phức tạp để dành cho một bài viết cung cấp kiến thức cơ bản như thế này, vả lại, để implement mấy thứ ấy dùng cho demo cũng cồng kềnh...
Chúng ta có thể dùng cấu trúc dữ liệu Merkle Tree (đã giới thiệu ở một bài viết khác) để tạo ra bằng chứng lưu trên Layer 1 như sau:
Sau một khoảng thời gian định kỳ, hoặc sau một số lượng giao dịch nhất định, gom tất cả các giao dịch đã xảy ra để tính Merkle Root r.
Shuffle thứ tự (dùng bộ sinh số ngẫu nhiên) toàn bộ các giao dịch trước khi tính Merkle Root, việc này nhằm tránh user có thể tự mình tính ra proof mà không thông qua Layer 2. Rõ ràng, nếu có \(n\) giao dịch, có thể tạo ra \(n!\) hoán vị khác nhau (r khác nhau) nên không thể đoán được.
Lưu lại bản thứ tự các giao dịch này trên Layer 2 để sau này hỗ trợ việc tạo ra proof cho user, đây là dữ liệu bí mật, được che giấu. Thực ra, nếu đã có bộ sinh số ngẫu nhiên, việc lưu bản thứ tự này không cần thiết, miễn là lưu trữ lại seed của bộ sinh số ngẫu nhiên là đủ xây dựng lại bản này.
Gọi function updateState(r) của S trên Layer 1 để lưu lại bằng chứng mới nhất của tất cả các giao dịch trên Layer 2. Như vậy, ta chỉ cần lưu đúng 1 giá trị hash đại diện cho toàn bộ các giao dịch trên Layer 2 => rất tiết kiệm.
- Verify (***) thế nào?
Dùng Merkle Path để xác thực. Nếu có \(n\) giao dịch, Merkle path sẽ cần \(\log n\) thành phần hash, nghĩa là, nếu có 1000 giao dịch, chỉ cần 10 hash cho mỗi giao dịch cần xác thực.
Dữ liệu verify sẽ là một tập hợp các bộ (transaction, Merkle Path) là tất cả các giao dịch đã xảy ra đối với người dùng cần rút tiền trên Layer 1. Smart contract S sẽ verify toàn bộ các giao dịch này và cộng dồn tính toán tất cả để trả lại số tiền chính xác cho người dùng. Như vậy, S sẽ có function claim(verify_txs[]) để thực hiện nhiệm vụ này.
Bài tiếp theo, mình sẽ dựa trên ý tưởng này để xây dựng giao thức và cài đặt
Subscribe to my newsletter
Read articles from Legos Light directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by