Como o Provably Fair é implementado no código?
Supondo que o jogo tenha terminado e que tenhamos a seed do servidor sem hash, a seed do cliente e o nonce, veja como funciona.
Serão necessárias três etapas principais para gerar os resultados do jogo.
byteGenerator (Geração de bytes aleatórios)
generateFloats (Conversão de bytes em floats (dígitos))
Float para eventos do jogo (Conversão de floats em eventos reais nos jogos originais)
Função ByteGenerator como gerador de bytes aleatórios Bytes Gerador
A byteGenerator função atua como um gerador de bytes aleatórios.
Ela recebe valores únicos de clientSeed, serverSeed, nonce, e cursor para gerar um valor aleatório e único com hash SHA-256 usando a função criptográfica HMAC_SHA256.
O valor SHA-256 gerado tem 32 bytes. Para garantir um equilíbrio entre um resultado de jogo suficientemente aleatório e a intensidade computacional, os 32 bytes são divididos em 8 seções de 4 bytes* cada para gerar cada resultado do jogo.
Em determinados jogos em que mais de 8 resultado é necessário, utilizaremos o cursor. O cursor começa inicialmente em 0 e aumenta até 1,2,3,4 para atender ao requisito de resultados,
Em jogos nos quais não precisamos de mais de 8 resultados aleatórios, o valor do cursor não é incrementado.
*4 bytes de dados nos fornecerão 2^32 (4,294,967,296) resultados possíveis, o que representa um conjunto suficientemente grande para aleatoriedade.
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;
}
}GerarFloats: função para converter bytes em floats
Esta função converte o valor hexadecimal SHA-256 de bytes em floats para uso em cálculos posteriores de eventos do jogo.
Abaixo é ilustrado como um valor hexadecimal SHA-256 é convertido de byte em float. A saída final, numArr, contém todos os resultados possíveis exigidos pelo jogo. Se apenas 1 resultado for necessário, a lista conterá apenas um valor.
Ela retorna uma matriz de números entre 0-1, em que count representa o número de elementos na matriz retornada.
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 os nossos jogos originais utilizam tanto as funções ByteGenerator e GerarFloats para gerar floats aleatórios entre 0 e 1. No entanto, a partir deste ponto, cada jogo segue um procedimento exclusivo para determinar o evento do jogo com base no float gerado.
O procedimento exclusivo será explicado em detalhes em Eventos do jogo.
Exemplo ilustrado
Aqui, ilustraremos como as entradas são usadas para gerar um evento de jogo 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
Observação: na prática, quando um jogo está ativo, a seed do servidor recebe hash. Portanto, o jogador e o operador não poderão visualizar a saída individual desse processo durante o jogo.
Como o mesmo algoritmo e as mesmas funções são usados durante e após o jogo, os usuários sempre podem comprovar o resultado com as mesmas entradas.
