GALABET FAIR STANDARD DRAFT

GFS 1.0

The rules behind
a Galabet result.

From the seed commitment to the final record. These are the calculations another implementation needs to reproduce.

Draft reference · GFS/1.0 calculation profile
Fair product editions have separate version numbers.

Galabet fox cartographer following a four-point byte chart through a transparent observatory instrument
PUBLIC INPUTS / REPRODUCIBLE OUTPUT

01 / INPUT CONTRACT

What goes
into the hash?

Keep the exact strings. A hex-looking seed is used as UTF-8 text, not decoded into 32 bytes before hashing.

Single-player / public fixture
serverSeed REVEALED
5c1f7d3e8a2b4c6d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d
clientSeed
galabet
nonce
42
cursor
0
HMAC message
galabet:42:0
A

Commit before play.

Publish SHA-256 of the server seed text. Keep evidence that the commitment existed before the round. A matching hash alone cannot establish when it was published.

B

Advance the nonce.

Use a new nonce for each new round under a seed pair. Store that counter durably. The calculation library does not manage sessions or prevent reuse.

C

Reveal after retirement.

Retire the seed before disclosing it. A live server seed exposes future results when the other inputs are known. This page uses an intentionally public fixture.

String constraints and the published commitment

The server seed is 64 lowercase hexadecimal characters. The client seed is a nonempty string of at most 64 JavaScript UTF-16 code units and cannot contain a colon. Nonce and cursor use nonnegative integer values; portable records must keep them within JavaScript’s safe integer range.

SHA-256(serverSeed)ab37723062965715c5e6eeb54816f909a8e787bd3bfaee67a3cc0a3b90707de7

02 / FOLLOW FOUR BYTES

143. 168. 234. 224.
That becomes 56.12.

HMAC-SHA256 uses the server seed as its key and clientSeed:nonce:cursor as its message. Read each digest from left to right, in groups of four bytes.

FIRST FOUR DIGEST BYTES

b0143
b1168
b2234
b3224
143/256 + 168/256² + 234/256³ + 224/256⁴

FRACTION / [0, 1)

0.5611712262034416floor(f × 10001) / 100

DICE RESULT

56.12cursor 0

Eight floats per digest.

Cursor 0 supplies floats 0–7. Cursor 1 supplies 8–15. Continue until the mapper has enough; the record stores the highest cursor consumed.

cursor = floor((floatCount − 1) / 8)
View the full digest8fa8eae01ccd43120b5028acd55241e63f01343d81935389da84578897a5fe39

Consecutive four-byte groups are interpreted in big-endian order, divided by 2³². Preserve binary64 arithmetic and the mapper’s rounding order when reproducing the reference implementation.

03 / GAME MAPPINGS

One result has
different shapes.

Nine games consume the float stream through play(). Crash has a separate calculation. Open a rule to see the mapping and the boundary between outcome generation and application logic.

GFS/1.0 mappings · d = number of decks
GameInput consumedReturned result / rule
Dice1floats
A number, 0.00–100.00
floor(f × 10001) / 100

Both endpoints are possible. The target, comparison operator and payout are application rules; they do not change the generated roll.

Limbo1floats
A multiplier, at least 1.00
raw = 10⁸ / (f × 10⁸ + 1)
max(1, floor(raw × (1 − edge) × 100) / 100)

The default houseEdge is 0.01. Preserve the operation order and round down to hundredths. The declared edge belongs with the record.

Plinkorowsfloats
A path and bucket index
f < 0.5 → 0 (left)
f ≥ 0.5 → 1 (right)
bucket = sum(path)

Rows range from 8 to 16; the default is 16. Consume one new float per row. A payout table is separate from the path and bucket.

Mines24floats
Sorted tile indices, 0–24
shuffle(25).slice(0, mines).sort(numeric)

Shuffle all 25 tiles, then select 1–24 mines (default 3). Always consume 24 floats, even when selecting only one mine. Rows and columns are a rendering choice.

Roulette1floats
A pocket, 0–36
floor(f × 37)

This is a 37-pocket mapping. Wheel order, colour, bet types and payouts belong to the application.

Blackjack52d − 1floats
The full dealing order
shuffle(52 × decks) → card labels

Use 1–8 decks (default 1). Base suit order is clubs, diamonds, hearts, spades; ranks run A, 2…9, T, J, Q, K. The mapper produces a deck, not a hand score or a settlement.

Hi-Lo52d − 1floats
The full dealing order
Same deck mapping as Blackjack

The default single deck consumes 51 floats. Reveals read the deck in order; rank comparisons and tie rules are application rules. Unrevealed future cards must stay on the server.

Keno39floats
Sorted numbers, 1–40
shuffle(40).slice(0, draws)
.map(i → i + 1).sort(numeric)

Draw 1–40 numbers (default 10). Always shuffle the full pool with 39 floats. Selection order is not preserved in the sorted result.

Wheel1floats
A zero-based segment index
floor(f × segments)

The default is 10 segments. Store the ordered payout table and its version separately: an index does not carry a multiplier.

Crash52 bitsof the HMAC digest
A shared crash multiplier
HMAC-SHA256(gameHash, salt) → first 13 hex digits

A separate hash-chain profile, not a play() mapper. Calculation uses a declared edge and the exact integer-to-number steps in the Crash section below.

04 / MULTIPLAYER PROFILE

Publish the end.
Reveal backwards.

A Crash round uses a game hash and salt. It does not use a player’s client seed or nonce. Every participant follows the same endpoint.

GAME 2hN−2revealed later
SHA-256 →
GAME 1hN−1first reveal
SHA-256 →
PUBLISHED FIRSThNchain commitment

Check the next link.

Hash the revealed game hash as UTF-8 text. It must equal the previous published value. The terminating hash anchors the first round.

Keep the salt’s provenance.

A calculation can reproduce a result without proving that its salt was unpredictable. Record the chain commitment before the chosen external salt becomes known, and retain that evidence separately.

Crash implementation guide ↗
Exact reference arithmetic / JavaScript
const e = 1n << 52n;
const h = BigInt('0x' + digestHex.slice(0, 13));
const scaled = (100n * e * 1_000_000n) / (h + 1n);
const cents = Number(scaled) / 1_000_000;
const result = Math.max(1, Math.floor(cents * (1 - houseEdge)) / 100);

Here digestHex is HMAC-SHA256 with gameHash as the text key and salt as the text message. The first 13 hex digits supply 52 bits. Preserve the integer truncation to six decimal places before applying the edge.

Flight uses this endpoint calculation. Betting windows, cash-out acceptance, receipts and balance settlement are server rules beyond this calculation profile.

05 / THE VERIFICATION RECORD

The number needs
its working.

A completed record gives an independent verifier the inputs and parameters used to produce the claimed result.

dice-record.json / unsigned public fixture
{
  "spec": "GFS/1.0",
  "profile": "single-player",
  "game": "dice",
  "params": {},
  "serverSeed": "5c1f7d3e8a2b4c6d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "commitment": "ab37723062965715c5e6eeb54816f909a8e787bd3bfaee67a3cc0a3b90707de7",
  "clientSeed": "galabet",
  "nonce": 42,
  "cursor": 0,
  "result": 56.12,
  "at": 0
}
spec · profile · game
Identify the calculation and its input model. A Fair product edition is not a GFS version.
params · nonce · cursor
Preserve the mapper parameters, round counter and highest digest cursor. A verifier recomputes the cursor; it is not a starting offset.
commitment · serverSeed
Connect the revealed input to the saved commitment. Before retirement, keep the server seed private.
result · at
Store the claimed outcome and Unix millisecond timestamp. This fixture uses at: 0; it is not a live round.

OPTIONAL SIGNATURE / EXACT PAYLOAD

Sign the record you actually share.

Remove only signature and signer, canonicalize the remaining object, and sign its UTF-8 bytes. Object keys sort in JavaScript UTF-16 order; arrays retain order and numbers use JavaScript JSON serialization. Adding a revealed seed changes the signed payload.

A valid signature proves agreement with the supplied key. Establishing who owns that key is a separate check.

Signing and key handling ↗

06 / IMPLEMENTATION CHECK

Start with one vector.
Then run the set.

A matching example is a useful first check. A port must also agree on digests, cursor progression, every game result and canonical record bytes.

check.mjs / with @galabet/fair installed
import { play, verifyCommitment } from '@galabet/fair';

const serverSeed = '5c1f7d3e8a2b4c6d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d';
const commitment = 'ab37723062965715c5e6eeb54816f909a8e787bd3bfaee67a3cc0a3b90707de7';

const { result, cursor } = await play({
  game: 'dice', serverSeed, clientSeed: 'galabet', nonce: 42,
});

console.log(await verifyCommitment(serverSeed, commitment));
console.log(result, cursor);
EXPECTED OUTPUT
true
56.12 0