> ## Documentation Index
> Fetch the complete documentation index at: https://docs.derivadex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Encrypt Requests for the Operator

> Encrypt signed DerivaDEX requests with the operator public key, build the byte payload DerivaDEX expects, and submit it safely to `/v2/request`.

## Encrypt requests for the operator

Use this after you have signed a DerivaDEX request and are ready to submit it to the operator. The examples use Python because the byte-level procedure is easiest to read there, but the same steps apply in any language that can produce the same bytes.

You need the signed request JSON exactly as it will be submitted, plus the operator public key from `GET /v2/encryption-key`.

After the payload is encrypted, submit the resulting bytes to `POST /v2/request`.

Each step crosses a different boundary: you fetch the operator key from DerivaDEX, build the encrypted bytes locally, and then send only the encrypted payload to the submission endpoint.

## Follow the encryption procedure

1. Fetch the operator public key from `GET /v2/encryption-key`.
2. Serialize the signed request to UTF-8 JSON exactly as you will submit it.
3. Generate a fresh client secp256k1 private key for this submission attempt, then derive its compressed public key.
4. Multiply the operator public key by the client private key to produce the shared point.
5. Run `keccak256` over the shared point bytes and keep the first 16 bytes as the AES-GCM key.
6. Generate a fresh 12-byte nonce, which is a one-time random value for this encryption.
7. Prefix the UTF-8 JSON bytes with a 4-byte big-endian length.
8. Encrypt that payload with AES-GCM, which encrypts the bytes and adds an integrity check.
9. Assemble the final byte payload as `ciphertext || tag || nonce || client_public_key_compressed`.
10. Submit those bytes to `POST /v2/request`.

Use a fresh client private key and a fresh encryption nonce for every submission attempt. For a retry after an ambiguous send, re-encrypt the same signed JSON; do not refresh `clientTimestampMs`, `recvWindowMs`, the request nonce, business fields, or the signature.

## Worked example

This flow is the same for orders, cancels, profile updates, and withdrawals. What changes is the signed JSON you feed into it.

This example starts from the same order fields used in [How to Sign DerivaDEX Requests with EIP-712](/how-to-guides/sign-requests-with-eip712). The signature here comes from that same EIP-712 hash, signed with a fixed sample wallet so the encryption result is reproducible. The JSON shape below matches what `TradingJSONEncoder().encode(order_intent.json)` produces in `ddx-python`.

### Example: encrypt a signed order request

Each example can be implemented in two ways here:

* `Direct implementation` shows the byte-building steps directly.
* `DDX client library` shows the shortest path if you already use `ddx-python`.

#### Direct implementation

Start with the helper functions that stay the same for every request type:

```python theme={null}
from coincurve import PublicKey, PrivateKey
from Crypto.Cipher import AES
from Crypto.Hash import keccak
from Crypto.Random import get_random_bytes


def encrypt_request(
    operator_public_key_hex: str,
    signed_request_json: str,
    client_private_key_hex: str | None = None,
    nonce_hex: str | None = None,
) -> str:
    operator_public_key = PublicKey(
        bytes.fromhex(operator_public_key_hex.removeprefix("0x"))
    )

    if client_private_key_hex is None:
        client_private_key = PrivateKey(get_random_bytes(32))
    else:
        client_private_key = PrivateKey(
            bytes.fromhex(client_private_key_hex.removeprefix("0x"))
        )

    client_public_key = client_private_key.public_key.format()
    shared_point = operator_public_key.multiply(client_private_key.secret)

    digest = keccak.new(digest_bits=256)
    digest.update(shared_point.format())
    aes_key = digest.digest()[:16]

    if nonce_hex is None:
        nonce = get_random_bytes(12)
    else:
        nonce = bytes.fromhex(nonce_hex.removeprefix("0x"))

    cipher = AES.new(aes_key, AES.MODE_GCM, nonce=nonce)
    encoded = signed_request_json.encode("utf-8")
    ciphertext, tag = cipher.encrypt_and_digest(
        len(encoded).to_bytes(4, byteorder="big") + encoded
    )

    return "0x" + (ciphertext + tag + nonce + client_public_key).hex()
```

Use fixed values when you want a reproducible result. These sample values are only for this worked example, not for live submissions:

```python theme={null}
operator_public_key = "0x03bc06b4271530d20b4ddb03e0069b00b2c0d03baf45d0ab60582448b6d70c2737"
client_private_key = "0xa178baab5a727c5d08c4ed1118179348d8d43e68c0d5e620757f852cdbc79dfd"
nonce = "0x4eaac2e15c60705ec631d9c8"

signed_request_json = (
    '{"t": "Order", "c": {"symbol": "ETHP", "strategy": "main", "side": "Bid", '
    '"orderType": 0, "nonce": '
    '"0x3137373038373530313938323238333436363300000000000000000000000000", '
    '"clientTimestampMs": 1770875019823, "recvWindowMs": 5000, '
    '"amount": "0.1", "price": "1800", "stopPrice": "0", '
    '"sessionKeySignature": null, "signature": '
    '"0x3d75b1f33e1c0f5aae31ec3bcc5f37825160a4f6e35dcf9c2fd51ab5822ac2787b92eb1ce814ab2a1e8bf52a4a879ffadf2d15a4bb2e8b3c389c9becc16cb6a81b"}}'
)

encrypted_request = encrypt_request(
    operator_public_key_hex=operator_public_key,
    signed_request_json=signed_request_json,
    client_private_key_hex=client_private_key,
    nonce_hex=nonce,
)
```

For this input, the target result is:

```python theme={null}
"0x7f8083b2c8a4a8dbc1baa5da0baf1b8f33974ffdaa0fffbba2212c90f863d7bae48191addcbdc7f404620a0ac9ad37905c042c12f3316f3d0b832a78fe58dce62334c4dd3d0661dd4f62e80d0ab4609755beeaa4ef48148b5456fb4d02feffa11db5a67613ccf0d0667cdb4d8b0f5fad8b4bae667c72867a62e4c6b701e41886a1f934d4e4a76f165dc500fd15ef9cf13b2a7950c57ebafef3064bfe424ef5f25d981234e491ada032d185d620786d23f2492881477abcf6ad158d875c44f704e611b2c5acce34ec8607a3014fe95c922d57da207894121a0e6c6f876c046fb2d3e2bfef4f85fbdf84d34474c864c94463cbaf6d900cdfa9ee26dbf35865d6c58cdd37dfb85c5c8a6c7173269811d90d1f8ea71c77cc4352d9ff471fae32a0ff9efad3026b721d5cf7ab8c1691db79007f8bb297f874f86537391d2d3addcee7ab4f2ea031f483b0708a13efa52775a2bdfab204d5a2d4f32f97b10aaa63c3d864233e428318ee6c317ba620598c43c8f1097d1864eec72eb4a20da82c2ebe048648de28ec88ef1c257f14d509dfdcc537b816deb3b637deeaff41f32e83adf48429fbbd663a59e5ada89a9894dd578cb89086ac16f081219510bf6f7c04491707d1caa38682d42cc4f85d742061c07e70c27d7fd26159a39e5a459a644eaac2e15c60705ec631d9c80208c7ebdceb4366aff5464cd3b05099481304c9d9b1adbadc2459444b038ca2b3"
```

In production, leave `client_private_key_hex` and `nonce_hex` unset so each submission attempt uses fresh values.

#### DDX client library

If you already use `ddx-python`, the library helper applies the same scheme and returns the bytes you can post to `POST /v2/request`:

```python theme={null}
from ddx.rest_client.utils.encryption_utils import encrypt_with_nonce

operator_public_key = "0x03bc06b4271530d20b4ddb03e0069b00b2c0d03baf45d0ab60582448b6d70c2737"
signed_request_json = (
    '{"t": "Order", "c": {"symbol": "ETHP", "strategy": "main", "side": "Bid", '
    '"orderType": 0, "nonce": '
    '"0x3137373038373530313938323238333436363300000000000000000000000000", '
    '"clientTimestampMs": 1770875019823, "recvWindowMs": 5000, '
    '"amount": "0.1", "price": "1800", "stopPrice": "0", '
    '"sessionKeySignature": null, "signature": '
    '"0x3d75b1f33e1c0f5aae31ec3bcc5f37825160a4f6e35dcf9c2fd51ab5822ac2787b92eb1ce814ab2a1e8bf52a4a879ffadf2d15a4bb2e8b3c389c9becc16cb6a81b"}}'
)

encrypted_request = encrypt_with_nonce(operator_public_key, signed_request_json)

print("0x" + encrypted_request.hex())
```

The library generates a fresh client key and a fresh nonce on each call, so the final bytes will differ from run to run.

## Check your result before you submit

1. Confirm that you are encrypting the signed JSON, not an unsigned request object.
2. Confirm that you are posting the encrypted bytes directly to `POST /v2/request`, not wrapping them in another JSON object.
3. Confirm that the operator public key came from the current `GET /v2/encryption-key` response.
4. If you retry after an ambiguous send, re-encrypt the same signed JSON with a fresh client private key and fresh encryption nonce.

## Appendix

Use this byte layout when you build the final payload:

| Part          | What it contains                                                   |
| ------------- | ------------------------------------------------------------------ |
| Plaintext     | `4-byte big-endian length \|\| signed_request_json_utf8_bytes`     |
| AES key       | First 16 bytes of `keccak256(shared_point_bytes)`                  |
| Nonce         | 12 random bytes                                                    |
| Final payload | `ciphertext \|\| tag \|\| nonce \|\| client_public_key_compressed` |

If you still need to produce the signature that goes inside the JSON payload, use [How to Sign DerivaDEX Requests with EIP-712](/how-to-guides/sign-requests-with-eip712).
