Các nhà phát triển Ethereum không còn xa lạ gì với dữ liệu mặc định là công khai. Đây là một trong những tính năng cốt lõi của blockchain: mọi giao dịch, mọi biến, mọi ô lưu trữ — đều minh bạch, có thể kiểm tra và bất biến.
Nhưng điều gì sẽ xảy ra khi bạn muốn xây dựng ứng dụng không để lộ dữ liệu người dùng ra toàn thế giới? Lúc này, Mã hóa hoàn toàn đồng hình (Fully Homomorphic Encryption - FHE) và bộ xử lý đồng xử lý CoFHE xuất hiện. Lần đầu tiên, các hợp đồng Ethereum có thể tính toán trên các giá trị đã mã hóa… nhưng đi cùng với quyền năng này là một trách nhiệm mới:
Kiểm soát ai được phép sử dụng hoặc xem dữ liệu đã mã hóa.
Đó là lý do hệ thống kiểm soát truy cập và cấp phép được đưa vào.
Tại sao kiểm soát truy cập lại quan trọng trong FHE?
Giả sử một người dùng mã hóa một giá trị (như một giá thầu bí mật hoặc số dư của họ) và gửi nó đến hợp đồng của bạn. Bạn lưu trữ giá trị đã mã hóa đó dưới dạng euint32
. Mọi thứ có vẻ ổn!
Nhưng hãy tưởng tượng có người khác nhìn thấy "handle" mã hóa đó trên chuỗi và cố gắng sử dụng nó:
function attack() public { FHE.decrypt(seenHandle); // 👀 tries to decrypt someone else’s data
}
Nếu không có biện pháp bảo vệ, điều này sẽ khiến việc mã hóa trở nên vô nghĩa. Đó là lý do mỗi "handle" mã hóa trong CoFHE đều được liên kết với một danh sách kiểm soát truy cập (ACL). Nó áp đặt các quy tắc nghiêm ngặt về việc ai có quyền thao tác hoặc giải mã một ciphertext cụ thể.
Theo mặc định:
- Hợp đồng tạo ra giá trị (qua
FHE.asEuint32()
) chỉ có quyền truy cập tạm thời trong giao dịch đó. - Không ai khác có thể truy cập ciphertext nếu không được cấp quyền rõ ràng.
Mô hình tư duy: quyền sở hữu, phạm vi và việc giải mã
Khi làm việc với các giá trị mã hóa trong Solidity qua FHE.sol, hãy nghĩ đến "ai có quyền làm gì, và trong bao lâu".
Có hai loại quyền truy cập:
- Truy cập cấp hợp đồng — qua
FHE.allowThis()
hoặcFHE.allow()
- Truy cập cấp người dùng — qua
FHE.allow(ciphertext, address)
+ output được giải mã + phép (permit) trongcofhejs
Có hai phạm vi thời gian:
- Tạm thời: quyền truy cập chỉ tồn tại trong giao dịch hiện tại
- Bền vững: quyền truy cập được duy trì qua nhiều giao dịch
Và để giải mã dữ liệu off-chain, người dùng cần hai điều:
- Hợp đồng phải gọi
FHE.allow(ciphertext, userAddress)
- Người dùng phải giữ một permit hợp lệ chứng minh họ có quyền giải mã dữ liệu đó
Cách sử dụng (về mặt khái niệm)
Quy trình cơ bản:
-
Mã hóa: Người dùng mã hóa dữ liệu phía client với
cofhejs.encrypt()
và gửi đến hợp đồng. -
Cấp quyền truy cập (phía hợp đồng): Hợp đồng nhận dữ liệu qua
InEuint32
và chuyển thànheuint32
bằngFHE.asEuint32(input)
.- Nếu muốn dùng giá trị này trong các giao dịch tương lai, bạn cần gọi
FHE.allowThis(value)
để giữ quyền truy cập.
- Nếu muốn dùng giá trị này trong các giao dịch tương lai, bạn cần gọi
-
Truy cập ngoài chuỗi (người dùng): Trước khi giải mã, hợp đồng phải gọi
FHE.allow(value, user)
và người dùng phải ký một permit bằngcofhejs.createPermit()
. -
Giải mã: Người dùng có thể giải mã giá trị bằng
cofhejs.unseal()
, chứng minh họ có permit hợp lệ và quyền truy cập.
Cách xử lý này đảm bảo rằng giá trị mã hóa:
- Không thể bị bất kỳ ai sử dụng ngẫu nhiên
- Chỉ có thể được giải mã bởi người được chỉ định
- Có thể truyền an toàn giữa các hợp đồng hoặc giữa các chuỗi (nếu kiểm soát quyền truy cập cẩn thận)
Ví dụ: số dư được mã hóa
Giả sử bạn đang xây dựng một token riêng tư hoặc một trò chơi mà người chơi có điểm số hoặc số dư được mã hóa.
Đây là cách xử lý:
function transfer(InEuint32 _amount, address to) public { euint32 amount = FHE.asEuint32(_amount); balances[msg.sender] = FHE.sub(balances[msg.sender], amount); balances[to] = FHE.add(balances[to], amount); // 🔓 Let users unseal their updated balances offchain FHE.allow(balances[msg.sender], msg.sender); FHE.allow(balances[to], to); // ✅ Optional: Let the contract use these values again later FHE.allowThis(balances[msg.sender]); FHE.allowThis(balances[to]);
}
Phía frontend, người dùng tạo permit như sau:
const permit = await cofhejs.createPermit({ type: 'self', issuer: wallet.address,
})
Và sau đó giải mã số dư:
const unsealed = await cofhejs.unseal(encryptedBalance, FheTypes.Uint32, permit.issuer, permit.getHash());
Vậy là xong — không có dữ liệu thô nào bị lộ, không cần thêm giả định về niềm tin.
Ghi nhớ
-
FHE.asEuint32()
chỉ cung cấp quyền truy cập tạm thời. Nếu muốn sử dụng lại, cần gọiFHE.allowThis()
. -
Để giải mã ngoài chuỗi, luôn luôn gọi
FHE.allow(handle, user)
phía hợp đồng. -
Permit là bằng chứng đã ký cho thấy người dùng sở hữu khóa mã hóa — và là bắt buộc để giải mã.
-
Nếu bạn quên gọi
allow()
, hàmunseal()
sẽ âm thầm thất bại với lỗiSEAL_OUTPUT_RETURNED_NULL
. -
Đừng chia sẻ encrypted handle giữa các người dùng hoặc hợp đồng nếu chưa cấp phép rõ ràng.
Điều gì làm CoFHE đặc biệt?
Khác với hệ thống ZK hoặc MPC, dữ liệu FHE có thể tồn tại trên chuỗi ở dạng mã hóa mãi mãi — nhưng cách duy nhất để trích xuất nó là thông qua kiểm soát truy cập.
Thay vì chỉ ẩn đầu vào hay chứng minh bí mật một lần, CoFHE xây dựng hệ thống trong đó:
- Các giá trị mã hóa là công dân hạng nhất
- Quyền sở hữu được thực thi ở lớp mã hóa
- Quyền truy cập là rõ ràng, chi tiết và có thể chứng minh
Nó giống một hệ thống phân quyền truy cập cho bộ nhớ đã mã hóa hơn là một hệ thống “hộp đen” tính toán truyền thống.
Muốn bắt tay vào triển khai?
Bài viết này mang tính lý thuyết là chính, nhưng nếu bạn muốn bắt tay vào xây dựng với CoFHE, hãy tham khảo tài liệu chính thức và ví dụ từng bước: