¿Cómo se implementa Provably Fair en el código?
Suponiendo que el juego ha terminado y tenemos la semilla del servidor sin hash, la semilla del cliente y el nonce, así es como funciona.
Se requieren tres pasos principales para generar los resultados del juego.
byteGenerator (Generación de bytes aleatorios)
generateFloats (Conversión de bytes a números de punto flotante [dígitos])
De números de punto flotante a eventos del juego (Conversión de números de punto flotante a eventos reales en los juegos originales)
Función ByteGenerator como generador de bytes aleatorios
La byteGenerator función sirve como generador de bytes aleatorios.
Toma valores únicos de clientSeed, serverSeed, nonce, y cursor para generar un valor único y aleatorio con hash SHA-256 mediante la función criptográfica HMAC_SHA256.
El valor SHA-256 generado tiene un tamaño de 32 bytes. Para garantizar un equilibrio entre un resultado de juego suficientemente aleatorio y la intensidad computacional, los 32 bytes se dividen en 8 secciones de 4 bytes* cada una para generar cada resultado del juego.
En ciertos juegos en los que se requieren más de 8 resultados, utilizaremos el cursor. cursor inicialmente comienza en 0 y aumenta a 1, 2, 3, 4 para cumplir con los resultados requeridos.
En los juegos en los que no requerimos más de 8 resultados aleatorios, el cursor no aumenta de valor.
*4 bytes de datos nos darán 2^32 (4,294,967,296) resultados posibles, lo que representa un grupo suficientemente amplio para la aleatoriedad.
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;
}
}
Función GenerateFloats para convertir bytes a números de punto flotante
Esta función convierte el valor hexadecimal SHA-256 de bytes a números de punto flotante para usarlo en cálculos posteriores de eventos del juego.
A continuación se ilustra cómo un valor hexadecimal SHA-256 se convierte de bytes a números de punto flotante. El resultado final, numArr, contiene todos los posibles resultados requeridos por el juego. Si solo se requiere 1 resultado, la lista contendrá solo un valor.
Devuelve un arreglo de números entre 0 y 1. count representa la cantidad de elementos en el arreglo devuelto.
Código:
// 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;
}
Todos nuestros juegos originales utilizan tanto las funciones ByteGenerator como GenerateFloats para generar números de punto flotante aleatorios entre 0 y 1. Sin embargo, a partir de este punto, cada juego sigue un procedimiento único para determinar el evento del juego a partir del número de punto flotante generado.
El procedimiento único se explicará en detalle en Eventos del juego.
Ejemplo ilustrado
Aquí ilustraremos cómo se utilizan las entradas para generar un evento de juego de dados
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: En realidad, cuando un juego está activo, la semilla del servidor tiene hash, por lo que el jugador y el operador no podrán ver el resultado individual de este proceso durante el juego.
Dado que se utilizan el mismo algoritmo y las mismas funciones durante y después de la ronda, el jugador siempre puede comprobar el resultado con las mismas entradas.
