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

# HTTP message signatures

> RFC 9421 session-key signing profile and migration from legacy REST authentication.

Monaco supports an **opt-in RFC 9421 signing protocol** alongside legacy
`X-Monaco-*` authentication. Both use the same wallet-authorized Ed25519 session
keys. Deploy server support before opting clients in. Existing clients and
sessions continue to work; legacy authentication is not retired.

## What the signature protects

The signature binds the HTTP method, escaped path, raw query, and exact body
bytes to a registered session key. Changing an order's price or quantity, query
parameter ordering, percent encoding, or JSON whitespace invalidates it.
Use HTTPS: signatures provide integrity and key possession, not confidentiality.

The gateway remains trusted to verify signatures, resolve wallet-to-session-key
bindings, and enforce authorization. Downstream services do not independently
verify the original HTTP signature. This protocol does not make a compromised
gateway unable to fabricate internal commands. Host and scheme are not covered;
keys must not be reused across independently trusted deployments. These trust
boundaries also apply to legacy request signing.

Session expiry, revocation, delegated-agent permissions, and account restrictions
continue to use the existing shared authentication checks. Register a new session
key through the existing wallet flow for rotation, migrate clients, then revoke
the old session. WebSocket and gRPC signing are unchanged.

## Signing profile

This is an application profile of [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html)
using [RFC 9530 Content-Digest](https://www.rfc-editor.org/rfc/rfc9530.html).
It is not a general-purpose verifier for arbitrary HTTP signature profiles.

Every signed request, including GET and empty-body requests, sends:

```http theme={null}
Content-Digest: sha-256=:BASE64_SHA256_OF_BODY:
Signature-Input: monaco=("@method" "@path" "@query" "content-digest");created=1788868800;keyid="SESSION_PUBLIC_KEY_HEX";alg="ed25519"
Signature: monaco=:BASE64_ED25519_SIGNATURE:
```

* The signature label is `monaco`; exactly one dictionary member is accepted.
  The four covered components appear in the order shown. `created`, `keyid`,
  and `alg` are required; their ordering may vary. Extra components, parameters,
  signature labels, or digest algorithms are rejected. Structured-field spaces
  are accepted and signature parameters are serialized canonically. Raw header
  limits are 1,024 bytes for `Signature-Input` and 128 bytes each for `Signature`
  and `Content-Digest`.
* `created` is an integer Unix timestamp in **seconds**, within 30 seconds of
  server time. Legacy timestamps use milliseconds.
* `keyid` is the registered 32-byte session public key, encoded as 64 lowercase
  hexadecimal characters. It is a lookup identifier, not a remote key URL.
* `alg` is exactly `ed25519`.
* `@method` is the uppercase request method. `@path` is the escaped path as sent.
* `@query` starts with `?` and retains raw ordering, duplicates, and encoding.
  Its value is `?` when the query is absent or empty. Do not decode or sort it.
* `content-digest` covers SHA-256 of the exact body bytes, encoded as a structured
  byte sequence. Hash the empty byte string when there is no body.

Two worked `@query` values, since divergent canonicalization is the most common
cause of an otherwise-correct signature failing:

* `GET /api/v1/orders` signs `@path` as `/api/v1/orders` and `@query` as `?`.
* `GET /api/v1/orders?status=FILLED%2CPARTIALLY_FILLED&pageSize=20` signs `@query`
  as `?status=FILLED%2CPARTIALLY_FILLED&pageSize=20` — exactly the bytes on the
  wire, neither percent-decoded nor reordered. Sending the same filter unencoded,
  as `?status=FILLED,PARTIALLY_FILLED&pageSize=20`, is a different signature base
  even though the server reads the same filter, so sign the string you actually
  send.

The signature base has one line per component followed by the signature
parameters. There is **no trailing newline**:

```text theme={null}
"@method": POST
"@path": /api/v1/orders
"@query": ?tag=a%2Fb&tag=a+b
"content-digest": sha-256=:BASE64_SHA256_OF_BODY:
"@signature-params": ("@method" "@path" "@query" "content-digest");created=1788868800;keyid="SESSION_PUBLIC_KEY_HEX";alg="ed25519"
```

Sign the UTF-8 bytes of that base with the session private key. Send the same
body bytes that were hashed. Monaco rejects missing coverage, invalid digests,
unsupported algorithms, ambiguous headers, and mixed RFC/legacy credentials.
Presence of `Signature` or `Signature-Input` selects standard verification; failure
never falls back to legacy authentication.
`Content-Digest` alone is ordinary HTTP integrity metadata, not a signature
credential. It does not select RFC authentication or override the body hash
verified by legacy authentication.

## Freshness, retries, and idempotency

The 30-second clock window limits how long a captured request can be reused.
**It does not prevent an identical signed request from being accepted again
inside that window.** Monaco keeps no nonce or distributed replay
cache. A fresh signature is also not proof that an operation is new.

Use the endpoint's documented idempotency mechanism where available. Do not
blindly retry writes without it. Re-sign a retry if its timestamp has aged out,
while retaining the original operation's idempotency key and payload. Endpoints
without idempotency support provide no exactly-once guarantee.

## TypeScript migration

After server deployment, opt in when constructing the SDK:

```typescript theme={null}
const sdk = createMonacoSDK({
  network: "staging",
  seiRpcUrl: "https://your-sei-rpc.example",
  requestSigning: "rfc9421",
});
```

The default remains `"legacy"`. RFC signing covers the final URL and serialized
string body. The SDK manages all signature headers and rejects caller overrides
or unsupported body types. RFC-signed requests disable redirects, including caller
overrides. Public requests keep their existing
authentication. No automatic protocol downgrade occurs on errors.

## Rust migration

The Rust REST SDK signs generated session-authenticated operations using the
final request URL and serialized body. Existing unsigned constructors keep their
signatures. Public operations remain unsigned.

```rust theme={null}
use monaco_sdk::{Client, RequestSigning, SessionSigner};

// The seed belongs to a session already registered through wallet authentication.
let signer = SessionSigner::new(session_private_seed, RequestSigning::Rfc9421);
let client = Client::new_authenticated("https://staging.apimonaco.xyz", signer);
```

Select `RequestSigning::Legacy` for a server that has not migrated. Authenticated
clients disable HTTP redirects. The low-level `SessionSigner::sign_at` helper is
also available for reusable reqwest requests; its caller must preserve the signed
bytes and disable redirects. Streaming bodies are rejected rather than silently
signed as empty.

## Deployment sequence

1. Deploy the server accepting both protocols and verify signed reads and writes
   with an existing session key in staging.
2. Verify stale timestamps, query/body tampering, revoked sessions, and malformed
   signatures fail. Confirm public routes bypass auth buffering.
3. Release SDKs and opt individual applications in. Keep old clients on legacy
   until their migration is verified. Client rollback selects `legacy`; do not
   roll the server back while RFC-only clients still target it.
4. Retire legacy signing only through a separate coordinated contract change
   after deployed-client usage is known. No retirement date is set.

Signed routes still read and hash their bodies and enforce a 2 MiB bound. Public
routes use their normal extractor limits rather than the signed
body buffer. This change reduces unnecessary global buffering; it does not remove
body hashing from trading requests or promise a trading-latency improvement.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.