LPsLux Proposals
Consensus Systems
LP-119

DEX Order Authorization Signature

Draft
Created
2026-08-21

Abstract

This LP specifies the exact bytes a trader signs to authorize an order on the Lux D-Chain matcher, and the rule that the signed bytes must be the whole order the matcher acts on. It is the order-side companion to LP-117 (the finality signed message): both record a signed-message layout from the shipping code so an independent implementation signs and verifies byte-identically.

Motivation

A signed order is a standing authorization: a relay carries it from the trader to the matcher, and the matcher acts on it later. If the signature covers less than the matcher reads, any relay between the two can rewrite the uncovered fields while the signature still verifies — changing the price guard, the flags, or the size a fill is charged against. And if the signature names no chain and no deadline, it is good on every D-Chain forever, so an order signed on one chain can be lifted onto another and replayed indefinitely.

On a permissionless exchange, where the relay is untrusted and any chain may exist, the signed subset must equal the acted-on order, and the signature must name its authorization scope.

Specification

The signed digest

The order-authorization signature is over keccak256 of these bytes, in order, big-endian. It is a hand-written fixed-width layout so a field cannot silently drop out of the preimage.

fieldwidthnotes
domain tag21ASCII lux.dex.order.auth.v1
network_id4authorization scope
chain_id32authorization scope
expiry8authorization scope — the signature is void after this
order_id8
symbol2 + nuint16 length, then bytes
side1
type1
price_int8limit price in integer ticks
size_ticks8size in integer ticks; an off-tick size is refused, never rounded
stop_price8IEEE-754 bits
limit_price8IEEE-754 bits
display_size8iceberg visible size, IEEE-754 bits
peg_offset8IEEE-754 bits
take_profit8IEEE-754 bits
stop_loss8IEEE-754 bits
post_only1
reduce_only1
hidden1
flags4
user_id2 + nuint16 length, then bytes — the account a fill is attributed to
client_id2 + nuint16 length, then bytes
sender20

The binding rules

  • The signed subset is the whole order the matcher acts on. Every field the matcher reads to reach a decision — the price guard, the post-only / reduce-only / STP flags, the stop and bracket legs, the iceberg's visible size, the lifetime, and the account the fill is charged to — is inside the digest. A field the matcher reads but the digest omits is rewritable by any relay, so the binding rule for this signature is: if the matcher reads it, it is signed.

  • The authorization scope is named. network_id, chain_id, and expiry are bound ahead of the economic fields. A signature made for one chain does not recover its signer on another, and a signature is void after expiry. The D-Chain transaction digest binds the same scope under lux.dchain.tx.auth.v2 for the same reason.

  • Sizes are exact, not rounded. size_ticks is an integer number of ticks; a size that is not an exact number of ticks is refused rather than snapped, so the digest is injective in size — two distinct sizes cannot share a signature. Advanced-order floats are bound by their exact IEEE-754 bits, identical on every architecture.

  • The domain tag is versioned. lux.dex.order.auth.v1 keeps an order signature from colliding with any other keccak256 preimage in the stack, and makes the next encoding a distinct namespace rather than a silent reinterpretation of these bytes.

Rationale

A fixed-width hand-written layout, rather than a schema with optional fields, is what makes "if the matcher reads it, it is signed" checkable: adding a field the matcher reads is a visible change to this table and to the v1 domain tag, not a value that quietly appears outside the signature. The float fields are bound by their bits rather than a decimal rendering so the preimage is identical on every node and injective.

Backwards Compatibility

This LP documents the shipped v1 order-authorization digest. A prior version of the code signed only the economic fields and named no scope; a signature made under that scheme does not verify under this one, which is the intended effect — those signatures were replayable across chains and did not bind what the matcher acted on.

Reference Implementation

github.com/luxfi/dex, pkg/dex/signed_order.go (SigningHash). The authorization scope fields live on SignedOrder; the economic and advanced-order fields live on the matcher's Order, and the digest binds both.

Security Considerations

The signature is the only thing standing between a trader's intent and a fill. A conformance suite MUST assert byte-equality of the digest for a shared corpus of orders across implementations, and MUST assert that mutating any matcher-read field changes the digest — a field the matcher reads that does not move the digest is a relay-rewrite surface, not a compatibility detail.