사용자 지정 게임 - 공정성 입증
커스텀 게임에서 사용되는 방법
YEET Original의 모든 베팅은 라운드 시드에서 시작됩니다. 라운드 시드는 클라이언트 시드와 베팅의 nonce를 HMAC-SHA256으로 해싱한 값이며, YEET이 선택한 서버 시드가 키로 사용됩니다. 게임은 해당 베팅에 필요한 모든 무작위 값을 이 라운드 시드에서 추출합니다. 서버 시드는 플레이하기 전에 해싱된 상태로 표시되며, 시드 페어를 교체할 때 공개되므로 과거 베팅의 라운드 시드를 다시 계산하여 결과가 조작되지 않았는지 확인할 수 있습니다.
다른 검증 결과와 일치하지 않을 수 있음
다른 카지노용으로 제작된 검증 사이트는 무작위 숫자 생성 방식이 다를 수 있으므로, 해당 사이트에서 YEET 베팅을 확인하면 결과가 다르게 나올 수 있습니다. YEET 베팅은 게임의 공정성 창에 있는 검증 탭이나 이 페이지의 코드를 사용하여 확인하세요.
난수 생성
각 베팅은 두 단계로 생성됩니다. 첫째, 시드 페어와 베팅의 nonce를 통해 하나의 라운드 시드가 생성됩니다. 둘째, 라운드 시드가 무작위 바이트 스트림을 생성하면 게임은 그 스트림에서 편향되지 않은 정수를 읽어옵니다. 동일한 입력값은 항상 동일한 숫자를 생성하며, 이것이 바로 결과를 검증할 수 있는 이유입니다.
1. 입력값
- 서버에서 생성한 임의의 64자 16진수 문자열입니다.
- 언제든지 수정할 수 있는 문자열입니다.
- 게임을 플레이할 때마다 카운터가 증가합니다.
- 커서(Cursor) — 라운드 시드 메시지의 마지막 부분입니다. 항상 0입니다.
2. 라운드 시드
라운드 시드는 텍스트 clientSeed:nonce:0에 서버 시드(32바이트)를 키로 사용하여 HMAC-SHA256으로 생성됩니다. 결과는 32바이트이며, 64개의 16진수 문자로 표기됩니다. 한 번의 베팅은 정확히 하나의 라운드 시드를 가집니다.
async function deriveGameSeed(serverSeed, clientSeed, nonce) {
const seedBytes = hexToBytes(serverSeed);
if (seedBytes.length !== 32) {
throw new Error(`Server seed must be 32 bytes (64 hex characters), got ${seedBytes.length}.`);
}
const key = await crypto.subtle.importKey(
'raw', toArrayBuffer(seedBytes), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
const message = new TextEncoder().encode(`${clientSeed}:${nonce}:0`);
return bytesToHex(new Uint8Array(await crypto.subtle.sign('HMAC', key, message)));
}3. 라운드 시드로부터의 엔트로피
라운드 시드는 두 번째 HMAC-SHA256의 키가 됩니다. 각 호출은 8바이트 카운터(리틀 엔디안, 0부터 시작)에 서명하여 32바이트 블록을 제공하며, 카운터는 블록당 1씩 증가합니다. 읽기 작업은 현재 블록에서 순서대로 바이트를 가져옵니다. 블록에 남은 것보다 더 많은 바이트가 필요하면 나머지 블록은 건너뛰고 다음 블록이 생성됩니다.
class GameEntropy {
constructor(seedBytes) {
if (seedBytes.length !== 32) throw new Error('Seed must be exactly 32 bytes (64 hex characters).');
this.buffer = new Uint8Array(32);
this.offset = 32;
this.cursor = 0;
this.key = crypto.subtle.importKey(
'raw', toArrayBuffer(seedBytes), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
}
async next(count) {
if (count < 1 || count > 32) throw new RangeError('count must be between 1 and 32');
if (this.offset + count > 32) {
const message = new ArrayBuffer(8);
const view = new DataView(message);
view.setUint32(0, this.cursor >>> 0, true);
view.setUint32(4, Math.floor(this.cursor / 0x100000000) >>> 0, true);
const hash = await crypto.subtle.sign('HMAC', await this.key, message);
this.buffer.set(new Uint8Array(hash));
this.cursor++;
this.offset = 0;
}
const slice = this.buffer.slice(this.offset, this.offset + count);
this.offset += count;
return slice;
}
}4. 편향되지 않은 숫자
결과는 정수로 도출됩니다. readUint32는 4바이트를 부호 없는 32비트 정수(리틀 엔디안)로 읽습니다. unbiasedUint(entropy, n)은 0부터 n − 1까지의 숫자 중 하나를 동일한 확률로 반환합니다. n이 2의 거듭제곱인 경우 나머지를 바로 취합니다. 그렇지 않은 경우 32비트 내에 들어가는 n의 최대 배수보다 작은 값만 허용하며, 값이 그 이상일 경우 다시 읽어 어떤 결과도 편향되지 않도록 합니다.
async function readUint32(entropy) {
const bytes = await entropy.next(4);
return new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, true);
}
async function unbiasedUint(entropy, range) {
if (range <= 0) throw new Error('Range must be positive');
if (range === 1) return 0;
if ((range & (range - 1)) === 0) {
return (await readUint32(entropy)) % range;
}
const maxValue = 0xffffffff;
const threshold = maxValue - (maxValue % range) - 1;
for (;;) {
const raw = await readUint32(entropy);
if (raw <= threshold) return raw % range;
}
}5. 도우미
function hexToBytes(hex) {
if (typeof hex !== 'string') throw new TypeError('Hex value must be a string.');
const clean = hex.trim().replace(/^0x/i, '');
if (clean.length % 2 !== 0) throw new Error('Hex value must have an even length.');
if (!/^[0-9a-fA-F]*$/.test(clean)) throw new Error('Hex value contains non-hex characters.');
const bytes = new Uint8Array(clean.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
function bytesToHex(bytes) {
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
}
function toArrayBuffer(bytes) {
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}6. 베팅 검증하기
- 기록에서 베팅을 열고 클라이언트 시드, 논스(nonce), 해싱된 서버 시드를 복사하세요.
- 시드 쌍을 회전하여 서버 시드를 공개하고, 해당 SHA-256 해시값이 베팅 전 확인한 해시와 일치하는지 확인하세요.
- 서버 시드, 클라이언트 시드, 논스를 사용하여
deriveGameSeed를 실행하고 라운드 시드를 가져오세요. - 라운드 시드에서
GameEntropy를 생성하고, 게임의 변환(변환 섹션 참조)을 적용하여 결과와 베팅 내용을 비교하세요.
