FigNet Secure Session
This page explains the current secure datagram layer used by FigNet.
Related pages:
What It Solves
Plain UDP-style networking has two practical problems:
- Sensitive bootstrap messages can be sent before any secure channel exists.
- Packet bytes can reach parsers and serializers before authenticity is known.
FigNet solves that for datagram transports by adding:
- connection-scoped key agreement
- authenticated encryption for protected payloads
- packet authentication before payload deserialization
The result is:
RegisterIdentityandValidateIdentitycan be protected on datagram transports- tampered encrypted packets are dropped before higher-level parsing
- encrypted means authenticated AEAD payload protection, not obfuscation
Transport Split
| Transport kind | Providers | Security model |
|---|---|---|
| Datagram | ENet, LiteNetLib | App-layer secure session |
| Stream | TCP, WebSocket, WebSocketCore, WebSocketNative | Transport security only (TLS/WSS) |
Cryptographic Stack
| Purpose | Algorithm |
|---|---|
| Key agreement | ECDiffieHellman on nistP256 |
| Key derivation | HKDF-SHA256 |
| Packet encryption | ChaCha20-Poly1305 |
| Trust model | No pinned-key validation in the active runtime |
Key properties:
- the server creates one global keypair per process lifetime
- the keypair is kept in memory only
- the client creates an ephemeral keypair per connection
- each direction gets separate derived key material
Current Identity Model
The active runtime does not pin or persist the server public key.
Current behavior:
- when the server process first needs secure datagram support, it generates one in-memory keypair
- that keypair is shared by all secure datagram listeners in the same process
- when the server restarts, a new keypair is generated
- clients do not compare the server public key against config
This keeps setup friction low and still protects the bootstrap payloads with authenticated encryption after the handshake.
What this does not provide:
- stable server identity across restarts
- resistance to a man-in-the-middle presenting another valid handshake key
For a stronger future model, see Secure Bootstrap (HTTPS).
Handshake Overview
The secure session is created lazily. It begins when the client first needs to send an encrypted datagram message.
Client connects transport
|
v
Needs encrypted datagram?
|
+-- no --> send plaintext normally
|
+-- yes -->
create secure session context
send SecureClientHello
wait for SecureServerHello
derive shared keys
send SecureClientFinish
mark session established
flush queued encrypted messages
Handshake Flow
Client Server
| |
|---- SecureClientHello ------------------>|
| client ephemeral public key |
| |
|<--- SecureServerHello -------------------|
| server public key + transcript proof |
| |
| derive shared secret |
| derive session keys |
| |
|---- SecureClientFinish ----------------->|
| |
| secure session established | secure session established
|---- RegisterIdentity (encrypted) ------->|
|---- ValidateIdentity (encrypted) ------->|
Handshake Messages
The secure-session control frames are reserved built-ins and always remain plaintext:
| Message | Purpose |
|---|---|
SecureClientHello | Starts the handshake and carries the client ephemeral public key |
SecureServerHello | Returns the server public key and transcript proof |
SecureClientFinish | Confirms the client accepted the secure session |
SecureHandshakeError | Signals handshake failure |
Key Derivation
Both sides use ECDH to derive the same shared secret. That secret is expanded with HKDF into:
- client-to-server encryption key
- server-to-client encryption key
- client-to-server nonce prefix
- server-to-client nonce prefix
- handshake confirmation material
Encrypted Packet Format
Encrypted datagram packets use this wire format:
[2 bytes messageId]
[1 byte callbackId + encrypted bit]
[4 bytes sequence]
[ciphertext]
[16 bytes Poly1305 tag]
Notes:
- the encrypted bit means app-layer encrypted datagram payload
- the sequence is required for nonce uniqueness
- the header plus sequence are authenticated as AAD
- encrypted packets do not use packet magic
Plaintext Datagram Format
Plaintext datagram packets include packet magic automatically:
[2 bytes packet magic 0xC0DE]
[2 bytes messageId]
[1 byte callbackId + encrypted bit = 0]
[payload]
Current rule set:
- plaintext datagram => packet magic included
- encrypted datagram => packet magic omitted
- stream transport => packet magic omitted
Packet magic is a protocol marker, not a security boundary.
Send Path
Create Message
->
Check transport kind
->
If stream:
send bytes unchanged
rely on TLS/WSS if enabled
If datagram and plaintext:
add packet magic
send
If datagram and encrypted:
if secure session not established:
queue message
start handshake
else:
write header
write sequence
encrypt payload into rented send buffer
append tag
send
Receive Path
Packet arrives
->
Resolve header offset
->
Read messageId and flags
->
If encrypted:
read sequence
authenticate + decrypt
if auth fails: drop packet
else: dispatch owned Message
If plaintext:
parse after packet-magic offset
dispatch normally
Performance Characteristics
The secure layer is designed for real-time networking:
- no per-packet heap allocations in the hot path
- per-connection crypto context is reused
- send buffers are rented and written directly
- receive payloads are decrypted before owned
Messageconstruction - handshake state is per connection, not global
Built-In Message Policy
For datagram transports:
| Message type | Encrypted by default |
|---|---|
RegisterIdentity | Yes |
ValidateIdentity | Yes |
Ping | No |
| Secure-session handshake control messages | No |
Why XOR Was Removed
Older payload-obfuscation approaches such as XOR were removed because they do not provide real security:
- easy to reverse
- no authenticity
- no tamper detection
- misleading encrypted semantics
The active runtime keeps only:
ChaCha20-Poly1305for app-layer encrypted datagramsTLS/WSSfor secure stream transports
Porting Checklist
If you want to implement FigNet-compatible secure datagram support in another language, the minimum requirements are:
- Implement the secure-session control message exchange.
- Support
P-256 ECDH. - Support
HKDF-SHA256. - Support
ChaCha20-Poly1305. - Use the exact encrypted packet structure:
messageId- flags byte
- 4-byte sequence
- ciphertext
- 16-byte tag
- Authenticate header + sequence as AAD.
- Use separate key material per direction.
- Do not apply packet magic to encrypted datagrams.
- Do not implement XOR compatibility.
Source Of Truth
Active code paths:
FigNet/Core/SecureSessionContext.csFigNet/Core/SecureSessionProtocol.csFigNet/Core/SecureServerCredentials.csFigNet/Core/SecureSessionTransportPolicy.csFigNet/Core/MessageHeader.csFigNet/Core/ReservedMessageId.csENetProvider/ClientSocketENet.csENetProvider/ServerSocketEnet.csLiteNetLibProvider/ClientSocketLiteNetLib.csLiteNetLibProvider/ServerSocketLiteNetLib.cs- Unity mirrors under
FigNetUnity/FigNet_Unity/Assets/FigNetCore/...