Wire Protocol Specification
Technical specification for Zentalk network communication binary formats.
Overview
The Zentalk wire protocol defines how nodes communicate at the network level. All communication is encrypted using TLS 1.3 and follows strict binary formatting for efficiency and security.
Transport Layer
| Property | Value |
|---|---|
| Encryption | TLS 1.3 |
| Transport | TCP |
| Relay Port | 9001 |
| API Port | 443 |
Connection Lifecycle
1. TCP Connection
└─ Client initiates TCP connection to node
2. TLS Handshake
└─ TLS 1.3 with mutual authentication
3. Protocol Handshake
└─ Exchange capabilities and versions
4. Session Active
└─ Send/receive framed messages
5. Graceful Shutdown
└─ DESTROY messages, then TCP FINMessage Framing
All messages follow a consistent framing format for reliable parsing and length-prefixed reading.
┌────────────────────────────────────────┐
│ Length (4 bytes, big-endian) │
├────────────────────────────────────────┤
│ Message Type (1 byte) │
├────────────────────────────────────────┤
│ Payload (variable) │
└────────────────────────────────────────┘Field Definitions
| Field | Size | Description |
|---|---|---|
| Length | 4 bytes | Total payload length (excluding this field) |
| Message Type | 1 byte | Identifies the message type |
| Payload | Variable | Type-specific data |
Maximum Message Size
| Constraint | Value |
|---|---|
| Max payload | 65,535 bytes |
| Max frame | 65,540 bytes |
| Typical relay cell | 542 bytes |
Message Types
| Type | Code | Direction | Purpose |
|---|---|---|---|
| HANDSHAKE | 0x01 | Bidirectional | Protocol negotiation |
| RELAY_CREATE | 0x02 | Client → Node | Create new circuit |
| RELAY_DATA | 0x03 | Bidirectional | Encrypted relay data |
| RELAY_DESTROY | 0x04 | Bidirectional | Tear down circuit |
| PADDING | 0x05 | Bidirectional | Traffic analysis resistance |
| PING | 0x06 | Bidirectional | Keepalive request |
| PONG | 0x07 | Bidirectional | Keepalive response |
HANDSHAKE (0x01)
Initial protocol negotiation after TLS establishment.
┌────────────────────────────────────────┐
│ Protocol Version (2 bytes) │
├────────────────────────────────────────┤
│ Capabilities (4 bytes, bitfield) │
├────────────────────────────────────────┤
│ Node ID (32 bytes) │
├────────────────────────────────────────┤
│ Timestamp (8 bytes) │
├────────────────────────────────────────┤
│ Signature (64 bytes) │
└────────────────────────────────────────┘
Total: 110 bytesRELAY_CREATE (0x02)
Request to create a new relay circuit.
┌────────────────────────────────────────┐
│ Circuit ID (4 bytes) │
├────────────────────────────────────────┤
│ Handshake Type (1 byte) │
├────────────────────────────────────────┤
│ Handshake Data (variable) │
└────────────────────────────────────────┘RELAY_DATA (0x03)
Encrypted data traveling through a circuit.
┌────────────────────────────────────────┐
│ Circuit ID (4 bytes) │
├────────────────────────────────────────┤
│ Cell Command (1 byte) │
├────────────────────────────────────────┤
│ Encrypted Payload (509 bytes, padded) │
├────────────────────────────────────────┤
│ Nonce (12 bytes) │
├────────────────────────────────────────┤
│ Authentication Tag (16 bytes) │
└────────────────────────────────────────┘
Total: 542 bytes (fixed)RELAY_DESTROY (0x04)
Tear down a circuit.
┌────────────────────────────────────────┐
│ Circuit ID (4 bytes) │
├────────────────────────────────────────┤
│ Reason Code (1 byte) │
└────────────────────────────────────────┘
Total: 5 bytesPADDING (0x05)
Dummy traffic for traffic analysis resistance.
┌────────────────────────────────────────┐
│ Random Data (variable, typically 509) │
└────────────────────────────────────────┘PING/PONG (0x06/0x07)
Connection keepalive.
┌────────────────────────────────────────┐
│ Timestamp (8 bytes) │
├────────────────────────────────────────┤
│ Nonce (8 bytes) │
└────────────────────────────────────────┘
Total: 16 bytesRelay Cell Format
Relay cells carry encrypted data through circuits. All cells are fixed-size to prevent traffic analysis.
┌────────────────────────────────────────┐
│ Circuit ID (4 bytes) │
├────────────────────────────────────────┤
│ Cell Command (1 byte) │
├────────────────────────────────────────┤
│ Stream ID (2 bytes) │
├────────────────────────────────────────┤
│ Digest (4 bytes) │
├────────────────────────────────────────┤
│ Payload Length (2 bytes) │
├────────────────────────────────────────┤
│ Payload Data (501 bytes, padded) │
├────────────────────────────────────────┤
│ Nonce (12 bytes) │
├────────────────────────────────────────┤
│ Authentication Tag (16 bytes) │
└────────────────────────────────────────┘
Total: 542 bytes (fixed)Cell Commands
| Command | Code | Description |
|---|---|---|
| BEGIN | 0x01 | Open new stream |
| DATA | 0x02 | Stream data |
| END | 0x03 | Close stream |
| CONNECTED | 0x04 | Stream opened |
| EXTEND | 0x05 | Extend circuit |
| EXTENDED | 0x06 | Circuit extended |
| TRUNCATE | 0x07 | Truncate circuit |
| TRUNCATED | 0x08 | Circuit truncated |
Encryption Layers
Messages are encrypted layer by layer, with each hop able to decrypt only its layer:
Original: [Payload]
After Exit encryption:
[AES-GCM(Key_exit, Payload || Exit_routing)]
After Middle encryption:
[AES-GCM(Key_middle, Exit_encrypted || Middle_routing)]
After Guard encryption:
[AES-GCM(Key_guard, Middle_encrypted || Guard_routing)]
On wire: Guard-encrypted blobDecryption Process
Guard Node:
1. Decrypt outer layer with Key_guard
2. Read routing info (next hop = Middle)
3. Forward to Middle
Middle Node:
1. Decrypt layer with Key_middle
2. Read routing info (next hop = Exit)
3. Forward to Exit
Exit Node:
1. Decrypt layer with Key_exit
2. Deliver to destinationHandshake Protocol
TLS 1.3 Setup
Client Server
│ │
├─────── ClientHello ──────────────►│
│ (supported ciphers) │
│ │
│◄────── ServerHello ───────────────┤
│ (selected cipher) │
│ Certificate │
│ CertificateVerify │
│ Finished │
│ │
├─────── Certificate ──────────────►│
│ CertificateVerify │
│ Finished │
│ │
│ [Application Data] │
└───────────────────────────────────┘Supported Cipher Suites
| Priority | Cipher Suite |
|---|---|
| 1 | TLS_AES_256_GCM_SHA384 |
| 2 | TLS_CHACHA20_POLY1305_SHA256 |
| 3 | TLS_AES_128_GCM_SHA256 |
Node Authentication
After TLS establishment, nodes exchange HANDSHAKE messages:
1. Both nodes send HANDSHAKE message
2. Verify signature using node's Ed25519 key
3. Check timestamp is within 5 minutes
4. Verify capabilities are compatible
5. Session establishedCapability Negotiation
| Bit | Capability | Description |
|---|---|---|
| 0 | RELAY | Can relay traffic |
| 1 | EXIT | Can be exit node |
| 2 | GUARD | Can be guard node |
| 3 | KYBER | Supports post-quantum |
| 4 | PADDING | Supports traffic padding |
| 5-31 | Reserved | Future use |
Example: 0x0000001F = RELAY + EXIT + GUARD + KYBER + PADDINGAPI Message Format
The HTTP API uses JSON over HTTPS on port 443.
Request Format
POST /api/v1/{endpoint} HTTP/1.1
Host: api.zentalk.network
Content-Type: application/json
Authorization: Bearer {jwt_token}
X-Request-ID: {uuid}
X-Timestamp: {unix_ms}
X-Signature: {ed25519_signature}
{
"action": "string",
"params": { ... },
"nonce": "string"
}Response Format
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: {uuid}
X-Response-Time: {ms}
{
"success": true,
"data": { ... },
"timestamp": 1704067200000
}Authentication Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer JWT token |
| X-Request-ID | Yes | Unique request identifier |
| X-Timestamp | Yes | Request timestamp (Unix ms) |
| X-Signature | Conditional | Ed25519 signature for sensitive operations |
Common Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /api/v1/keys/bundle | GET | Fetch user’s key bundle |
| /api/v1/keys/prekeys | POST | Upload one-time prekeys |
| /api/v1/messages | POST | Submit encrypted message |
| /api/v1/messages | GET | Retrieve pending messages |
| /api/v1/nodes | GET | List relay nodes |
Encoding Rules
Strict encoding rules ensure interoperability across implementations.
Integer Encoding
| Rule | Description |
|---|---|
| Byte order | Big-endian (network order) |
| Unsigned | All integers are unsigned |
| Fixed width | Use exact specified width |
Example: 0x00001234 (4 bytes) = 4660 decimal
Byte layout: [0x00] [0x00] [0x12] [0x34]
MSB LSBString Encoding
| Rule | Description |
|---|---|
| Format | UTF-8 |
| Length prefix | 2 bytes (max 65,535 chars) |
| Null terminator | Not used |
String "hello":
┌───────────┬────────────────────┐
│ 0x00 0x05 │ 0x68 0x65 0x6C ... │
│ (length) │ (UTF-8 bytes) │
└───────────┴────────────────────┘Binary Data
| Rule | Description |
|---|---|
| Format | Raw bytes |
| Length prefix | 4 bytes (for variable length) |
| Fixed fields | No length prefix |
| No base64 | Raw bytes on wire |
Timestamps
| Rule | Description |
|---|---|
| Format | Unix timestamp |
| Precision | Milliseconds |
| Size | 8 bytes (uint64) |
| Timezone | UTC |
Example: 2024-01-01 00:00:00 UTC
Value: 1704067200000 (ms)
Bytes: 0x00 0x00 0x01 0x8C 0xDB 0xC7 0xCC 0x00Arrays
┌────────────────────────────────────────┐
│ Count (4 bytes) │
├────────────────────────────────────────┤
│ Element 1 │
├────────────────────────────────────────┤
│ Element 2 │
├────────────────────────────────────────┤
│ ... │
└────────────────────────────────────────┘Error Responses
Wire Protocol Errors
| Code | Name | Description |
|---|---|---|
| 0x00 | SUCCESS | Operation completed |
| 0x01 | PROTOCOL_ERROR | Invalid message format |
| 0x02 | CIRCUIT_NOT_FOUND | Unknown circuit ID |
| 0x03 | CIRCUIT_DESTROYED | Circuit was torn down |
| 0x04 | TIMEOUT | Operation timed out |
| 0x05 | RESOURCE_LIMIT | Too many circuits/streams |
| 0x06 | AUTH_FAILED | Authentication failed |
| 0x07 | VERSION_MISMATCH | Incompatible protocol version |
| 0x08 | CRYPTO_ERROR | Encryption/decryption failed |
| 0x09 | NODE_OVERLOADED | Node at capacity |
| 0x0A | STREAM_CLOSED | Stream no longer exists |
| 0xFF | INTERNAL_ERROR | Unexpected server error |
Error Message Format
┌────────────────────────────────────────┐
│ Error Code (1 byte) │
├────────────────────────────────────────┤
│ Error Message Length (2 bytes) │
├────────────────────────────────────────┤
│ Error Message (UTF-8, variable) │
├────────────────────────────────────────┤
│ Context Data Length (2 bytes) │
├────────────────────────────────────────┤
│ Context Data (optional, variable) │
└────────────────────────────────────────┘API Error Response
{
"success": false,
"error": {
"code": "INVALID_SIGNATURE",
"message": "Request signature verification failed",
"details": {
"expected_signer": "0x1234...",
"timestamp": 1704067200000
}
}
}HTTP Status Codes
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request (malformed) |
| 401 | Unauthorized (auth failed) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 429 | Rate Limited |
| 500 | Internal Server Error |
| 503 | Service Unavailable |
Connection Management
Keepalive
| Parameter | Value |
|---|---|
| PING interval | 30 seconds |
| PONG timeout | 10 seconds |
| Max missed PINGs | 3 |
Reconnection
1. Connection lost
2. Wait: min(30s, 2^attempt * 1s)
3. Attempt reconnect
4. If failed, increment attempt counter
5. Max attempts: 10, then give upFlow Control
| Parameter | Value |
|---|---|
| Max pending cells | 1000 |
| Window size | 500 cells |
| Window update threshold | 250 cells |
Related Documentation
- Protocol Specification - Cryptographic protocol details
- Onion Routing - Relay architecture
- API Reference - HTTP API documentation