Wire format reference.
The bytes-over-the-wire spec for a Keysat license. Stable across SDKs and across language ports. About 90 lines of pseudocode to implement in a new language.
Overview
A Keysat license key looks like this on a receipt:
KS-9F2A-7C41-XK22-6D8E-LM77-PQ91
Strip the KS- prefix and the dashes, and you have a Crockford base32-encoded blob. Base32-decode that blob, and you get the binary license envelope: a fixed-layout struct followed by an Ed25519 signature.
Binary layout
All multi-byte integers are big-endian.
| Offset | Length | Field | Notes |
|---|---|---|---|
0 | 4 | Magic | ASCII KSAT (0x4B 0x53 0x41 0x54). |
4 | 1 | Version | Currently 0x01. Decoders MUST reject unknown versions. |
5 | 1 | Flags | Bit 0: TRIAL. Bit 1: PERPETUAL. Bits 2–7 reserved. |
6 | 16 | License ID | UUIDv4 binary form. |
22 | 16 | Issuer fingerprint | SHA-256 of the issuer public key, truncated to 16 bytes. |
38 | 8 | Issued-at | Unix seconds, signed. |
46 | 8 | Expires-at | Unix seconds, signed. 0 if PERPETUAL flag is set. |
54 | 2 | Seats | Max machines. 0 = unlimited. |
56 | 2 | Payload length | Length L of the variable-size payload that follows. |
58 | L | Payload | UTF-8 JSON: { "product": "...", "policy": "...", "entitlements": [...] }. |
58 + L | 64 | Signature | Ed25519 signature over bytes 0 .. (58 + L). |
Crockford base32
Keysat uses Crockford’s base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ) without checksum, without padding, and case-insensitive on decode.
The reason for Crockford over standard base32: human-friendly. I, L, O, U are excluded from the alphabet to avoid ambiguity when typing keys off a printed receipt.
Dash grouping & prefix
For display, keys are upper-cased, then grouped into 4-character chunks separated by dashes, and prefixed with KS-:
// raw base32, length depends on payload size 9F2A7C41XK226D8ELM77PQ91RR54VV01 // grouped + prefixed for display KS-9F2A-7C41-XK22-6D8E-LM77-PQ91-RR54-VV01
Decoders MUST strip the KS- prefix (case-insensitive), strip whitespace and dashes, and case-fold to upper before base32-decoding.
Signature
The signature covers the entire envelope from offset 0 through the end of the payload — that is, all bytes before the 64-byte signature itself.
Verify with the issuer’s Ed25519 public key. The fingerprint at offset 22 lets the verifier confirm that the key it has matches the key the license was signed with: SHA-256 the public key bytes, truncate to 16 bytes, compare. If it doesn’t match, the verifier MUST reject before attempting signature check — this gives a clear "wrong issuer" error rather than a generic "bad signature".
Worked example
Test vector for the Python SDK’s cross-check tests (issuer fingerprint 0xfeed face cafe babe..., single-seat perpetual license):
# Hex dump of the binary envelope 00000000 4B 53 41 54 01 02 9F 2A 7C 41 XK 22 6D 8E LM 77 |KSAT...*|A.."m..w| 00000010 PQ 91 RR 54 VV 01 FE ED FA CE CA FE BA BE 00 00 |...T....|........| 00000020 00 00 00 00 65 4F 12 34 00 00 00 00 00 00 00 00 |....eO.4|........| 00000030 00 01 00 24 7B 22 70 72 6F 64 75 63 74 22 3A 22 |...${"product":"| 00000040 73 75 6E 64 69 61 6C 22 2C 22 70 6F 6C 69 63 79 |sundial","policy| 00000050 22 3A 22 64 65 66 61 75 6C 74 22 7D ...sig... |":"default"}.....| # As displayed KS-9F2A-7C41-XK22-6D8E-LM77-PQ91-…
The full vector lives in licensing-client-python/tests/fixtures/canonical.json and is what every official SDK is tested against.
Issuer public key format
Public keys are exchanged in PEM format, SubjectPublicKeyInfo encoded:
-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAmz7q8r4t1v…h3k2pXq9wL -----END PUBLIC KEY-----
This is the same encoding that openssl pkey -pubout produces. Keysat exposes it at GET /v1/issuer/public-key:
{
"public_key_pem": "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----\n",
"public_key_b64": "mz7q8r4t1v…h3k2pXq9wL",
"fingerprint_hex": "feed face cafe babe …"
}
Porting to a new language
The wire format is small enough to port in an afternoon. The order is:
- Copy the test vectors from licensing-client-python/tests/fixtures/canonical.json.
- Implement Crockford base32 decode (~30 lines).
- Implement the binary unmarshal (~40 lines, mostly offset arithmetic).
- Wire it up to your language’s Ed25519 verifier from a vetted crypto library.
- Run the cross-check tests — if they pass, you’re wire-compatible.
See PORTING_SDK_TO_NEW_LANGUAGES.md in the repo for the full contributor guide.
Versioning policy
The version byte at offset 4 is a hard gate. Decoders MUST reject any version they don’t implement. We commit to:
- Never silently changing the v1 layout. Any change ⇒ new version byte.
- Maintaining v1 verifier support indefinitely — even if v2 ships, your existing customer keys stay verifiable.
- Publishing test vectors for every new version under
tests/fixtures/in the canonical SDK.