Algoritmo de sorteio (versão v1)
Como o Sorteador sorteia: semente de 32 bytes, HMAC-SHA256 por contador, inteiros uniformes por rejeição e Fisher-Yates, com código para refazer.
Esta página descreve a versão v1 do algoritmo, a mesma que roda no servidor e na verificação do seu navegador. Mudar qualquer detalhe criaria uma versão v2; o v1 nunca é alterado, e cada resultado informa a versão usada.
1. A semente
Antes de cada sorteio, o servidor gera 32 bytes aleatórios com o gerador criptográfico do sistema. A semente é escrita em hexadecimal minúsculo, com 64 caracteres. Ela é gravada no resultado e exibida no link do resultado.
2. O fluxo de bytes
Todo o acaso do sorteio vem de um fluxo de bytes derivado da semente. O bloco de número i, começando em zero, é o HMAC-SHA256 que usa os 32 bytes da semente como chave e o contador i, escrito em 8 bytes na ordem big-endian, como mensagem. Cada bloco tem 32 bytes e é lido em inteiros de 32 bits sem sinal, big-endian, de quatro em quatro bytes. Quando o bloco acaba, passa-se ao seguinte.
3. Inteiros uniformes por rejeição
Para obter um inteiro em [0, n), calcula-se limite = floor(2^32 / n) * n. Lê-se um inteiro de 32 bits do fluxo. Se ele for maior ou igual ao limite, é descartado e outro é lido. Caso contrário, o resultado é o valor módulo n. A rejeição elimina o viés do resto da divisão: todos os valores têm exatamente a mesma chance.
4. Lista de participantes
A lista é normalizada: cada linha perde os espaços das pontas e as linhas vazias são descartadas. A ordem informada é preservada. A impressão digital da lista é o SHA-256, em hexadecimal, das linhas normalizadas unidas por quebras de linha.
5. Cada ferramenta
Cara ou coroa: sorteia-se um inteiro em [0, 2). Zero é cara; um é coroa.
Números com repetição: para cada número pedido, o resultado é o mínimo mais um inteiro uniforme em [0, tamanho), em que tamanho = máximo - mínimo + 1.
Números sem repetição: Fisher-Yates parcial sobre o intervalo, com as trocas guardadas num mapa esparso. Para i de 0 até quantidade - 1, sorteia-se j = i + inteiro uniforme em [0, tamanho - i), troca-se a posição i com a j, e o valor da posição i, somado ao mínimo, é o próximo número.
Nomes e roleta: a lista normalizada é embaralhada por Fisher-Yates. Para i do último índice até 1, sorteia-se j em [0, i + 1) e trocam-se as posições i e j. Em nomes, os vencedores são os primeiros itens da lista embaralhada, na quantidade pedida. Na roleta, o vencedor é o primeiro item. A animação não influencia o resultado.
Times: a lista é embaralhada do mesmo modo e distribuída em rodízio: o participante de posição i vai para o time de número i módulo a quantidade de times. Os times diferem em no máximo um participante.
6. Código de referência
O código abaixo refaz um sorteio a partir da semente, da lista e dos parâmetros publicados no link do resultado. Roda em navegadores e no Node.js 20 ou superior, que têm crypto.subtle. É uma versão enxuta do código-fonte do Sorteador.
const unhex = (s: string) => Uint8Array.from(s.match(/../g) ?? [], (h) => parseInt(h, 16));
async function criarFluxo(semente: string) {
const chave = await crypto.subtle.importKey(
'raw', unhex(semente), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
let contador = 0n;
let bloco = new Uint8Array(0);
let pos = 0;
const u32 = async () => {
if (pos + 4 > bloco.length) {
const msg = new Uint8Array(8);
new DataView(msg.buffer).setBigUint64(0, contador++); // big-endian
bloco = new Uint8Array(await crypto.subtle.sign('HMAC', chave, msg));
pos = 0;
}
const v = new DataView(bloco.buffer, bloco.byteOffset + pos, 4).getUint32(0);
pos += 4;
return v;
};
// Inteiro uniforme em [0, n) por rejeição.
return async (n: number) => {
const limite = Math.floor(2 ** 32 / n) * n;
let v = await u32();
while (v >= limite) v = await u32();
return v % n;
};
}
// Embaralhamento Fisher-Yates sobre a lista normalizada.
async function embaralhar(lista: string[], semente: string) {
const proximo = await criarFluxo(semente);
const a = lista.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = await proximo(i + 1);
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
// Exemplo: nomes (2 vencedores), times (3 times) e cara ou coroa.
async function exemplo(semente: string, lista: string[]) {
const emb = await embaralhar(lista, semente);
const vencedores = emb.slice(0, 2);
const times: string[][] = Array.from({ length: 3 }, () => []);
emb.forEach((p, i) => times[i % 3].push(p)); // rodízio
const lado = (await (await criarFluxo(semente))(2)) === 0 ? 'cara' : 'coroa';
return { vencedores, times, lado };
}
7. Como conferir
Abra o link do resultado, copie a semente, a lista de participantes e os parâmetros, e execute o código. Se a ferramenta, a versão do algoritmo e os dados forem os mesmos, os vencedores serão idênticos. A página do resultado faz esse cálculo por você no botão de verificação.
8. O que o método garante, e o que não garante
Garante que o resultado é reproduzível por qualquer pessoa e que, sem conhecer a semente de antemão, ninguém prevê o vencedor. Não garante que a lista seja honesta: se alguém deixa um participante de fora, o sorteio é justo apenas entre os que ficaram. Por isso o link do resultado guarda a impressão digital da lista e a conferência de nome.