# README
Stateless vpack wire format
This document specifies the byte‑level (on‑wire) layout produced by StatelessEncoder.CompressVote
and accepted by StatelessDecoder.DecompressVote
.
The goal is to minimize vote size while retaining a 1‑to‑1, loss‑free mapping to the canonical msgpack representation of agreement.UnauthenticatedVote
.
The canonical msgpack representation we rely on is provided by agreement/msgp_gen.go, generated by our custom msgpack code generator
which ensures fields are generated in lexicographic order, omit empty key-value pairs, and use specific formats for certain types as defined in
our specification.
1. High‑level structure
+---------+-----------------+---------------------+--------------------------+
| Header | VrfProof ("pf") | rawVote ("r") | OneTimeSignature ("sig") |
| 2 bytes | 80 bytes | variable length | 256 bytes |
+---------+-----------------+---------------------+--------------------------+
All fields appear exactly once, and in the fixed order above. The presence of optional sub‑fields inside rawVote
are indicated by a 1‑byte bitmask in the header.
No field names appear, only values.
2. Header (2 bytes)
Offset | Description |
---|---|
0 | Presence flags for optional values (LSB first, see table). |
1 | Reserved, currently zero. |
2.1 Bit‑mask layout (byte 0)
Bit | Flag | Field enabled | Encoded size |
---|---|---|---|
0 | bitPer | r.per (varuint) | 1 - 9 bytes |
1 | bitDig | r.prop.dig (digest) | 32 bytes |
2 | bitEncDig | r.prop.encdig (digest) | 32 bytes |
3 | bitOper | r.prop.oper (varuint) | 1 - 9 bytes |
4 | bitOprop | r.prop.oprop (address) | 32 bytes |
5 | bitStep | r.step (varuint) | 1 - 9 bytes |
Binary fields are represented by their 32-, 64-, and 80-byte values without markers. Integers use msgpack's variable-length unsigned integer encoding:
fixint
(≤ 127), 1 byte in length (values 0x00-0x7f)uint8
2 bytes in length (marker byte 0xcc + 1-byte value)uint16
3 bytes in length (marker byte 0xcd + 2-byte value)uint32
5 bytes in length (marker byte 0xce + 4-byte value)uint64
9 bytes in length (marker byte 0xcf + 8-byte value)
3. Field serialization order
After the 2-byte header, the encoder emits values in the following order:
Field | Type | Encoded size | Presence flag |
---|---|---|---|
pf | VRF credential | 80 bytes | Required |
r.per | Period | varuint | bitPer |
r.prop.dig | Proposal digest | 32 bytes | bitDig |
r.prop.encdig | Digest of encoded proposal | 32 bytes | bitEncDig |
r.prop.oper | Proposal's original period | varuint | bitOper |
r.prop.oprop | Proposal's original proposer | 32 bytes | bitOprop |
r.rnd | Round number | varuint | Required |
r.snd | Voter's (sender) address | 32 bytes | Required |
r.step | Step | varuint | bitStep |
sig.p | Ed25519 public key | 32 bytes | Required |
sig.p1s | Signature over offset ID | 64 bytes | Required |
sig.p2 | Second-tier Ed25519 public key | 32 bytes | Required |
sig.p2s | Signature over batch ID | 64 bytes | Required |
sig.s | Signature over vote using p | 64 bytes | Required |