DocsSecurity

Threat Model

Who can attack a GFS 1.0 round, what each of them might try, and whether the scheme prevents it, detects it or does nothing about it.

GFS 1.0 defends one property: a result was fixed by inputs chosen before the bet. It defends it mostly by detection. Very little in the scheme stops an attack from happening. What it does is leave a mismatch behind that a later check will find, provided somebody runs the check and holds what the check needs.

The table uses three verdicts. "Prevents" means the attempt fails whether or not anyone is watching. "Detects" means the attempt works at the time and is exposed afterwards, under conditions the last column states. "Nothing" means GFS 1.0 has no mechanism that touches it, and a fully verified record is compatible with the attack having happened.

Nothing in the repository records an independent audit of the library or the specification, and this page is not one.

Actors and Attempts

ActorAttemptGFS 1.0Condition or reason
OperatorReveal a different server seed from the one committedDetectsThe seed won't hash to the commitment. The player must hold a commitment they saw before betting. Finding a second seed with the same SHA-256 is the part that's prevented
OperatorReport a result the seeds don't giveDetectsOnly after the seed is revealed. Until then the record can't be checked at all
OperatorCalculate upcoming results while a seed is liveNothingThe operator holds the server seed, the client seed and the next nonce. That's every input
OperatorChoose the server seed after learning the client seedNothingA record doesn't show which came first. The player's only defence is to set a client seed after the commitment is on screen
OperatorBackdate a commitment, or show it only after the betNothingRecords carry a commitment and no evidence of when it was published
OperatorNever rotate, so the seed is never revealedNothingNo deadline exists in the spec. An unrevealed seed fails no check. It prevents every check
OperatorWithhold or delete recordsNothingA record the player never received can't be verified by the player
OperatorPay less than the result is worth, or not payNothingStake, payout and balance are outside the record and outside the library
OperatorAdvertise a return the games don't deliverNothinghouseEdge is a declared input to Limbo and Crash, not an audited one. The other games declare nothing
OperatorMisstate when a Crash cash-out arrivedNothingThe chain fixes the crash point. It knows nothing about clicks
OperatorPublish the Crash chain that pays worst out of manyPreventsThe salt must not exist when the chain is published. This holds as far as the salt source is outside the operator's control
Insider at the operatorRead a live seed from storage, logs or a backup and bet on known resultsNothingEvery record from such a session verifies. There's no mismatch to find
Insider at the operatorRewind a nonce counter so known results come round againNothingVisible only to someone comparing records for a repeated nonce under one commitment
PlayerRecover the server seed from the commitment or from past resultsPreventsSHA-256 preimage resistance and HMAC-SHA256 key secrecy, over a 256-bit seed from the platform CSPRNG. Holds only while the operator keeps the seed secret
PlayerGet a second bet on a seed pair and nonce already seenNothingplay is stateless and derives the same triple as often as it's asked. The counter belongs to the operator
PlayerPresent a fabricated record of a winDetects, partlyA false result fails against the real seed. A record built from the player's own seed and commitment passes every check. Only a signature under a key known to be the operator's separates the two
PlayerRead a concealed result mid-round, such as a Mines boardNothingConcealment is server bookkeeping. The scheme has no concept of a round in progress
Network attackerAlter the commitment or a record in transitNothingGFS has no transport layer. TLS is the operator's job. A signed record exposes tampering if the public key arrived by another route
Verifier hostServe a verifier that reports a match for anything, or hides oneNothingA web verifier is code from a server. Its verdict is worth what the host is worth

Count the verdicts. Two rows say "prevents", three say "detects", and the remaining fourteen say "nothing". That's an accurate picture of commit-and-reveal as the industry runs it, which is what 1.0 set out to describe.

Every Detection Rests on a Gap

Look at what the "detects" rows require. The seed has to be revealed. The checker has to hold a commitment from before the bet. The checker has to have the record. The software doing the checking has to be honest. Each of those requirements appears elsewhere in the same table with "nothing" beside it.

So an operator who wants to avoid being caught doesn't need to break SHA-256. It needs to not rotate, or not hand over the record, or show the commitment late, and none of those leaves a failed check. They leave an absence, and an absence is harder to take to a regulator than a mismatch. A player or auditor should treat "I can't verify this" as a finding in its own right and not as a neutral state.

Operator-Side Gaps

Advance knowledge, payouts, advertised return and Crash cash-out timing are covered on What verification proves, and the Crash case again under Limits of Verification. The rest are less often written down.

The Insider

Advance knowledge is usually discussed as something "the casino" has, and dismissed because the house gains little from knowing a result it can't change. That argument treats the operator as one party. It isn't. Anyone who can read the session store, a database replica, a backup, a log line or a crash dump containing a live seed can compute every coming result for that session and play it, or pass it to someone who will. Their records verify, because nothing was altered. GFS 1.0 offers no detection here at all. The defences are access control and the rules on Protecting the Server Seed, and they're ordinary security work.

Seed Chosen After the Client Seed

The client seed is supposed to be the player's guarantee that the operator couldn't pre-arrange results. It's only that if the server seed was committed before the operator learned the client seed.

At session creation in Galabet's demo API the server generates both seeds, so for a player who never changes the default, the operator chose every input. When the player does change their client seed, the demo retires the server seed and draws a new one, and it draws it after the new client seed has arrived (setClientSeed in apps/api/src/demo/demo.service.ts). The demo takes that seed from the CSPRNG and doesn't inspect it. A dishonest operator running the same sequence could draw a thousand, look at what each gives against the client seed it now knows, and keep the one it likes. A run of low Limbo results is bad for the player whatever they bet. The resulting records are indistinguishable from honest ones.

The Crash profile closes the equivalent hole with its salt. The single-player profile has no counterpart in 1.0. An operator can narrow it today by showing the next commitment before the player submits a new client seed, as Publishing Commitments describes, though that still depends on the player having saved it.

Refusing to Reveal

The spec says what rotation is and sets no deadline for it. A casino can keep one seed live for a year, or for ever. Every record under that seed stays uncheckable and none of them fails. The same goes for selective refusal: rotate promptly for the player who lost, stall for the player who is disputing a result, lose the seed in a "storage incident". Rotation and Reveal recommends letting the player trigger rotation. Nothing enforces it.

Withholding Records

The player can verify the records the player has. If the history endpoint omits a bet, or the account is closed and the history with it, the remaining evidence is whatever the player saved. Galabet's demo keeps the last 100 records per session and deletes them with the session. That's a demo's budget, and it's also a reminder that retention is the operator's decision.

Checking-Side Gaps

Commitment timing has its own page. Two others sit with the person doing the verifying.

A signature binds a record to a public key and says nothing about whose key it is. The signer field travels inside the record, so a forger supplies their own. Until the operator's key is published somewhere the forger can't write to, a valid signature proves only that somebody signed. Signing Records has the mechanics, including the fact that a bet-time signature stops verifying once serverSeed is added.

Then the verifier. Galabet's verifier runs in the browser and is served by Galabet. An operator's own "verify" page is served by the operator, the one party with a motive to show green. In both cases you're trusting the host to have sent honest code on that request. This is the one gap a reader can close today without waiting for anybody. Run verifyRecord from a copy of the library you installed yourself, or write your own port, which for Dice is about a dozen lines, and prove the port against the 2,240 game vectors in vectors/gfs-1.0.json. The core package has no runtime dependencies and does its hashing through Web Crypto, both of which you can confirm in packages/fair/package.json and src/crypto.ts, so there's little to review.

Measures Outside 1.0

None of the following exists in GFS 1.0 or in @galabet/fair 0.1.0. They're listed so the gaps above have a named remedy, not because a reader can switch them on.

A beacon input would be a third value in the HMAC message, taken from a public randomness source at a round that didn't exist when the commitment was published. It removes advance knowledge from the operator and from any insider, and it makes choosing a seed against a known client seed pointless. This is what GFS 1.1 is planned to specify. It's unwritten. The record type already has a beacon field, and 0.1.0 neither reads nor verifies it.

Under third-party commitment escrow the operator lodges each commitment with a party it doesn't control before using the seed, and that party timestamps it. Timing becomes checkable by someone other than the player. If the lodged commitment comes with a stated reveal deadline, a refusal to reveal turns from an absence into a missed deadline with a date on it. It still can't force the reveal.

The third is signed records published at bet time. If every record is signed when the bet is placed and sent somewhere the operator can't later edit, withholding and rewriting history both become visible, and a player's fabricated record has nothing to match. This needs the key-ownership problem solved first, and it needs the signed object to include the stake and terms if it's to help with payout disputes, which a GFS 1.0 record does not.

Nothing on that list addresses payout honesty, advertised return or cash-out timing. Those belong to licensing, audit and the operator's own logs, and a fairness scheme that claimed to cover them would be overclaiming.