Pawnshake v0.1.0 · draft

Reference

Libraries

Three implementations, one set of test vectors: Go for servers, TypeScript for the web, Dart for Flutter apps. All at 100 % test coverage, all byte-identical.

Install

LanguagePackageInstall
Gogithub.com/PawnTown/pawnshake/gogo get github.com/PawnTown/pawnshake/go@latest
TypeScript / JSnpm pawnshakenpm install pawnshake
Dart / Flutterpub pawnshakedart pub add pawnshake

What is in them

Identity

Device certificates, issue and verify; ZIP-215 Ed25519.

Start

Rules profiles, CHALLENGE / ACCEPT / START, colours, the genesis hash.

Records

Wire format, body, header, record ids, signing, decoding, arg checks.

Chains

Prev and seen links, duplicates, fork detection, the canonical order.

Server

Heads, anchor receipts, the lag quota.

Moves

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.

FileCovers
vectors/core.jsonvarints, certificate, profile, genesis (hash, colours, keys), records (body, header, id, signature, wire, head), receipt, canonical order
vectors/movecode.jsonsorted legal moves for 13 positions
vectors/zip215.jsonthe 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.