DocsReference
Limits and Non-Goals
What @galabet/fair 0.1.0 and GFS 1.0 leave out, what a claim of conformance does and does not cover, and every numeric limit in one table.
GFS 1.0 describes one thing: how a result is derived from a server seed committed in advance, a client seed and a nonce. @galabet/fair implements that derivation and the record that lets someone repeat it. If a site says it conforms, the most that can mean is that its results, recomputed from revealed seeds, match what the reference produces for the same inputs. Nobody issues that claim on the project's behalf. There is no certification, no registry and no badge to look up, so a conformance claim is worth what you can check yourself in the verifier.
Left to the Operator
The library never touches money. It has no wallet, no balance, no stake and no payout. A Dice roll of 56.12 comes back as a number, and whether that wins, what it pays and how the stake was taken are the operator's rules, which is why the game pages tell operators to publish them. The houseEdge in a Limbo record shapes the multiplier the mapper returns. It does not tell you the site pays that multiplier.
Nothing is stored and nothing is sent. There is no database layer, no session, no nonce counter and no HTTP client or server in the package. Reserving a nonce so that two requests can't share it, keeping the server seed secret while it is live, holding revealed seeds for as long as the records they explain, publishing commitments where a player can see them: all of that is the operator's code. Storing seeds and reserving nonces describes what that code has to get right.
Card games stop at the shuffle. blackjack and hilo return a deck in dealing order and the library has no dealing, no hand values and no guesses. This project also has no server-side engine that plays a concealed Blackjack or Hi-Lo round, where cards are shown one at a time while the rest of the deck stays hidden. Anyone offering those games builds that part.
Not Proved by Verification
A verified record shows that the result follows from the seeds and that the seed hashes to the commitment in the record. It does not show when the commitment was published. commit stamps publishedAt with the clock of the machine that called it, verifyRecord never reads at, and nothing in a record proves the player saw the commitment before betting. That evidence has to come from somewhere the operator can't rewrite afterwards.
The operator holds both seeds and the next nonce, so it can compute upcoming results before the player bets. It cannot change them once the commitment is out. GFS 1.0 writes that weakness down and leaves it in place. A randomness beacon as a third input is planned as GFS 1.1 and has not been written. Records may carry a beacon field today, and 0.1.0 neither uses it in derivation nor checks it; inspectRecord marks such a record incomplete.
Return to player, withdrawals and whether the site behaves honestly are outside the scheme altogether. What verification proves goes through this at length.
Gaps in 0.1.0
Errors are plain Error objects with a message. There are no error codes, no subclasses and no stable contract on the wording, so code that matches message text will break when a message is reworded. The errors page lists them all.
play checks less than you might assume. Limbo's houseEdge is not validated: 5 gives a result of 1 on every round and −1 turns the public inputs' 1.76 into 3.56. Wheel accepts any whole number of segments from 2 up, and a million is fine. A nonce has no upper bound, including values past Number.MAX_SAFE_INTEGER where neighbouring integers collide. Parameters a game doesn't read are ignored silently. The mappers don't check that a float lies between 0 and 1. inspectRecord is the strict path and verifyRecord is not: it throws on malformed fields where you might expect a reason. A bad rows or decks can surface as count must be a positive integer.
A signature made when the bet is placed stops verifying once serverSeed is added to the record, because the seed is part of the signed payload. The record hash changes at reveal for the same reason. Troubleshooting has the ways around it.
canonicalJson is the part of RFC 8785 that records need. For numbers, strings and sorted keys it produces what the RFC asks for. It does not reject a lone surrogate, which the RFC requires and which comes out here as "\ud800". It never calls toJSON, so a Date becomes {}, and it has been tested with record-shaped data only.
Signing uses Ed25519 through Web Crypto, which arrived late. The source lists Node 18.4, Chrome 113, Firefox 130 and Safari 17 as the minimums, against Node 18 for everything else. There is no fallback implementation. On an older platform the signing functions reject with the platform's own error.
Things That Do Not Exist
@galabet/fair is the only package. There is no command-line tool and no companion package for storage, for a beacon, for a web framework or for React. The repository README has a table of such packages and labels it as intended architecture. None of them has been written, and a comment in verifyRecord's source that points to a beacon package is pointing at a plan. The package itself is version 0.1.0 and has not been published to npm. The HTTP API in this repository is a demo and is not deployed anywhere; examples that call it target http://localhost:3000. "Fair 1.0", "Fair 2.0" and "Fair 3.0" are editions of the website and say nothing about the spec or the package, as Versioning explains.
Numeric Limits
Each row was read from the source and then tried on both sides of the boundary.
| Limit | Value | Enforced by |
|---|---|---|
| Server seed | exactly 64 lowercase hex characters | assertServerSeed, so everything that derives |
| Client seed | 1 to 64 UTF-16 code units, no :. 32 two-unit emoji fit, 33 don't | assertClientSeed |
| Nonce, cursor | whole number, 0 or more, no ceiling | deriveDigest |
Nonce, cursor, at in an inspected record | 0 to 9,007,199,254,740,991 | inspectRecord |
| Floats per digest | 8, so cursor n covers floats 8n to 8n + 7 | digestToFloats |
Plinko rows | 8 to 16, default 16 | plinko |
Mines mines | 1 to 24 on 25 tiles, default 3 | mines |
Keno draws | 1 to 40 from a pool of 40, default 10 | keno |
Blackjack and Hi-Lo decks | 1 to 8, default 1. Eight decks take 415 floats and end on cursor 51 | deck |
Wheel segments | 2 or more in play, 2 to 100 when inspected, default 10 | wheel, validateInspectionParams |
Limbo houseEdge | unchecked in play, 0 to 0.5 when inspected, default 0.01 | validateInspectionParams |
Crash houseEdge | 0 up to but not including 1, default 0.01. 0 to 0.999999 and required when inspected | crashResult, inspectRecord |
Crash salt | unchecked in crashResult, 1 to 1,024 characters when inspected | inspectRecord |
| Crash chain length | 1 to 10,000,000 | createCrashChain |
| Inspected record size | 65,536 bytes as UTF-8. 65,537 is refused | parseInspection |
| Inspected record nesting | 12 levels of objects and arrays, counting the record itself | parseInspection |
| Public key, signature, secret key | 64, 128 and 128 lowercase hex characters | verifySignature, sign, inspectRecord |
verifyRecord applies none of the inspection limits. A record of any size with any nesting goes straight to the hash functions, which is one more reason to put inspectRecord in front of anything a stranger can submit.
