Bỏ qua đến nội dung chính

Provably Fair - Triển khai

Đã cập nhật cách đây hơn 2 tuần

Cách Provably Fair được triển khai trong mã nguồn?

Giả sử trò chơi đã kết thúc và chúng ta có các giá trị sau: server seed chưa băm, client seed, và nonce, thì quy trình sẽ hoạt động như sau.

3 bước chính để tạo ra kết quả trò chơi:

  1. byteGenerator (Tạo ngẫu nhiên các byte)

  2. generateFloats (Chuyển byte thành số thực)

  3. Float to game events (Chuyển đổi số thực thành sự kiện trong trò chơi gốc)


Hàm ByteGenerator – Trình tạo byte ngẫu nhiên

Hàm byteGenerator là trình tạo byte ngẫu nhiên.

Nó sử dụng các giá trị duy nhất gồm clientSeed, serverSeed, nonce, và cursor để tạo ra một giá trị SHA-256 được băm bằng HMAC_SHA256.

Giá trị SHA-256 được tạo ra có kích thước 32 byte. Để đảm bảo kết quả đủ ngẫu nhiên mà không quá nặng về xử lý, 32 byte này được chia thành 8 phần, mỗi phần 4 byte để tạo kết quả trò chơi.

Trong các trò chơi cần hơn 8 kết quả, chúng tôi sử dụng cursor, bắt đầu từ 0 và tăng dần đến 1, 2, 3,... tùy theo số kết quả cần sinh ra.

Với các trò chơi chỉ cần ≤ 8 kết quả ngẫu nhiên, cursor không thay đổi.

Ghi chú: 4 byte = 2³² = 4,294,967,296 kết quả khả thi – đủ cho mức độ ngẫu nhiên cao.

Code mẫu:

function* byteGenerator({ serverSeed, clientSeed, nonce, cursor }: ByteGeneratorInterface) {
let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;

while (true) {
const hmac = crypto.createHmac('sha256', serverSeed);
hmac.update(`${clientSeed}:${nonce}:${currentRound}`);
const buffer = hmac.digest();

while (currentRoundCursor < 32) {
yield Number(buffer[currentRoundCursor]);
currentRoundCursor += 1;
}
currentRoundCursor = 0;
currentRound += 1;
}
}

Hàm GenerateFloats – Chuyển đổi Byte thành Float

Hàm này chuyển giá trị byte SHA-256 thành số thực (float) để sử dụng trong logic xác định sự kiện trong game.

Bên dưới là minh họa cách một giá trị SHA-256 dạng byte được chuyển thành số thực. Đầu ra cuối cùng, numArr, chứa tất cả các kết quả khả thi được yêu cầu bởi trò chơi. Nếu chỉ cần 1 kết quả, danh sách chỉ có 1 phần tử.

Hàm trả về mảng số thực nằm trong khoảng 0 đến 1. Số lượng phần tử trong mảng tùy vào tham số count.

Code mẫu:

export function generateFloats({ 
serverSeed,
clientSeed,
nonce,
cursor,
count,
}: GenerateFloatsInterface) {
const rng = byteGenerator({ serverSeed, clientSeed, nonce, cursor });
const bytes = [];

while (bytes.length < count * 4) {
bytes.push(rng.next().value!);
}

const numArr = chunk(bytes, 4).map(bytesChunk =>
bytesChunk.reduce((result, value, i) => {
const divider = 256 ** (i + 1);
const partialResult = value / divider;
return result + partialResult;
}, 0),
);
return numArr;
}

Tất cả các trò chơi gốc (original games) của chúng tôi sử dụng hai hàm byteGeneratorgenerateFloats để tạo ra giá trị ngẫu nhiên từ 0 đến 1. Từ đó, mỗi trò chơi sẽ có thuật toán riêng để biến các giá trị này thành sự kiện thực tế trong game.

Cách chuyển đổi này sẽ được trình bày chi tiết trong phần Game Events.


Ví dụ minh họa

Dưới đây là quy trình minh họa việc sinh ra kết quả trò chơi xí ngầu (dice):

Giá trị đầu vào: serverSeed, clientSeed, nonce, cursor

Bước 1: byteGenerator tạo ra giá trị SHA-256 dạng byte

a3f4e0ac7c7e8e9b5f16106c6b1d14e87c2c5a8d59b1d1c6a0b5f3e5a7d4c9a8

Bước 2: Chia 256-bit thành 8 phần bằng nhau, mỗi phần 4 byte

"a3f4e0ac", "7c7e8e9b", "5f16106c", "6b1d14e8", "7c2c5a8d", "59b1d1c6", "a0b5f3e5", "a7d4c9a8"

Bước 3: Tách từng phần thành mảng 4 bytes

Chỉ hiển thị 2 phần đầu:

set 1: a3-f4-e0-ac
set 2: 7c-7e-8e-9b

Bước 4: Chuyển byte sang số (0–255)

set 1: [163, 244, 224, 172]
set 2: [124, 126, 142, 155]

Bước 5: Áp dụng công thức chuyển sang float

Float 1 = (163 / 256¹) + (244 / 256²) + (224 / 256³) + (172 / 256⁴)= 0.64045528601
Float 2 = (124 / 256¹) + (126 / 256²) + (142 / 256³) + (155 / 256⁴)= 0.48630610737

Bước 6: Mảng kết quả đầu ra

numArr = [0.64045528601, 0.48630610737, ...]

Game Event Generation

Bước 7: Sử dụng numArr để sinh ra kết quả trong game, tùy theo yêu cầu

Ví dụ: Trò chơi xí ngầu sử dụng float đầu tiên:

javascriptCopyEditconst resultValue = Math.floor(0.64045528601 * 10001) / 100; // Kết quả: 64.05

Lưu ý: Trong khi trò chơi đang diễn ra, server seed được băm nên người chơi và hệ thống không thể xem kết quả byte thực tế.

Tuy nhiên, vì cùng một thuật toán và hàm được sử dụng trong quá trình chơi và khi xác minh, người chơi luôn có thể kiểm tra và chứng minh kết quả nếu biết được các input: client seed, server seed, và nonce.

Nội dung này có giải đáp được câu hỏi của bạn không?