Encryption & Transport
Trading payloads are end-to-end encrypted with HPKE (RFC 9180), then sealed with AES-256-GCM.
The bodies described elsewhere (PlaceOrderInput, cancel, modify, …) are the logical plaintext. On the wire they are AEAD ciphertext. Cleartext headers carry routing metadata and the request_type discriminant.
Use an SDK. Do not implement this by hand for production. The six official SDKs (JS, Python, Rust, C++, Java, Go) perform login, HPKE setup, protobuf encoding, nonce management, and encrypt/decrypt. This page is reference material for auditors and ports.
Scope
| Applies to | Does not apply to |
|---|---|
WebSocket trading after login (binary TradingWsBinaryFrame) | Public market-data sockets (e.g. /ws/gomarket) |
REST order / leverage routes that accept RestEncryptedEdgeRequest (bearer + one-shot HPKE) | Plain HTTPS account/auth/stats JSON that is not an encrypted trading body |
| Authenticated private pushes (orders, positions, margin, …) once a session is sealed | Unauthenticated public REST |
REST trading clients (for example GodarkRestClient) still use Authorization: Bearer <access_token> and send one-shot HPKE bodies (encapped_key + encrypted_body per request). They do not leave order fields as clear JSON on those routes.
Cryptographic suite
| Piece | Value |
|---|---|
| KEM | DHKEM(X25519, HKDF-SHA256) |
| KDF | HKDF-SHA256 |
| AEAD | AES-256-GCM (96-bit nonce, 128-bit tag) |
| Mode | HPKE Base setup against a pinned sequencer static public key |
After setup, peers export directional keys (gdx-hpke/v1 c2s / gdx-hpke/v1 s2c) and seal each message with an explicit 96-bit nonce: 0u32_be ‖ counter_be.
HPKE info binding
| Path | info |
|---|---|
| WebSocket session | gdx-hpke/v1\0 ‖ account (32 bytes) ‖ conn_id (u64 BE) |
| REST one-shot | gdx-hpke/v1/rest\0 ‖ account (32 bytes) ‖ request_id (u64 BE) |
account is the 32-byte Solana L2 identity from login / JWT sub. REST encrypted requests must set OrderHeader.conn_id = 0.
Session lifecycle (WebSocket)
- Authenticate with
op: "login"— see Authentication and WebSocket Trading. Login returns a non-zeroconn_idand a 32-byteaccount(current protocol). HPKE setup requires both. - HPKE Base setup. The client encapsulates to the pinned sequencer static public key and sends
TradingWsBinaryFrame.hpke_setup(HpkeSetup:account,conn_id,encapped_key). The peer replies withhpke_setup_reply(established: truewhen ready). - Encrypt trading ops under the sealed session with AES-256-GCM. Clear
OrderHeader/ResponseHeaderare AAD; ciphertext is the AEAD output (plaintext ‖ tag length includes the 16-byte tag inbody_length). - Private pushes arrive as encrypted frames and are opened with the s2c key.
Official SDKs bake the configured host and the configured host pins or accept GDX_HPKE_STATIC_PUBLIC_KEY / hpkeStaticPublicKeyHex for a custom host. They do not expose a public Mainnet preset.
Plaintext is the inner message
Encrypted client bodies encode the inner sequencer message only — for example PlaceOrderInput, cancel input, ModifyOrderInput. The clear header request_type (gdx.common.v1.RequestType) is the discriminant.
Not AEAD plaintext: EdgeSequencerRequest / SequencerToEdgeMessage oneofs. Those wrap cleartext TradeQuery / Admin / hop frames, not sealed order bodies.
Associated data (AAD)
Every sealed frame authenticates a protobuf header as AAD.
Requests — OrderHeader:
| Field | Type | Notes |
|---|---|---|
account | bytes (32) | Solana wallet pubkey (L2 account). Must match login. |
symbol_id | uint64 | Resolved instrument id. |
request_type | enum | Selects the inner plaintext type (place, cancel, modify, …). |
nonce | uint64 | Monotonic send counter (AAD + GCM nonce input). |
body_length | uint32 | Ciphertext length including the 16-byte GCM tag. |
correlation_id | bytes | Optional 16-byte BE u128; empty means zero. |
conn_id | uint64 | WS session connection id; 0 on REST one-shot. |
Responses — ResponseHeader:
| Field | Type | Notes |
|---|---|---|
account | bytes (32) | Authenticated account — re-derive from session state; do not trust a forged envelope alone. |
message_type | enum | ack, order_update, snapshots, margin update, … |
body_length | uint32 | Ciphertext length including tag. |
nonce | uint64 | Server-side counter for this direction. |
fencing_epoch | uint64 | Replay/fencing across reconnect. |
correlation_id | bytes | Echoed from the request when present. |
session_seq | uint64 | Per-connection ack sequence. |
conn_id | uint64 | Same connection id as setup. |
AAD bytes must match the canonical protobuf encoding; a one-byte mismatch fails GCM verification.
Wire shape (conceptual)
WebSocket trading ops and HPKE setup use binary TradingWsBinaryFrame messages (not clear JSON order args). JSON remains for control ops (login, subscribe, ping, …).
REST encrypted place/cancel/modify carry RestEncryptedEdgeRequest fields such as header, encrypted_body, encapped_key, and request_id. Use the SDK-selected content type and encoding for that envelope; do not send the logical order as application/json and do not label raw protobuf/binary ciphertext as JSON. Logical field lists on Place Order describe the decrypted protobuf, not a clear REST JSON body.
Security properties
| Property | How |
|---|---|
| Confidentiality | AES-256-GCM under HPKE-exported directional keys. |
| Recipient authenticity (setup) | Client encapsulates to a pinned sequencer static public key (per environment). |
| Integrity | 128-bit GCM tag over ciphertext + header AAD. |
| Identity binding | HPKE info and AAD include the 32-byte account (and conn_id / request_id). |
| Replay resistance | Per-direction nonce tracking; REST binds request_id into info. |
What this channel does not give you
- Protection against a malicious operator who holds the sequencer private key and can open sealed sessions. HPKE protects orders from passive observers and from edge logs outside the decryption boundary; it does not make the venue non-custodial with respect to the operator.
- Compatibility with UUID identities. A 16-byte internal user/session UUID is not the 32-byte Solana
accountrequired by HPKE. Human-readableaccount_idandsession_idstrings are metadata and are not HPKE identity bytes.
Error codes
Failures surface as non-zero code / error_code on responses or admit errors. Relevant trade-ACK codes include SESSION_EXPIRED (3008), E2E_DECRYPTION_FAILED (3009), and REQUEST_TYPE_MISMATCH (3015). See Error Codes.
Reference implementation
| Language | Where to look |
|---|---|
| JavaScript / TypeScript | gdx-js-sdk — src/hpke.ts, src/session.ts, src/wire.ts, src/restClient.ts |
| Rust / Python / C++ / Go / Java | Matching hpke / session modules in each official SDK |
Pins and environment presets live in each SDK’s client config (the configured host / the configured host baked-in keys; a custom host via env; no public Mainnet SDK preset).