룰렛에서 입증된 공정성이 작동하는 방식
Cloudbet에서 룰렛을 플레이하는 방법과, 매 스핀이 입증된 공정성을 갖추고 있는지 확인하는 방법을 알아보세요.
룰렛 플레이 방법
룰렛은 회전하는 공이 숫자가 매겨진 휠의 어디에 떨어질지에 베팅하는 클래식 카지노 게임입니다. 숫자 테이블에 한 개 이상의 베팅을 배치하면 휠이 돌아가고 공이 던져집니다. 공이 숫자가 매겨진 칸에 안착하면 그 숫자가 당첨 번호가 되며, 이를 포함하는 베팅은 배당금을 지급받고 나머지는 모두 패배합니다.
베팅 방법
몇 가지 다른 방식으로 결과에 베팅할 수 있으며, 같은 라운드에서 여러 베팅을 자유롭게 조합할 수 있습니다. 인사이드 베팅(특정 숫자)은 배당이 크지만 맞히기 어렵고, 아웃사이드 베팅(더 넓은 그룹)은 배당이 낮지만 더 자주 맞습니다.
- 스트레이트업 / 싱글 넘버 (35:1): 정확히 하나의 숫자(0~36, 아메리칸 룰렛에서는 00 포함)에 베팅합니다.
- 스플릿 (17:1): 인접한 두 숫자.
- 스트리트 (11:1): 한 줄의 세 숫자.
- 코너 (8:1): 모서리에서 만나는 네 숫자.
- 퍼스트 포 / 톱 라인 (유러피언 8:1 / 아메리칸 6:1): 0(아메리칸에서는 00 포함), 1, 2, 3을 포함합니다.
- 식스 라인 (5:1): 인접한 두 줄, 여섯 숫자.
- 컬럼 (2:1): 세 개의 세로줄 중 하나.
- 더즌 (2:1): 1~12, 13~24, 25~36.
- 레드/블랙, 오드/이븐, 하이/로우 (각각 1:1).
유러피언 룰렛 vs. 아메리칸 룰렛
Cloudbet은 두 가지 버전을 모두 제공합니다. 유러피언 룰렛은 싱글 제로를 사용하며(37가지 결과, RTP 97.30%), 아메리칸 룰렛은 두 번째 "00" 칸이 추가됩니다(38가지 결과, RTP 97%). 이로 인해 하우스 엣지가 약간 높아집니다. 어떤 버전을 플레이하고 있는지는 베팅 전에 명확히 표시됩니다. 두 버전 모두 미리 설정한 칩 값과 스핀 횟수로 연속 스핀이 가능한 오토플레이 모드를 지원합니다.
베팅이 완료되고 라운드가 결정되면, 당첨 베팅은 각자의 배당률에 따라 자동으로 지급되며 테이블은 다음 라운드를 위해 초기화됩니다.
이 페이지에서는 룰렛이 입증된 공정성을 갖춘 무작위 숫자 시퀀스를 당첨 번호로 바꾸는 과정을 다룹니다. 그 무작위 시퀀스 자체가 어떻게 생성되는지(서버 시드, 클라이언트 시드, 논스, 해싱, SHAKE256)에 대해서는 Cloudbet의 입증된 공정성 설명을 참고하세요.
무작위 숫자를 룰렛 결과로 바꾸는 과정
룰렛의 경우, 입증된 공정성 시스템은 당첨 번호가 사후에 정해지는 것이 아니라 검증 가능한 무작위성으로 선택된다는 것을 증명해야 합니다.
과정은 간단합니다. 유러피언 룰렛에서는 무작위 숫자 시퀀스에서 0에서 36 사이의 정수 하나가 추출되며, 이것이 당첨 번호가 됩니다. 아메리칸 룰렛에서는 추출된 숫자가 0에서 37 사이일 수 있으며, "37"은 더블 제로인 "00"에 매핑되고, 0에서 36까지의 결과는 그대로 사용됩니다.
당첨 번호 선택 방식
SHAKE256으로 생성된 무작위 바이트(Cloudbet의 입증된 공정성 페이지 참고)는 0에서 2³²−1 사이의 4바이트 부호 없는 정수로 분할됩니다. 룰렛의 당첨 번호는 37가지(유러피언) 또는 38가지(아메리칸) 결과 중에서 선택되므로, 아래 단계에서 P는 37 또는 38입니다.
이 P가지 결과 중에서 당첨 번호를 공정하게 선택하기 위해 거부 샘플링을 적용한 다음(입증된 공정성 작동 방식 참고), 그 결과 숫자를 P로 나눈 나머지를 구합니다. 이 나머지가 당첨 번호가 됩니다(아메리칸 룰렛의 경우 "37"은 "00"으로 표시됩니다).

당첨 번호가 생성되고 나면 서버 측에 저장되어 숨겨지며, 라운드가 시작된 이후에는 감지되지 않고 변경되거나 조작될 수 없습니다. 베팅이 완료되면 이 사전에 결정된 번호와 비교되어 라운드가 정산됩니다.
직접 룰렛 결과 검증하기
Cloudbet의 룰렛 구현(유러피언과 아메리칸 모두)과 정확히 일치하는 작은 Node.js 프로그램을 작성했습니다. 서버 시드, 클라이언트 시드, 논스를 입력해 결과를 재현해 보세요. (참고: Cloudbet에서 라운드의 서버 시드를 공개하려면 이미 새로운 시드 쌍으로 교체한 상태여야 합니다.)
의존성:
npm install js-sha3
코드:
// If you are running this yourself, then you can input your own data here
// You can then compare:
// 1) Cloudbet's provided Commitment to the Commitment outputted here. This proves that the Server Seed was not altered during gameplay.
// 2) Cloudbet's provided game outcome to the game outcome outputted here. This proves that your game's outcome was determined solely by the pre-game seeds and was not altered afterwards.
const Version = Object.freeze({ AMERICAN: "american", EUROPEAN: "european" }); // Here the possible versions are listed
let myServerSeed = 'your_server_seed'; // ✍️YOUR_INPUT✍️: Enter your (unhashed) Server Seed here
let myClientSeed = 'your_client_seed'; // ✍️YOUR_INPUT✍️: Enter your Client Seed here
let myNonce = 1; // ✍️YOUR_INPUT✍️: Enter your Nonce here
let myRouletteVersion = Version.AMERICAN; // ✍️YOUR_INPUT✍️: Enter your roulette version (either Version.AMERICAN or Version.EUROPEAN)
const usingCustomData = false; // Change this to true if you are using your own data
// Load libraries
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');
// Step 1: Generate a Server Seed and Commitment
// And generate a Client Seed - either by player input or by the player's browser
function generateServerSeed() {
let seed = crypto.randomBytes(32).toString('hex');
if (usingCustomData) { seed = myServerSeed; }
const commitment = sha3_256(seed);
return { 'serverSeed': seed, 'commitment': commitment };
}
function getClientSeed() {
let seed = crypto.randomBytes(32).toString('hex');
if (usingCustomData) { seed = myClientSeed; }
return seed;
}
// Step 2: Create a Round Signature with SHA3 using a Server Seed, Client Seed and Nonce
function createRoundSignature(serverSeed, clientSeed, nonce) {
return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}
// Step 3: Create a SHAKE256 stream generator from a Round Signature.
// To simplify this example program, this function doesn't create a stream, but instead a sequence of custom length.
function* createRandomNumberSequence(numbersToGenerate, roundSignature) {
const numOfBytes = 4 * numbersToGenerate; // 4 bytes (32 bits) per random number we generate
const byteStream = shake256.create(8 * numOfBytes); // shake takes bits as input
byteStream.update(roundSignature); // Use the Round Signature to seed the generator
const buf = Buffer.from(byteStream.digest()); // The Buffer class lets us package every 4 bytes into one 32-bit integer
for (let i = 0; i < numbersToGenerate; i++) {
yield buf.readUInt32BE(i * 4); // Read buf in 4-byte steps
}
throw new Error("No more numbers in the random number sequence! Fix: make 'numbersToGenerate' larger.");
}
// Step 4: Draw from the random number sequence to calculate the game result
function getGameResult(rouletteVersion, rng) {
let numOfOutcomes = 0;
if (rouletteVersion === Version.EUROPEAN) {
numOfOutcomes = 37;
} else if (rouletteVersion === Version.AMERICAN) {
numOfOutcomes = 38;
}
let result = String(sample(numOfOutcomes, rng))
// Convert the result '37' to '00'
if (result === '37') {
result = '00';
}
return result;
}
// Sample a number from P number of outcomes using Rejection Sampling
function sample(P, rng) {
const maxAcceptable = Math.floor(0x100000000 / P) * P;
// Rejection sampling
let rand;
do {
rand = rng.next().value;
} while (rand >= maxAcceptable);
// Modulo operation
const sampled_number = rand % P;
return sampled_number;
}
// Example usage
(function main() {
let nonce = 1;
let rouletteVersion = Version.AMERICAN;
if (usingCustomData) { nonce = myNonce; rouletteVersion = myRouletteVersion; }
const { serverSeed, commitment } = generateServerSeed();
const clientSeed = getClientSeed();
const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
const numbersToGenerate = 1 * 2; // We need 1 random number, but there is an improbable case we'll need two
const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);
const value = getGameResult(rouletteVersion, rng);
console.log('📝 Commitment (shown before the game):', commitment);
console.log('🔒 Server Seed (revealed after the game):', serverSeed);
console.log('🎲 Client Seed:', clientSeed);
console.log('🔢 Nonce:', nonce);
console.log(`🎯 Winning Number (in the ${rouletteVersion} version):`, value);
})();

