스포츠

게임

일반

사용자 지정 게임 - 공정성 입증

커스텀 게임에서 사용되는 방법

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를 생성하고, 게임의 변환(변환 섹션 참조)을 적용하여 결과와 베팅 내용을 비교하세요.