Implementação do Jogo Justo na Clob Markets
Na Clob Markets, adotamos um método inovador para implementar o jogo Justo em nossa plataforma de jogos online. Compreendendo que as abordagens tradicionais podem ser complexas e potencialmente intimidadoras para novos jogadores, refinamos nosso processo para torná-lo mais acessível e transparente.
Geração Tradicional de Semente do Servidor
Normalmente, os cassinos online produzem uma semente do servidor não hashada, representada como uma string hexadecimal de 64 caracteres. Embora essa abordagem seja segura, ela resulta em uma semente que aparece como uma série complexa de caracteres, dificultando que os jogadores a decifrem e verifiquem.
Exemplo de uma Semente Tradicional do Servidor
g9IiwibWFjIjoiNDljMGEwYzgwOGM0MjYxMjUzOWQxNzE3OGFjZTI4YmE2NmVmNDkwZjg4ZjY2MDZjNDk3MDA0MDc0MmU2NjVjMSIsInRhZyI6IiJ9
O método pioneiro da Clob Markets para gerar sementes de servidor.
Para melhorar tanto a acessibilidade quanto a clareza, implementamos um sistema de frase mnemônica de 12 palavras para nossa semente de servidor. Esse método simplifica a aparência da semente, tornando-a mais intuitiva e familiar — especialmente para os usuários acostumados com formatos de carteira de criptomoedas.
Exemplo da Semente do Servidor da Clob Markets
fringe father crew abandon lift jar blast awesome knife labor tape common
Gerando a Frase Mnemônica de 12 Palavras
Nosso processo de geração da semente do servidor começa com a construção de uma frase mnemônica de 12 palavras. Inicialmente, codificamos a entropia em incrementos de 32 bits, e depois derivamos um checksum a partir do hash SHA256 dessa entropia. Os bits combinados de entropia e checksum são, em seguida, utilizados para formar a frase mnemônica.
Cálculo de Entropia e Checksum
CS = ENT / 32
MS = (ENT + CS) / 11
| ENT | CS | ENT+CS | MS |
+-------+----+--------+------+
| 128 | 4 | 132 | 12 |
| 160 | 5 | 165 | 15 |
| 192 | 6 | 198 | 18 |
| 224 | 7 | 231 | 21 |
| 256 | 8 | 264 | 24 |
Modelo JS de Justiça para Geração de Números em Ponto Flutuante e Inteiros
const byteGenerator = async function* (
cursor = 0,
clientSeed: string | null = null,
serverSeed: string | null = null,
nonce: number | null = null
) {
// Setup cursor variables
let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
// Generate outputs until cursor requirement is fulfilled
while (true) {
// HMAC function used to output provided inputs into bytes
const hmac = createHmac(
'SHA-256',
clientSeed + ':' + nonce + ':' + currentRound,
serverSeed
);
const buffer = new Uint8Array(str2ab(hmac));
// Update cursor for next iteration of loop
while (currentRoundCursor < 32) {
// The yield keyword is used in a Generator function, similar to JavaScript's function* syntax
yield buffer[currentRoundCursor];
currentRoundCursor++;
}
currentRoundCursor = 0;
currentRound++;
}
};
const createHmac = (algorithm: string, text: string, key: string | null) => {
// eslint-disable-next-line new-cap
const shaObj = new jsSHA(algorithm, 'TEXT');
shaObj.setHMACKey(key, 'TEXT');
shaObj.update(text);
return shaObj.getHMAC('BYTES');
};
const generateIntegers = async ({
cursor = 0,
count = 1,
target = 100,
clientSeed,
serverSeed,
nonce = 0,
floats = [],
}: IntegerProps) => {
// Step 1: Generate a series of floats
floats =
floats.length > 0
? floats
: await generateFloats({
cursor,
count,
clientSeed,
serverSeed,
nonce,
});
// Step 2: Positions
const positions = Array.from({ length: target }, (_, i) => i + 1);
// Step 3: Get hits
const hits = floats.map((float, index) => {
return positions.splice(Math.floor(float * (target - index)), 1)[0];
});
return hits;
};
const generateFloats = async ({
cursor = 0,
count = 1,
range = [0, 1],
clientSeed,
serverSeed,
nonce = 0,
intOnly = false,
checkScaledResult = true,
}: Props) => {
// Random number generator function
const rng = byteGenerator(cursor, clientSeed, serverSeed, nonce);
// Declare bytes as empty array
const bytes = [];
// Populate bytes array with sets of 4 from RNG output
while (bytes.length < count * 4) {
const item = await rng.next();
bytes.push(item?.value);
}
// Return bytes as floats
const floats: number[] = [];
const byteChunks = chunkArray(bytes, 4);
for (const bytesChunk of byteChunks) {
let result = 0;
for (let i = 0; i < bytesChunk.length; i++) {
const divider = Math.pow(256, i + 1);
const partialResult = bytesChunk[i] / divider;
result += partialResult;
}
if (checkScaledResult) {
// Scale the result to the desired range
const scaledResult = result * (range[1] - range[0]) + range[0];
let validResult = intOnly ? Math.floor(scaledResult) : scaledResult;
while (floats.includes(validResult)) {
validResult++;
if (validResult > range[1]) {
validResult = range[0];
}
}
floats.push(validResult);
} else {
floats.push(result);
}
}
return floats;
};
Gerar Semente Binária a partir da Função Mnemônica
Dentro do nosso sistema, convertemos tanto as sementes binárias do cliente quanto as do servidor em números de ponto flutuante. Essa operação é essencial para produzir as sementes binárias que confirmam a justiça dos nossos algoritmos. Ao garantir que a semente mnemônica seja transformada com precisão em formato binário, mantemos a integridade e a transparência de nossos processos.
Além disso, essa função nos permite verificar que a semente mnemônica divulgada está alinhada com a semente binária do servidor, demonstrando claramente que nossos sistemas e algoritmos operam de maneira justa. Gerar uma semente binária a partir da mnemônica fornece um método confiável para assegurar a integridade geral e a justiça dos nossos procedimentos.
import CryptoJS from 'crypto-js';
export const generateBinarySeed = (mnemonic: string): string => {
const salt = 'mnemonic';
const iterationCount = 2048;
const derivedKeyLength = 64;
const seed = CryptoJS.PBKDF2(mnemonic, salt, {
iterations: iterationCount,
keySize: derivedKeyLength / 4, // Key size is specified in words, so divide by 4
hasher: CryptoJS.algo.SHA512,
});
const binarySeed = CryptoJS.enc.Hex.parse(seed.toString());
const hexStr = binarySeed.toString(CryptoJS.enc.Hex);
const bin2hex = (s: string) => {
let i;
let l;
let o = '';
let n;
s += '';
for (i = 0, l = s.length; i < l; i++) {
n = s.charCodeAt(i).toString(16);
o += n.length < 2 ? '0' + n : n;
}
return o;
};
const binStr = bin2hex(hexStr);
const shortBinStr = binStr.slice(0, 64);
return shortBinStr;
};
Convertendo a Frase Mnemônica em uma Semente Binária
Para derivar a semente binária a partir da frase mnemônica, empregamos o algoritmo PBKDF2 utilizando HMAC-SHA512. Nesse processo, a frase mnemônica atua como senha, enquanto o salt é a string literal 'mnemonic'.
Conversão Final para uma String Hexadecimal de 64 Caracteres
Em seguida, refinamos a string de 128 caracteres, convertendo-a para o formato hexadecimal convencional de 64 caracteres, tipicamente utilizado para gerar resultados.
Rotacionando Seu Par de Sementes
Sempre que um jogador atualiza sua semente do cliente, nosso sistema rotaciona simultaneamente a semente do servidor. Essa rotação do par de sementes garante que ambas permaneçam sincronizadas e sejam renovadas a cada jogo, reforçando a integridade do nosso processo de geração de números aleatórios.
Nonce
O nonce, que aumenta a cada aposta, opera em conjunto com as sementes do cliente e do servidor para produzir um resultado único para cada jogo. Esse processo garante tanto a justiça quanto a imprevisibilidade de cada aposta.
Conclusão
Esse método inovador para gerar sementes de servidor não só simplifica o processo de verificação dos resultados, mas também o torna mais acessível para os usuários. Ao preservar alta entropia e uma robusta segurança criptográfica — semelhante às técnicas utilizadas em carteiras de criptomoedas —, ele garante a integridade dos nossos jogos e reforça a confiança dos jogadores na justiça e transparência de nossa plataforma de jogos.