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 toDoes 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 sealedUnauthenticated 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

PieceValue
KEMDHKEM(X25519, HKDF-SHA256)
KDFHKDF-SHA256
AEADAES-256-GCM (96-bit nonce, 128-bit tag)
ModeHPKE 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

Pathinfo
WebSocket sessiongdx-hpke/v1\0 ‖ account (32 bytes) ‖ conn_id (u64 BE)
REST one-shotgdx-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)

  1. Authenticate with op: "login" — see Authentication and WebSocket Trading. Login returns a non-zero conn_id and a 32-byte account (current protocol). HPKE setup requires both.
  2. 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 with hpke_setup_reply (established: true when ready).
  3. Encrypt trading ops under the sealed session with AES-256-GCM. Clear OrderHeader / ResponseHeader are AAD; ciphertext is the AEAD output (plaintext ‖ tag length includes the 16-byte tag in body_length).
  4. 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:

FieldTypeNotes
accountbytes (32)Solana wallet pubkey (L2 account). Must match login.
symbol_iduint64Resolved instrument id.
request_typeenumSelects the inner plaintext type (place, cancel, modify, …).
nonceuint64Monotonic send counter (AAD + GCM nonce input).
body_lengthuint32Ciphertext length including the 16-byte GCM tag.
correlation_idbytesOptional 16-byte BE u128; empty means zero.
conn_iduint64WS session connection id; 0 on REST one-shot.

Responses — ResponseHeader:

FieldTypeNotes
accountbytes (32)Authenticated account — re-derive from session state; do not trust a forged envelope alone.
message_typeenumack, order_update, snapshots, margin update, …
body_lengthuint32Ciphertext length including tag.
nonceuint64Server-side counter for this direction.
fencing_epochuint64Replay/fencing across reconnect.
correlation_idbytesEchoed from the request when present.
session_sequint64Per-connection ack sequence.
conn_iduint64Same 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

PropertyHow
ConfidentialityAES-256-GCM under HPKE-exported directional keys.
Recipient authenticity (setup)Client encapsulates to a pinned sequencer static public key (per environment).
Integrity128-bit GCM tag over ciphertext + header AAD.
Identity bindingHPKE info and AAD include the 32-byte account (and conn_id / request_id).
Replay resistancePer-direction nonce tracking; REST binds request_id into info.

What this channel does not give you

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

LanguageWhere to look
JavaScript / TypeScriptgdx-js-sdk — src/hpke.ts, src/session.ts, src/wire.ts, src/restClient.ts
Rust / Python / C++ / Go / JavaMatching 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).