DocsReference
Versioning and Changelog
The three version numbers in this project, what each one promises to someone holding old records, and the changelog for @galabet/fair.
Three numbers travel with this project and they don't move together. The spec has one, the npm package has one, and the test vectors have one. A fourth label, the "Fair 1.0" you may see on the website, is not a version of any of them.
import { readFile } from 'node:fs/promises';
import * as fair from '@galabet/fair';
import * as games from '@galabet/fair/games';
const pkg = JSON.parse(await readFile('packages/fair/package.json', 'utf8'));
const vectors = JSON.parse(await readFile('vectors/gfs-1.0.json', 'utf8'));
console.log('package', pkg.version);
console.log('spec ', fair.SPEC_VERSION);
console.log('vectors', vectors.spec, `with ${vectors.games.length} game rounds`);
console.log('exports', Object.keys(fair).length, 'from the main entry,', Object.keys(games).length, 'from /games');
package 0.1.0
spec GFS/1.0
vectors GFS/1.0 with 2240 game rounds
exports 43 from the main entry, 15 from /games
The example reads two files by path, so it runs from the root of the repository and not from an installed copy.
Spec Versions
The spec is numbered GFS/major.minor. GFS/1.0 is the string in every record's spec field and the value of SPEC_VERSION. The major number changes when derivation changes: the HMAC message, how the key is used, how bytes become floats, or what an existing mapper does with them. Any of those would give a different result for the same seeds, so a new major means old and new records can't be checked by the same code path.
A minor adds things and leaves existing results alone. New mappers and optional extensions belong there. The one planned is GFS 1.1, a public randomness beacon as a third input, and it has not been written. The beacon field already in the record type is a placeholder for it.
Version 0.1.0 of the library accepts exactly one spec string. verifyRecord answers anything else with the reason unsupported spec <value>, and inspectRecord throws. A record labelled GFS/1.1 would therefore fail in 0.1.0 even if nothing about its derivation differed.
Package Versions
@galabet/fair follows semantic versioning and is at 0.1.0. Under semver a 0.x version promises nothing about the API. Function names, signatures, error messages and the shape of VerifyOutcome can change in 0.2.0 without a major bump. Pin an exact version, and read the changelog below before moving.
What can't drift inside 0.x is the arithmetic. The package implements GFS 1.0, and the vectors hold it to that. The repository's vectors:check script regenerates all three vector files from the library and compares them with the ones checked in. Against 0.1.0 it prints vectors match, and a release that changed one derived result would make it fail with vectors drift.
The project README states an intent that a package's major version will track the spec major it implements. With 0.1.0 implementing GFS 1.0, that rule plainly isn't in force yet, and it can't be until the package reaches 1.0.0.
Vector Files
The files in vectors/ are named for the spec, not the package: gfs-1.0.json for the nine seed games, gfs-1.0-crash.json for the crash profile and gfs-1.0-sign.json for signatures. Each one carries "spec": "GFS/1.0" inside it. They are the definition of conformance for ports, which is covered on the test vectors page.
Published vectors are never edited. If one turns out to be wrong, the fix is a new spec minor with an erratum that names the bad vector, and the old file stays as it was. Rewriting it would let two ports both claim "passes GFS 1.0" while disagreeing with each other. No erratum exists. Nothing has been published yet either, and the rule starts at publication, so until the first release the files in the repository can still change.
Breaking Changes for Someone Holding Old Records
An operator's database and a player's saved receipts outlive any version of the library. For them, two things matter, and API churn is neither.
The first is the derived result. If the same serverSeed, clientSeed, nonce, game and params ever produce a different result or cursor, every stored record for that game stops verifying. That covers the obvious changes to derivation and mappers, and one that is less obvious: defaults. A Plinko record stored with params: {} verifies because the default is 16 rows. Change the default and the record fails though nobody touched it, which is why the record format page says to write parameters out.
The second is the canonical string. recordHash and every signature are computed over signingPayload(record). Changing key order, number formatting, the treatment of undefined, or which fields are left out of the payload changes every hash anyone has stored and invalidates every signature ever made. One known gap sits right on this line. A bet-time signature stops verifying once serverSeed is added, and the direct repair, leaving serverSeed out of the payload, is itself a change to the canonical string. It can't be slipped into a patch release.
Renamed functions, reworded errors, new checks in play and stricter validation are ordinary semver matters. They can break your build. They can't break a record.
Website Editions Are Not Versions
"Fair 1.0", "Fair 2.0" and "Fair 3.0" name editions of the Galabet website: its pages, its demo games, its design. They have no relation to GFS/1.0, which is the spec, or to 0.1.0, which is the package. A site edition can change without a single derived result changing.
Changelog
0.1.0 (Unreleased)
The first version. It has not been published to npm, so it has no release date, and npm install @galabet/fair does not work yet. It implements GFS 1.0, has no runtime dependencies, and ships ES module and CommonJS builds with type declarations.
Contents, grouped the way index.ts exports them:
- Seeds and commitments:
createServerSeed,createClientSeed,commit,verifyCommitment,assertServerSeed,assertClientSeed, with the constantsSERVER_SEED_BYTESandCLIENT_SEED_MAX_LENGTH. - Derivation:
deriveDigest,deriveFloats,digestToFloats,bytesToFloat, and the constantsBYTES_PER_DIGESTandFLOATS_PER_DIGEST. - Playing and verifying:
play,verifyRecord,GAMES,isGameName,SPEC_VERSION. - Records:
canonicalJson,signingPayload,recordHash. - Inspection of untrusted input:
inspectRecord,parseInspection,validateInspectionParams,MAX_RECORD_BYTES. - Ed25519 signing:
generateKeyPair,sign,verifySignature,signRecord,verifyRecordSignature. - Crash profile:
createCrashChain,crashGameHash,expandCrashChain,verifyCrashLink,crashResult. - Bytes and hashes:
toHex,fromHex,sha256,sha256Hex,hmacSha256,randomBytes,timingSafeEqualHex. - The
@galabet/fair/gamesentry: mappersdice,limbo,roulette,wheel,plinko,mines,keno, the card functionsdeck,blackjack,hiloandcardLabel, andshufflewithfloatsNeededForShuffle.GAMESandisGameNameare exported here as well. - Sixteen types, listed on the types page.
Known gaps:
- Errors have messages and no codes.
playdoes not validate Limbo'shouseEdge, does not cap Wheel'ssegments, puts no ceiling on the nonce and ignores parameters a game doesn't read.inspectRecordis stricter on all four.- A bad
rowsordeckscan surface ascount must be a positive integer. verifyRecordthrows on malformed fields and returns reasons only for well-formed records with wrong values.- A signature made at bet time fails once
serverSeedis added to the record, andrecordHashchanges at reveal. beaconis carried in records and never checked.canonicalJsoncovers the part of RFC 8785 that records need, and does not reject lone surrogates.PlinkoResultis declared and not exported.GameNameandGameParamscan't be imported from the/gamesentry.- The
exportsmap givesrequirethe ES module typings, so TypeScript undernode16resolution reports TS1479 for a CommonJS file even though the code runs.dist/index.d.ctsis built and not referenced. - Signing needs a platform with Ed25519 in Web Crypto, and there is no fallback.
Most of these are described with their symptoms on the troubleshooting and limits pages.
