Hồi hè năm nay (2025) mình có tham gia 1 cái hackathon của Sui ở trong TP.HCM. Ở đấy mình làm 1 cái ứng dụng chat qua WebRTC, cơ chế bắt tay WebRTC sẽ là trao đổi thông tin network ban đầu qua Blockchain event mà không thông qua signal server như cách làm thông thường.
Trao đổi thông tin qua Blockchain
Theo cách làm thông thường thì khi bắt tay chúng ta phải chuẩn bị 1 cái server trung gian gọi là signal server. Khi 2 máy tính muốn connect với nhau server này có nhiệm vụ nhận thông tin network của 2 bên và chuyển tiếp đến bên còn lại để thực hiện bắt tay. Sau khi quá trình bắt tay kết thúc thì 2 máy sẽ connect trực tiếp với nhau mà không cần đến server này nữa. Cách làm của mình là sử dụng Blockchain event để trao đổi thông tin network này giữa 2 account với nhau mà không thông qua signal server, tức là tiết kiệm được phần server trung gian này. Nhưng có 1 vấn đề là, khi trao đổi qua Blockchain event như vậy thì là thông tin về network này hoàn toàn public và ai cũng có thể đọc được (thông tin về địa chỉ public ip, private ip...) dẫn đến khả năng tiềm tàng có thể bị theo dõi tấn công.
Mình có dịp trình bày phương án này với Kostas là co-founder của Mysten Lab. Bác này có PHD về mật mã cryptography với nhiều paper đã xuất bản về crypto, từng lead mảng này cũng như dự án Blockchain ở Meta trước đó và sau đấy tách ra riêng làm Sui. Lúc đó Kostas có nhận xét là thông tin về network này bị public nên sẽ tốt hơn là có phương án để bảo vệ thông tin này.
Vấn đề gửi/nhận thông tin an toàn
Lúc đấy mình nghĩ làm sao để bảo mật thông tin này, nếu mà sinh ra 1 khóa bí mật để mã hóa thông tin network này trước khi gửi lên mạng blockchain, sau đấy làm cách nào gửi khóa bí mật này cho người nhận 1 cách bí mật để giải mã?? Nếu mà phải dùng thêm 1 cái server thì hóa ra phương án của mình để tiết kiệm server trung gian thành vô ích, chưa kể là dùng server để gửi thì server sẽ biết được cái khóa bí mật này thì cũng không ổn chút nào.
Dạo gần đây mình mới có thời gian để suy nghĩ kỹ hơn về vấn đề này. Ban đầu mình nảy ra ý tưởng là khi muốn gửi message cho 1 địa chỉ ví nào đó, sender sẽ dùng public key của account mục tiêu để mã hóa/encrypt thông tin, gửi thông tin đã mã hóa này lên mạng blokchain, receiver nhận thông tin mã hóa này và giải mã bằng khóa bí mật của mình. Cách này đảm bảo mặc dù ai cũng có thể thấy được thông tin đã mã hóa nhưng chỉ có người nhận mới có thể giải mã và đọc được thông tin nhờ khóa bí mật của mình. Cách này nghe cũng ổn nhưng khi mình tham khảo ChatGPT thì sẽ có 1 số vấn đề:
- Mã hóa dùng public key không thể mã hóa thông tin dài.
- Quá trình tính toán encrypt/decrypt lâu và tốn tài nguyên.
Cách mã hóa bằng public key và giải mã bằng private key này gọi là mã hóa bất đối xứng (Asymmetric Encryption).
ECDH (Eliptic curve Diffie-Hellman)
Sau đó thì ChatGPT mới gợi ý cho mình 1 cách làm đã được chuẩn hóa hơn rất nhiều.
- Sử dụng khóa bí mật của mình và khóa công khai của đối phương để sinh ra secret key chung. Cách này đảm bảo 2 người (và chỉ 2 người) có được 1 secret key chung mà không phải gửi gì qua mạng cả.
- Người gửi mã hóa message bằng shared secret và gửi thông tin mã hóa lên mạng.
- Người nhận lấy thông tin đã mã hóa và giải mã bằng shared secret.
Cách làm này có ưu điểm:
- Mã hóa thông tin dài bất kỳ.
- Quá trình tính toán nhanh và nhẹ hơn.
Cách mã hóa và giải mã sử dụng 1 key chung này gọi mã hóa đối xứng (Symmetric Encryption).
Ở đây mình thấy kỳ diệu nhất là ở điểm dùng khóa bí mật của mình và khóa công khai của đối phương là có thể sinh ra secret key chung.
Khóa bí mật và khóa công khai
Đầu tiên chúng ta xuât phát từ công thức để tạo cặp khóa bí mật và khóa công khai trong Blockchain.
Pubkey = Prikey * G
Trong đấy
G
là hằng số, 1 điểm cố định trên mặt phẳng 2 chiều tọa độ (x, y) nằm trên đồ thị của 1 hàm số xác định (gọi là đường cong Eliptic / Eliptic curve).- Khóa bí mật
Prikey
là 1 con số. - Khi cộng điểm
G
nàyPrikey
lầnPrikey * G
chúng ta có được 1 điểm Pubkey tọa độ (x, y) cũng nằm trên đồ thị đường cong Eliptic nói trên.
Cái phép toán này có đặc điểm là với tọa độ G
là hằng số
- Cho trước số
Prikey
, có thể dễ dàng tính raPubkey
. - Cho trước tọa độ
Pubkey
, không thể tìm raPrikey
(phải dò từng khả năngPrikey
với 1 số lượng phép thử quá lớn).
Hầu như toàn bộ ngành Mật mã học cũng như Blockchain, tiền điện tử được tạo ra từ công thức này, với tính chất 1 chiều
Prikey => Pubkey
, đồng thời từPrikey
có thể tạo radigital signature
mà có thể được xác nhận bởiPubkey
. Mình cảm giác nó giống như việc tìm ra chất bán dẫn chỉ cho dòng điện chạy theo 1 chiều vậy, từ đó cũng tạo ra các mạch logic và toàn bộ ngành công nghiệp đồ điện tử hiện nay.
Sinh ra shared secret
Với 2 account Blockchain A và B ta có
PubkeyA = PrikeyA * G
PubkeyB = PrikeyB * G
Tính thử
PrikeyA * PubkeyB = PrikeyA * (PrikeyB * G) = (PrikeyA * PrikeyB) * G
PrikeyB * PubkeyA = PrikeyB * (PrikeyA * G) = (PrikeyB * PrikeyA) * G
Các phép cộng điểm ở trên Eliptic Curve đã được chứng minh là có tính giao hoán và kết hợp như 1 phép cộng thông thường, từ đó suy ra
PrikeyA * PubkeyB = PrikeyB * PubkeyA = X
Như vậy 2 tài khoản A và B có thể sinh ra 1 điểm tọa độ bí mật chung giữa 2 người là X mà chỉ cần biết khóa công khai của đối phương kết hợp với sử dụng khóa bí mật của chính mình. Từ X sẽ sinh ra 1 cái khóa chung shared secret dùng để mã hóa và giải mã giữa 2 người.
Ý tưởng
Sau đấy thì mình mới biết đây là phương pháp đã được chuẩn hóa và sử dụng phổ biến trong các giao thức như HTTPS, SSH hay trong các ứng dụng nhắn tin có mã hóa đầu cuối như Signal, Telegram. Chỉ khác ở chỗ các giao thức này với mỗi session 2 bên sẽ sinh ra mỗi bên 1 cặp khóa mới và trao đổi public key, từ đó tính ra shared key giống nhau ở mỗi bên. Với account Blockchain thì public key coi như có sẵn khi biết account nên không cần trao đổi nữa.
Mình đang có idea có thể áp dụng phương pháp này để làm tính năng gửi nhận message 1 cách bảo mật và an toàn giữa 2 tài khoản blockchain bất kỳ, thông qua cách sử dụng Blockchain event. Tức là tận dụng Blockchain để gửi và nhận tin nhắn đã mã hóa để có thể giản lược được server trung gian. Khóa để mã hóa và giải mã là key chung bí mật giữa 2 người. Hiện cách này mình đang áp dụng cho quá trình bắt tay WebRTC giữa 2 tài khoản Blockchain bất kỳ (chưa áp dụng tính năng mã hóa an toàn).
Concept Implementation
Đầu tiên chúng ta sẽ tạo 1 contract có hàm để ghi lại event log nội dung và đích đến mà user muốn gửi đi.
Nếu là EVM
pragma solidity ^0.8.24;
contract Message {
event SendMessage(address indexed from, address indexed to, string cid);
function sendMessage(address to, string calldata cid) public {
emit SendMessage(msg.sender, to, cid);
}
}
Trường hợp sử dụng Sui contract
module sui_message::Message {
use sui::event;
/// Send event
public struct SendMessage has drop, copy {
from: address,
to: address,
cid: vector<u8>,
}
/// Emit send event
public entry fun sendMessage(to: address, cid: vector<u8>, ctx: &mut TxContext) {
let event = SendMessage {
from: ctx.sender(),
to,
cid,
};
event::emit(event);
}
}
Nội dung contract này khá là tối giản. Chúng ta chỉ định nghĩa 1 cái event, và sau đó là hàm nhận yêu cầu từ phía ứng dụng và emit cái event này là xong.
Về phía ứng dụng workflow để gửi message
- Tạo message, xác định địa chỉ cần gửi.
- Tính ra shared key từ prikey của mình và pubkey của người nhận.
- Mã hóa message này dùng shared key.
- Gửi nội dung message đã mã hóa lên dịch vụ lưu trữ/storage như IPFS hay Walrus (của Sui), nhận về mã hash cho đoạn nội dung này.
- Gọi hàm Blockchain với tham số
(địa chỉ muốn gửi, mã hash trỏ đến nội dung đã mã hóa)
.
Để nhận message
- Lắng nghe event tạo ra trên Blockchain, filter những event có nội dung
To
là địa chỉ của mình. - Catch được event gửi cho mình, lấy địa chỉ người gửi, mã hash và load về full nội dung đã mã hóa từ dịch vụ lưu trữ.
- Tính ra shared key từ prikey của mình và pubkey của người gửi.
- Giải mã message này dùng shared key.
Để lấy được nội dung history đã gửi hoặc đã nhận chúng ta có thể request những event đã có trong quá khứ và giải mã. Cái này tùy thuộc vào API của full node mà chúng ta kết nối tới cho phép truy cập đến nội dung event log cũ đến khoảng thời gian nào.
Cái này mình nghĩ là có thể nên sử dụng giống email hơn là chat vì mỗi lần chat mà phải bấm ký transaction 1 lần thì rất bất tiện. Nếu làm chat thì nên làm qua giao thức WebRTC tức là chỉ giai đoạn bắt tay chuyển tiếp thông tin qua Blockchain, còn lại message sẽ được gửi trực tiếp giữa 2 người (hoặc fallback qua server trung gian nếu network không cho phép kết nối trực tiếp). Sau đó khi muốn có thể lưu lịch sử chat lên Blockchain bằng cách mã hóa như trên để 1 trong 2 người có thể đọc được.
Vấn đề
Về mặt concept thì cài đặt không quá phức tạp nhưng khi muốn làm ở mức độ production thì có 1 số vấn đề phát sinh.
Khi làm web app sử dụng kèm wallet extension, ta không thể yêu cầu wallet cung cấp private key để tính toán shared key được. Do vậy cần cài đặt và expose API tạo shared key kiểu wallet.generateSharedKey(partnerPubKey)
vào trong wallet. Việc này phải phụ thuộc vào wallet. Với lại nếu có API như vậy từ wallet thì các app khác (app độc hại có thể bắt chước lại code, vì code trong app của mình là không thể giấu được) trong cùng môi trường account đó cũng có thể gọi để lấy được key này => không an toàn. Trừ khi trong phần gen key tự động thêm thông tin unique của app mà khi gọi không thể control được như origin url (vẫn có khả năng bị thao túng giả origin),... để chắc chắn app khác là sẽ ra kết quả khác.
Việc dùng 1 shared key để mã hóa/giải mã xuyên suốt toàn bộ cũng không đảm bảo lắm, nhỡ key chung bị lộ 1 lần là toàn bộ message sẽ bị lộ hết. Như trong signal thì với mỗi message họ lại gen ra 1 cặp key riêng để tạo shared key khác cho mỗi lần mã hóa message.
Mình đang nghĩ đến phương án là với mỗi message hoặc session sẽ tạo ra cặp khóa mới, lưu public key lên contract để thông báo cho người khác biết mình đang dùng key gì. Các cặp khóa sinh ra có private key có thể theo dạng chain kiểu Main prikey => prikey 1 => prikey 2...
cho dễ quản lý...