Pawnshake v0.1.0 · draft

Protocol

Byte layouts

Every payload, field by field. The coloured dumps are the real bytes from the conformance vectors that the Go, TypeScript and Dart libraries all reproduce. Hover or tap a byte to see which field it belongs to.

magic tag hash number op args key / nonce text signature

Conventions

  • Fixed integers are big-endian (u16, u32, u64).
  • Varints are unsigned LEB128 and must be minimal: 00 is 0, e8 07 is 1000. A longer encoding of the same number is rejected, so every message has exactly one byte form.
  • Magic tags open every signed message (PSREC001, PSCHAL01, PSCERT01 ...). A signature for one kind of message can never verify as another, and a new layout gets a new tag.
  • Hashes are SHA-256, signatures Ed25519 (64 bytes), verified under ZIP-215 rules.

A move on the wire

White's first move, 1. e4, exactly as it travels from White's phone to Black's: sequence 1, nothing seen yet, signed 1000 ms after START, move code 13.

Wire record: 1. e469 bytes
00000100e8070d44fae652fdb8dbfee518d7
001012f32d796234794fc1714893d63877fb
0020171abbf997a3139789432ebaf8824d2c
003049b4e667d15b17f3a121ab8181072e83
0040f926995805

seq 1seen 1at 2op 1signature 64

Black's acknowledgement is the same shape with op 230 (ACK) and seen = 1:

Wire record: Black's ACK of e470 bytes
00000101ffc304e681ada2be5a4e06b9c193
0010a15b7bc0ea683d4343498aa5c6250832
002078e6057a543d114db5218bd460c340d9
00306ab281c09745592d92737c8a4fff9ef2
0040c3231c7abd00

seq 1seen 1at 3op 1signature 64

What gets signed

The receiver rebuilds the body from its own chain and the wire bytes, hashes it, and checks the signature over the header.

Body of 1. e476 bytes
0000f0210b45ff8480f06637feac142b51b9
001061da55787086a81ea46bb65b22e3c526
002000000000000000000000000000000000
003000000000000000000000000000000000
004000000000000003e80d000114

prev (genesis hash, seq 1) 32seen u32 4seen_hash (zeros: nothing seen) 32at u32 4op 1ply u16 2legal (20 moves) 1
Header of 1. e4 (signed)77 bytes
00005053524543303031f0210b45ff8480f0
00106637feac142b51b961da55787086a81e
0020a46bb65b22e3c5260000000001f6c0cc
00302cd18657aa649bde056f96b6f276bc09
00408c17ab417295b8882bff08afed

PSREC001 8genesis_hash 32signer (0 white) 1seq u32 4body_hash 32

A head

The header's variable parts plus its signature. The genesis is known to everyone involved, so this is all an anchor needs, and it still commits to the whole game.

Head of 1. e498 bytes
00000001f6c0cc2cd18657aa649bde056f96
0010b6f276bc098c17ab417295b8882bff08
0020afed44fae652fdb8dbfee518d712f32d
0030796234794fc1714893d63877fb171abb
0040f997a3139789432ebaf8824d2c49b4e6
005067d15b17f3a121ab8181072e83f92699
00605805

signer 1seq varint 1body_hash 32signature 64

A server verdict

Server records carry two seen values, one per player. This one is VERDICT_RESULT (op 242) with the result, the termination and both final clocks as varints.

Wire record: VERDICT_RESULT76 bytes
00000102028827f20101880e904e95eafab1
00102fd3f7245612eff4f9f7ba0e7dd16fcd
0020692ab639e243723e16c68da3feb5b038
003086156190145399d210bb0c2ad5cfad43
0040bdc0207b16e29af78accf403

seq 1seen (white) 1seen (black) 1at 2op 1args 6signature 64

Device certificate

The server certifies each device key while the device is online. Valid 30 days, renewed silently.

Certificate
PSCERT01 8 B
key_id 1 B which server key
user_id 1 + n
device_pk 32 B
issued 4 B
expires 4 B
server_sig 64 B
Certificate for user:white124 bytes
00005053434552543031010a757365723a77
0010686974652e28d334410b2fa7d4f626d5
00201bf1ee9fb464653b79090f9be0b3c823
003082845a946b49d19c6b49d2647c128ad4
00403a826cd889403081ae262b2b95d06375
0050d6111c96763f737323c05c0725634f28
00607d5fb5faa4cb9c2fd7743c2c72944a93
007048d2e059d1e05455359a810c

PSCERT01 8key_id 1user id length 1user id 10device public key 32issued 4expires 4server signature 64

Rules profile

A name and numbered key/value pairs, both varints. This is whitepawn/1 for a 3+2 rated blitz game online, so it has no key 14 (link_settle): only games with peer clocks carry it, two bytes more.

Profile whitepawn/1, blitz 3+245 bytes
00000b77686974657061776e2f310d011e02
001002031004882705fa0106e80707904e08
0020b0ea0109010a000b010c000d07

name length 1name 11count (13) 11 j_min_ms = 30 22 j_rtt_mult = 2 23 sample_window = 16 24 pause_budget_ms = 5000 35 ping_interval_ms = 250 36 rate_tolerance_ppm = 1000 37 checkpoint_interval_ms = 10000 38 away_after_ms = 30000 49 leave_with_arbiter = 1 210 leave_offline = 0 211 free_first_moves = 1 212 takebacks = 0 213 report_wait_days = 7 2

Genesis messages

Three messages start a game; see Genesis for the handshake. CHALLENGE carries the challenger's certificate and the rules profile in full.

CHALLENGE284 bytes
000050534348414c30315053434552543031
0010010a757365723a77686974652e28d334
0020410b2fa7d4f626d51bf1ee9fb464653b
003079090f9be0b3c82382845a946b49d19c
00406b49d2647c128ad43a826cd889403081
0050ae262b2b95d06375d6111c96763f7373
006023c05c0725634f287d5fb5faa4cb9c2f
0070d7743c2c72944a9348d2e059d1e05455
0080359a810c0002bf20000007d00102000b
009077686974657061776e2f310d011e0202
00a0031004882705fa0106e80707904e08b0
00b0ea0109010a000b010c000d075dfbabee
00c0df318bf33c0927c43d7630f51b82f351
00d0740301354fa3d7fc51f0132ebbe8b409
00e0ebf2488cf70a50cfe9d7850275401ad5
00f0b4476d73ce909588e686c7625890dde9
0100799e850a5f520094391cc623f4ef83c4
01107971ff7214d428ce8cdb5309

PSCHAL01 8cert: PSCERT01 8cert: key_id 1cert: user id length 1cert: user id 10cert: device public key 32cert: issued 4cert: expires 4cert: server signature 64initial_ms 4increment_ms 4rated 1arbiter 1color 1rules profile 45commit = SHA-256(nonce_A) 32signature A 64
ACCEPT244 bytes
0000505341435054303165bead4fb790c9db
0010ae91fdb4b39bff95666c19972e5a6e4f
0020be2bf896822222435053434552543031
0030010a757365723a626c61636b90607de6
00400a61811da61e412834cdb4fe07fca7f2
005067b615058dd11a84b0dcb2da6b49d19c
00606b49d26459215be3abc6ac8b784dc351
0070f89485238e891c195987b1308fa9f9bc
0080b7521ebccc0d77b7e94d0d903e5d6d60
00909cfd22147891b5a33552a2aa60cc6bee
00a07e033502100f0e0d0c0b0a0908070605
00b004030201d95401acc78e1f2a06fba01b
00c0764ade338be95b9d5a5a969f2b240b1b
00d08865f18cb351d2d72967f0d73a0f057d
00e0ccaf81855fc03fe6a2cf3d1299e07cd2
00f0d59abc01

PSACPT01 8SHA-256(CHALLENGE) 32cert: PSCERT01 8cert: key_id 1cert: user id length 1cert: user id 10cert: device public key 32cert: issued 4cert: expires 4cert: server signature 64nonce_B 16signature B 64
START120 bytes
00005053535452543031887f67a079cd5525
00109bfed0d6bad7b3400444cec14e6d954e
00201d06ade5dade10690102030405060708
0030090a0b0c0d0e0f100c372f256b77b7ff
00409df937b92c62a4bd0016439c3cd48edf
005030096d138c739d1f0d6e8a4310db559b
006018aa24ad4d0e2a6560cb4bbc2552ecdf
00708b986809a555d609

PSSTRT01 8SHA-256(ACCEPT) 32nonce_A (revealed) 16signature A 64

Anchor receipt

The server's signed answer to an anchor: the highest heads it holds for both players, its clock, and the hash of its previous receipt for this game.

Receipt254 bytes
00005053414e4348303101f0210b45ff8480
0010f06637feac142b51b961da55787086a8
00201ea46bb65b22e3c526000000028a5c56
0030995af759ee73e21048768a85fc0c634d
0040ef30782df17a2b5e12bf107089000000
005002b491f6f9672192e9641e68d3cbb444
00609201e5ef64e532ad11ae946a60f579b3
00705f000000000000000000000000000000
008000000000000000000000000000000000
00900000000001a3185c507b000000030700
00a000000000000000000000000000000000
00b00000000000000000000000000000c66e
00c0627a88816acce92d510ab9b7860714ca
00d00cadfb49d7d51f9f4343dd3a459d229d
00e08d91bed16388f9e1e1d01aecee33a5f6
00f047719961499da5e03051783f770e

PSANCH01 8key_id 1genesis_hash 32white_seq 4white record id 32black_seq 4black record id 32state_hash 32state_verified 1server_ms u64 8anchor_no 4prev_receipt 32server signature 64

Ping and pong

Peer games exchange pings four times a second on every link that carries the records, inside that link's session. They are unsigned and never stored: they only feed the live checks.

PING
id 2 B
t 4 B sender's clock
beat optional a BEAT, at least once a second
PONG
id 2 B
t_echo 4 B
t_rx 4 B
t_tx 4 B

Heartbeat

While a game with peer clocks runs, each device adds a signed heartbeat to a PING, as its optional last field, at least once a second on every open link: "at my clock at, my newest record was head_seq". A record that contradicts it is a fork (see Clocks).

BEAT113 bytes
PSBEAT01 8 B
genesis_hash 32 B
signer 1 B 0 white, 1 black
head_seq 4 B my newest record
at 4 B my clock
sig 64 B Ed25519, device key, over everything before

The first message on a direct link, one from each side. After it every frame on that link is sealed with ChaCha20-Poly1305 under a key per direction, derived from the two hellos, with the frame counter as the nonce (see Links).

LINK_HELLO161 bytes
PSLINK01 8 B
genesis_hash 32 B
role 1 B 0 white, 1 black
eph_pk 32 B X25519, fresh for this session
nonce 16 B random
have_white 4 B highest seq held
have_black 4 B for the flush
sig 64 B Ed25519, the role's device key, over everything before

Sizes at a glance

PayloadBytes
move, ACK or resign on the wireabout 70
header (signed)77
head (anchor unit)about 98
certificate124 (with a 10-character user id)
CHALLENGE / ACCEPT / START284 / 244 / 120
receipt254
heartbeat (inside a PING)113
link hello161
a stored 40-move game (compact form)about 1 KB of records plus 3 signatures