Install
| Language | Package | Install |
|---|---|---|
| Go | github.com/PawnTown/pawnshake/go | go get github.com/PawnTown/pawnshake/go@latest |
| TypeScript / JS | npm pawnshake | npm install pawnshake |
| Dart / Flutter | pub pawnshake | dart pub add pawnshake |
What is in them
Device certificates, issue and verify; ZIP-215 Ed25519.
Rules profiles, CHALLENGE / ACCEPT / START, colours, the genesis hash.
Wire format, body, header, record ids, signing, decoding, arg checks.
Prev and seen links, duplicates, fork detection, the canonical order.
Heads, anchor receipts, the lag quota.
Move codes: sort, encode, decode.
TypeScript: sign and check a move
import { Chain, WHITE, BLACK, encodeMove, newGenesis, wire, decodeWire } from "pawnshake";
// both sides build the chain from the same three genesis messages
const gen = newGenesis(challenge, accept, start, serverKeys, nowSeconds);
const chain = new Chain(gen.hash, [gen.white, gen.black, null]);
// White plays e4: the move code is its index among the legal moves
const [code, legal] = encodeMove(legalUcis, "e2e4"); // 13, 20
const rec = chain.signNext(deviceSeed, chain.next(WHITE, atMs, code), () => [1, legal]);
send(wire(rec)); // ~70 bytes
// Black's side: decode, verify and add (throws on a fork)
const { dup } = chain.add(decodeWire(WHITE, bytes), () => [1, legal]);
Go: verify on the server
import ps "github.com/PawnTown/pawnshake/go"
gen, err := ps.NewGenesis(challenge, accept, start, serverKeys, now)
white, black := gen.Keys()
chain := ps.NewChain(gen.Hash, white, black, serverPub)
rec, err := ps.DecodeWire(ps.White, wireBytes)
sealed, dup, err := chain.Add(rec, moveInfo) // a *ps.ForkError means: the signer loses
for _, r := range chain.Order() { // the canonical order
apply(r)
}
Dart: in a Flutter app
import 'package:pawnshake/pawnshake.dart';
final (code, legal) = encodeMove(legalUcis, 'e2e4');
final rec = chain.signNext(seed, chain.next(white, atMs, code), (_) => (ply, legal));
socket.add(wire(rec));
Conformance vectors
Every library reads the same files in its test suite. A new implementation is conformant when it reproduces them byte for byte.
| File | Covers |
|---|---|
vectors/core.json | varints, certificate, profile, genesis (hash, colours, keys), records (body, header, id, signature, wire, head), receipt, canonical order |
vectors/movecode.json | sorted legal moves for 13 positions |
vectors/zip215.json | the 196 ZIP-215 Ed25519 edge cases; all must verify |
# Go generates core.json and movecode.json
cd go && go test . -update && go test ./movecode -update
# every suite checks them, with coverage gates at 100 %
cd go && go test -cover ./...
cd js && npm run coverage
cd dart && tool/coverage.sh
Used in
WhitePawn signs its online games with Pawnshake: the play server (Go) checks every record and the web app (TypeScript) signs and acknowledges; the Flutter app (Dart) is next. Using it somewhere else? Open an issue on GitHub.