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.

versions.mjs
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');
Output
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 constants SERVER_SEED_BYTES and CLIENT_SEED_MAX_LENGTH.
  • Derivation: deriveDigest, deriveFloats, digestToFloats, bytesToFloat, and the constants BYTES_PER_DIGEST and FLOATS_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/games entry: mappers dice, limbo, roulette, wheel, plinko, mines, keno, the card functions deck, blackjack, hilo and cardLabel, and shuffle with floatsNeededForShuffle. GAMES and isGameName are exported here as well.
  • Sixteen types, listed on the types page.

Known gaps:

  • Errors have messages and no codes.
  • play does not validate Limbo's houseEdge, does not cap Wheel's segments, puts no ceiling on the nonce and ignores parameters a game doesn't read. inspectRecord is stricter on all four.
  • A bad rows or decks can surface as count must be a positive integer.
  • verifyRecord throws on malformed fields and returns reasons only for well-formed records with wrong values.
  • A signature made at bet time fails once serverSeed is added to the record, and recordHash changes at reveal.
  • beacon is carried in records and never checked.
  • canonicalJson covers the part of RFC 8785 that records need, and does not reject lone surrogates.
  • PlinkoResult is declared and not exported. GameName and GameParams can't be imported from the /games entry.
  • The exports map gives require the ES module typings, so TypeScript under node16 resolution reports TS1479 for a CommonJS file even though the code runs. dist/index.d.cts is 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.