Cơ chế Provably Fair trong cá cược giải quyết một vấn đề đã tồn tại ở các casino trực tuyến ngay từ khi chúng xuất hiện: làm thế nào để người chơi có thể tin rằng kết quả của một ván chơi không bị can thiệp sau khi kết quả đã được tạo ra? Hóa ra, câu trả lời chính là công cụ mà các nhà mật mã học thường sử dụng mỗi khi hai bên cần đạt được sự đồng thuận về một vấn đề nào đó mà không cần tin tưởng lẫn nhau — đó là phương án “cam kết-tiết lộ”.
Phần tiếp theo là hướng dẫn chi tiết về quy trình mà Cloudbet áp dụng cho trò chơi Mines — từ việc tạo số ngẫu nhiên ban đầu đến việc đặt các mỏ — cũng như cách bất kỳ người chơi nào có chút kiến thức về lập trình đều có thể tự mình xác minh mọi kết quả mà họ từng tham gia.
Đây là bài viết đầu tiên trong chuỗi bài phân tích kỹ thuật về cơ chế Provably Fair của sau các trò chơi Cloudbet Originals. Để tự mình xác minh bất kỳ ván chơi nào (bao gồm cả Mines), hãy sử dụng Công cụ Provably Fair của Cloudbet.
Contents
Cam kết kết quả trước khi ván chơi bắt đầu
Trước mỗi ván chơi, máy chủ sẽ tạo ngẫu nhiên một server seed gồm 32 byte và ngay lập tức công bố giá trị băm SHA3-256 của server seed đó — đây chính là cam kết. Giá trị băm này giống như một “dấu vân tay” một chiều: nó chứng minh rằng seed đã tồn tại tại thời điểm đó mà không tiết lộ chi tiết.
Sau khi ván chơi kết thúc, Cloudbet sẽ công khai server seed gốc. Bất kỳ ai cũng có thể tính băm và xác nhận rằng kết quả này khớp với cam kết trước khi bắt đầu trò chơi, điều này chứng minh rằng hạt giống — và do đó, bố cục mine — đã được xác định trước khi ô đầu tiên được chọn. Không có thời điểm nào mà casino có thể đổi nó lấy một giá trị có lợi hơn.
Kết hợp yếu tố ngẫu nhiên của người chơi
Nếu chỉ sử dụng server seed, người chơi sẽ phải hoàn toàn phụ thuộc vào nguồn ngẫu nhiên do casino tạo ra. Để khắc phục điều đó, hệ thống sẽ kết hợp thêm hai đầu vào khác: client seed (do người chơi thiết lập) và nonce (một số nguyên tăng dần sau mỗi lần đặt cược).
Cả ba giá trị này được nối lại với nhau và được băm bằng thuật toán SHA3-256 để tạo ra round signature:
roundSignature = SHA3-256(`${serverSeed}:${clientSeed}:${nonce}`)
Client seed giúp đảm bảo casino không thể dự đoán trước kết quả tại thời điểm công bố cam kết, vì khi server seed được tạo thì hệ thống vẫn chưa biết giá trị do người chơi cung cấp. Nonce đảm bảo mỗi ván chơi sẽ tạo ra một round signature khác nhau, ngay cả khi server seed và client seed vẫn giữ nguyên giữa các lần đặt cược.
Tạo số ngẫu nhiên bằng SHAKE256
Round signature sau đó được đưa vào SHAKE256, một hàm đầu ra có độ dài mở rộng (XOF) thuộc họ SHA-3. Khác với một hàm băm thông thường, XOF có thể tạo ra một chuỗi byte giả ngẫu nhiên với độ dài tùy ý từ một đầu vào cố định. Điều này đặc biệt hữu ích vì việc đặt nhiều quả mìn đòi hỏi phải tạo ra nhiều giá trị ngẫu nhiên độc lập.
Trong quá trình triển khai, hệ thống lấy 4 byte mỗi lần từ chuỗi đầu ra này. Khi phần đầu ra ban đầu được sử dụng hết, hệ thống sẽ nối thêm chuỗi "tiếp theo" vào trạng thái hiện tại rồi tiếp tục tạo thêm dữ liệu đầu ra. Tuy nhiên, trên thực tế, với một lưới 25 ô và bất kỳ số lượng mìn hợp lý nào, toàn bộ dữ liệu cần thiết đều nằm gọn trong 256 bit đầu tiên.
Đặt mine mà không tạo ra sai lệch: lấy mẫu loại bỏ
Việc chuyển đổi một số nguyên ngẫu nhiên 32 bit thành một vị trí trên lưới bằng phép chia lấy dư tưởng chừng đơn giản nhưng lại tồn tại một vấn đề tinh vi: trừ khi 2^32 chia hết cho số vị trí còn lại P, một số vị trí sẽ có xác suất được chọn cao hơn một chút so với các vị trí khác.
Cloudbet loại bỏ hoàn toàn sai lệch này bằng kỹ thuật lấy mẫu loại bỏ:
const maxAcceptable = Math.floor(0x100000000 / P) * P;
let rand;
do { rand = rng.next().value; } while (rand >= maxAcceptable);
const index = rand % P;
Mọi giá trị nằm ngoài bội số lớn nhất của P có thể biểu diễn trong phạm vi 32 bit sẽ bị loại bỏ, và hệ thống sẽ lấy giá trị tiếp theo từ chuỗi đầu ra của SHAKE256. Kết quả là các giá trị còn lại sẽ tạo thành phân phối đồng đều trên đúng P vị trí.
Sau khi mỗi mine được đặt, vị trí đó sẽ bị loại khỏi tập các vị trí có thể chọn và P sẽ giảm đi 1. Quy trình này tương đương với việc thực hiện một thuật toán tráo Fisher-Yates một phần trên mảng phẳng gồm 25 ô của lưới.
Xác minh một vòng đấu
Việc xác minh sau trận đấu yêu cầu bốn giá trị, tất cả đều được Cloudbet công bố sau khi vòng đấu kết thúc:
serverSeedclientSeednonceminePositionsđã được khai báo
Các bước xác minh như sau:
- Băm
serverSeedbằng SHA3-256 và xác nhận kết quả khớp với giá trị cam kết trước ván chơi. - Tạo
roundSignature = SHA3-256(serverSeed:clientSeed:nonce). - Khởi tạo chuỗi SHAKE256 bằng
roundSignaturevà chạy vòng lặp lấy mẫu loại bỏ để tái tạo độc lậpminePositions.
Nếu kết quả khớp nhau, điều đó xác nhận rằng bàn chơi không bị thay đổi sau khi ván chơi bắt đầu.
Ví dụ triển khai (Node.js)
npm install js-sha3
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');
function generateServerSeed() {
const seed = crypto.randomBytes(32).toString('hex');
return { serverSeed: seed, commitment: sha3_256(seed) };
}
function createRoundSignature(serverSeed, clientSeed, nonce) {
return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}
function* createShake256Stream(signature) {
const byteStream = shake256.create(32);
byteStream.update(signature);
while (true) {
const buf = Buffer.from(byteStream.digest({ buffer: true }));
for (let i = 0; i < buf.length; i += 4) {
if (i + 4 <= buf.length) yield buf.readUInt32BE(i);
}
byteStream.update('next');
}
}
function pickUniquePositions(count, totalTiles, rng) {
const available = Array.from({ length: totalTiles }, (_, i) => i);
const result = [];
for (let i = 0; i < count; i++) {
const P = available.length;
const maxAcceptable = Math.floor(0x100000000 / P) * P;
let rand;
do { rand = rng.next().value; } while (rand >= maxAcceptable);
const index = rand % P;
result.push(available[index]);
available.splice(index, 1);
}
return result;
}
const { serverSeed, commitment } = generateServerSeed();
const clientSeed = 'player-provided-seed';
const nonce = 0;
const sig = createRoundSignature(serverSeed, clientSeed, nonce);
const rng = createShake256Stream(sig);
const mines = pickUniquePositions(5, 25, rng);
console.log('Commitment:', commitment);
console.log('Server seed (post-game):', serverSeed);
console.log('Mine positions:', mines);
Một vài điều đáng lưu ý
Vì sao lại dùng SHA-3 thay vì SHA-2? Đối với một cơ chế cam kết đơn giản, cả hai đều đáp ứng tốt yêu cầu. Tuy nhiên, cấu trúc sponge của SHA-3 giúp loại bỏ các cuộc tấn công mở rộng độ dài. Ngoài ra, việc sử dụng SHA-3 xuyên suốt toàn bộ quy trình — cả để tạo cam kết và làm nền tảng cho SHAKE256 — cũng giúp hệ thống duy trì một kiến trúc mật mã nhất quán.
Vì sao chọn SHAKE256 thay vì PRNG seed? Tính xác định và khả năng kiểm chứng. SHAKE256 là một thuật toán chuẩn hóa với đặc tả rõ ràng, vì vậy các cách triển khai độc lập bằng bất kỳ ngôn ngữ lập trình nào cũng sẽ tạo ra đầu ra giống hệt nhau từ cùng một đầu vào. PRNG tùy chỉnh sẽ dẫn đến các hành vi do cách triển khai quy định, khiến việc xác minh của bên thứ ba trở nên khó khăn hơn.
Người chơi có thể lợi dụng hệ thống bằng cách chọn một client seed có lợi không? Về nguyên tắc, có. Người chơi có thể thử nhiều client seed khác nhau để tìm ra giá trị tạo ra một bàn chơi có lợi cho mình. Điều này được xem là hoàn toàn chấp nhận được trong thiết kế của hệ thống.Quyền lựa chọn client seed của người chơi là một tính năng, chứ không phải một lỗ hổng. Rủi ro mà casino phải đối mặt là tương tự như người chơi. Quan trọng hơn, chính cơ chế cho phép người chơi tìm kiếm một client seed có lợi cũng đồng thời cho phép họ chứng minh rằng casino chưa bao giờ can thiệp vào server seed của mình.
Trò chơi Mines là một điểm khởi đầu lý tưởng để tiếp cận thiết kế công bằng có thể chứng minh được, chính bởi vì quy trình tạo số ngẫu nhiên (RNG) của nó tương đối độc lập: chỉ gồm một chu kỳ “cam kết-tiết lộ”, một luồng byte đơn giản và một kỹ thuật lấy mẫu đã được hiểu rõ. Khi loạt bài này tiếp tục, chúng ta sẽ thấy các sản phẩm Cloudbet Originals khác sẽ điều chỉnh và mở rộng những nền tảng này như thế nào — đôi khi bằng cách bổ sung các nguồn entropy, các bản đồ đầu ra khác nhau hoặc quy trình xác minh nhiều giai đoạn — đồng thời vẫn duy trì cam kết cốt lõi: không có kết quả nào có thể được xác định trước sau khi người chơi đã thực hiện hành động của mình.


