Passer au contenu principal

Provably Fair - Implémentation

Comment Provably Fair est-il implémenté dans le code ?

En supposant que la partie est terminée et que nous disposons de la seed serveur non hachée, de la seed client et du nonce, voici comment cela fonctionne.

Trois étapes principales sont nécessaires pour générer les résultats du jeu.

  1. byteGenerator (génération d'octets aléatoires)

  2. generateFloats (conversion d'octets en nombres à virgule flottante [chiffres])

  3. Nombres à virgule flottante vers événements de jeu (conversion des nombres à virgule flottante en événements réels dans les jeux originaux)

Fonction ByteGenerator comme générateur d'octets aléatoires Octets Générateur

La byteGenerator fonction sert de générateur d'octets aléatoires.

Elle utilise des valeurs uniques de clientSeed, serverSeed, nonce, et cursor pour générer une valeur hachée SHA-256 aléatoire et unique à l'aide de la fonction cryptographique HMAC_SHA256.

La valeur SHA-256 générée a une taille de 32 octets. Afin d'assurer un équilibre entre un résultat de jeu suffisamment aléatoire et l'intensité des calculs, les 32 octets sont divisés en 8 sections de 4 octets* chacune pour générer chaque résultat de jeu.

Dans certains jeux nécessitant plus d'8 résultat de jeu, nous utiliserons le cursor. Le curseur commence initialement à 0 et augmente jusqu'à 1,2,3,4 afin de répondre aux exigences de résultat,

Pour les jeux ne nécessitant pas plus d'8 résultat aléatoire, la valeur du curseur n'augmente pas.

* 4 octets de données nous donnent 2^32 (4,294,967,296) résultats possibles, ce qui constitue un ensemble suffisamment vaste pour garantir l'aléatoire.

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 : fonction de conversion d'octets en nombres à virgule flottante

Cette fonction convertit la valeur hexadécimale SHA-256 d'octets en nombres à virgule flottante afin de l'utiliser dans les calculs ultérieurs des événements de jeu.

Ci-dessous, vous trouverez comment une valeur hexadécimale SHA-256 est convertie d’un octet en nombre à virgule flottante. La sortie finale, numArr, contient tous les résultats possibles requis par le jeu. Si un seul résultat 1 est nécessaire, la liste ne contiendra qu’une seule valeur.

Elle renvoie un tableau de nombres compris entre 0 et 1, où count représente le nombre d'éléments du tableau renvoyé.

Code :

// 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;
}

Tous nos jeux originaux utilisent à la fois les fonctions ByteGenerator et GenerateFloats pour générer des nombres à virgule flottante aléatoires entre 0 et 1. Toutefois, à partir de cette étape, chaque jeu suit une procédure unique pour déterminer l'événement de jeu à partir du nombre à virgule flottante généré.

La procédure propre à chaque jeu est expliquée en détail dans Événements de jeu.

Exemple illustré

Nous allons illustrer ici comment les données d'entrée sont utilisées pour générer un événement de jeu de dés.

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

Remarque : en réalité, lorsqu'un jeu est actif, la seed serveur est hachée. Le joueur et l'opérateur ne peuvent donc pas voir le résultat individuel de ce processus pendant la partie.

Comme le même algorithme et les mêmes fonctions sont utilisés pendant et après la partie, les utilisateurs peuvent toujours prouver le résultat avec les mêmes données d'entrée.

Avez-vous trouvé la réponse à votre question ?