Come viene implementato Provably Fair nel codice?
Supponendo che il gioco sia terminato e che disponiamo del seed del server non sottoposto a hash, del seed del client e del nonce, ecco come funziona.
Per generare i risultati di gioco sono necessari tre passaggi principali.
byteGenerator (Generazione di byte casuali)
generateFloats (Conversione di byte in float (cifre))
Da float a eventi di gioco (Conversione dei float in eventi effettivi nei giochi originali)
Funzione ByteGenerator come generatore casuale di byte generatore
La byteGenerator funzione agisce come generatore di byte casuali.
Utilizza valori univoci di clientSeed, serverSeed, nonce, e cursor per generare un valore hash SHA-256 casuale e univoco utilizzando la funzione crittografica HMAC_SHA256 .
Il valore SHA-256 generato ha una dimensione di 32 byte. Per garantire un equilibrio tra un risultato di gioco sufficientemente casuale e l'intensità di calcolo, i 32 byte vengono suddivisi in 8 sezioni di 4 byte* ciascuna per generare ogni risultato di gioco.
In alcuni giochi in cui è richiesto più di 8 risultato di gioco, utilizzeremo il cursor. Il cursore inizia inizialmente da 0 e aumenta fino a 1,2,3,4 per soddisfare il requisito relativo ai risultati,
Per i giochi in cui non sono richiesti più di 8 risultati casuali, il valore del cursore non aumenta.
*4 byte di dati forniscono 2^32 (4,294,967,296) possibili risultati, un insieme sufficientemente ampio per garantire la casualità.
function* byteGenerator({ serverSeed, clientSeed, nonce, cursor }: ByteGeneratorInterface) {
// Setup cursor variables let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
// Generate outputs until cursor requirement fullfilled
while (true) {
// HMAC function used to output provided inputs into bytes
const hmac = crypto.createHmac('sha256', serverSeed);
hmac.update(`${clientSeed}:${nonce}:${currentRound}`);
const buffer = hmac.digest();
// Update curser for next iteration of loop
while (currentRoundCursor < 32) {
yield Number(buffer[currentRoundCursor]);
currentRoundCursor += 1;
}
currentRoundCursor = 0;
currentRound += 1;
}
}GenerateFloats: funzione per convertire i byte in float
Questa funzione converte il valore esadecimale SHA-256 da byte in float, da utilizzare in ulteriori calcoli per gli eventi di gioco.
Di seguito viene illustrato come un valore esadecimale SHA-256 viene convertito da byte a float. L'output finale, numArr, contiene tutti i possibili risultati richiesti dal gioco. Se è richiesto un solo risultato 1, l'elenco conterrà un solo valore.
Restituisce un array di numeri compresi tra 0-1, se count rappresenta il numero di elementi nell'array restituito.
Codice:
// Convert the hash output from the rng byteGenerator to floats
export function generateFloats({
serverSeed,
clientSeed,
nonce,
cursor,
count,
}: GenerateFloatsInterface) {
// Random number generator function
const rng = byteGenerator({ serverSeed, clientSeed, nonce, cursor });
// Declare bytes as empty array
const bytes = [];
// Populate bytes array with sets of 4 from RNG output
while (bytes.length < count * 4) {
bytes.push(rng.next().value!);
}
// Return bytes as floats using lodash reduce function
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;
}
Tutti i nostri giochi originali utilizzano sia le funzioni ByteGenerator e GenerateFloats per generare float casuali compresi tra 0 e 1. Tuttavia, da questo punto in poi ogni gioco segue una procedura specifica per determinare l'evento di gioco a partire dal float generato.
La procedura specifica verrà spiegata in dettaglio in Eventi di gioco.
Esempio illustrato
Qui illustreremo come vengono utilizzati gli input per generare un evento di gioco Dice
Input Values: Given some random input values
serverSeed, clientSeed, nonce, cursor
Step 1: byteGenerator creates a SHA-256 byte
"a3f4e0ac7c7e8e9b5f16106c6b1d14e87c2c5a8d59b1d1c6a0b5f3e5a7d4c9a8”
Step 2: 256 bytes is split into 8 equal set of 32 bytes
"a3f4e0ac”, “7c7e8e9b”, “5f16106c”, “6b1d14e8”,
“7c2c5a8d”, “59b1d1c6”, “a0b5f3e5”, “a7d4c9a8”
Step 3: Each set is broken down into 2 bytes each
(only first 2 sets is shown)
set 1: a3-f4-e0-ac
set 2: 7c-7e-8e-9b
......
Step 4: Each of the 2 bytes represent a number from 0 to 255
set 1: [163, 244, 224, 172]
set 2: [124, 126, 142, 155]
......
Step 5: Using the formula in the code to generate numArr
Float 1 = (163 / (256^1)) + (244 / (256^2)) + (224 / (256^3))
+ (172 / (256^4)) = 0.64045528601
Float 2 = (124 / (256^1)) + (126 / (256^2)) + (142 / (256^3))
+ (155 / (256^4)) = 0.48630610737 Float 3 ......
Step 6: Output the floats in a list
numArr = [0.64045528601, 0.48630610737, ...... ]
*** Game Event Generation ***
Step 7: The numArr output will be used to generate game events,
depending on the game requirement
Example 7: Using dice as an example.
The game event is generated using the first numArr value.
const resultValue = floats.map(val => Math.floor(floats * 10001) / 100);
resultValue = Math.floor(0.64045528601 * 10001) / 100
= 64.05
Nota: quando un gioco è attivo, il seed del server è sottoposto a hash; pertanto, durante il gioco né il giocatore né l'operatore possono visualizzare l'output individuale di questo processo.
Poiché durante e dopo il gioco vengono utilizzati lo stesso algoritmo e le stesse funzioni, gli utenti possono sempre verificare il risultato a parità di input.
