Security

Authentication

API keys, JWT tokens, and cryptographic signature authentication for LX

Authentication

LX supports multiple authentication methods designed for different use cases, from simple API keys to cryptographic signatures for high-security operations.

Authentication Methods

MethodUse CaseSecurity LevelLatency
API Key + HMACGeneral API accessHigh~1ms
JWT BearerWeb/mobile appsMedium-High~0.5ms
Ed25519 SignatureTrading operationsVery High~2ms
Dilithium SignatureQuantum-safe tradingHighest~5ms
Passkey/WebAuthnUser loginHighVariable

API Key Authentication

Generating API Keys

# Via CLI
lx-dex api-key create --name "Trading Bot" --permissions "trade,read"

# Output
API Key: lx_live_abc123xyz789...
Secret:  sk_live_secret_key_here...

# IMPORTANT: Secret is shown only once. Store securely!

Request Signing

All authenticated requests must include HMAC-SHA512 signature:

import { createHmac } from 'crypto'

interface SignedRequest {
  apiKey: string
  timestamp: number
  signature: string
  body?: string
}

function signRequest(
  apiKey: string,
  secret: string,
  method: string,
  path: string,
  body?: object
): SignedRequest {
  const timestamp = Date.now()
  const bodyStr = body ? JSON.stringify(body) : ''

  // Create signature payload
  const payload = `${timestamp}${method}${path}${bodyStr}`

  // Sign with HMAC-SHA512
  const signature = createHmac('sha512', secret)
    .update(payload)
    .digest('hex')

  return {
    apiKey,
    timestamp,
    signature,
    body: bodyStr
  }
}

// Usage
const signed = signRequest(
  'lx_live_abc123',
  'sk_live_secret',
  'POST',
  '/api/v1/orders',
  { symbol: 'BTC-USD', side: 'buy', price: '50000', quantity: '1.0' }
)

// HTTP Request
fetch('https://api.dex.lux.network/api/v1/orders', {
  method: 'POST',
  headers: {
    'X-LX-API-Key': signed.apiKey,
    'X-LX-Timestamp': signed.timestamp.toString(),
    'X-LX-Signature': signed.signature,
    'Content-Type': 'application/json'
  },
  body: signed.body
})

Go Implementation

package auth

import (
    "crypto/hmac"
    "crypto/sha512"
    "encoding/hex"
    "fmt"
    "strconv"
    "time"
)

// Signer handles API request signing
type Signer struct {
    APIKey string
    Secret []byte
}

// NewSigner creates a new request signer
func NewSigner(apiKey, secret string) *Signer {
    return &Signer{
        APIKey: apiKey,
        Secret: []byte(secret),
    }
}

// Sign creates signature for a request
func (s *Signer) Sign(method, path string, body []byte) (timestamp int64, signature string) {
    timestamp = time.Now().UnixMilli()

    // Build payload: timestamp + method + path + body
    payload := fmt.Sprintf("%d%s%s%s", timestamp, method, path, string(body))

    // HMAC-SHA512
    mac := hmac.New(sha512.New, s.Secret)
    mac.Write([]byte(payload))
    signature = hex.EncodeToString(mac.Sum(nil))

    return timestamp, signature
}

// VerifySignature verifies an incoming request signature
func VerifySignature(
    apiKey string,
    secret []byte,
    timestamp int64,
    method, path string,
    body []byte,
    signature string,
) error {
    // Check timestamp freshness (5 minute window)
    now := time.Now().UnixMilli()
    if abs(now-timestamp) > 5*60*1000 {
        return ErrTimestampExpired
    }

    // Rebuild expected signature
    payload := fmt.Sprintf("%d%s%s%s", timestamp, method, path, string(body))
    mac := hmac.New(sha512.New, secret)
    mac.Write([]byte(payload))
    expected := hex.EncodeToString(mac.Sum(nil))

    // Constant-time comparison
    if !hmac.Equal([]byte(signature), []byte(expected)) {
        return ErrInvalidSignature
    }

    return nil
}

API Key Permissions

PermissionDescriptionRisk Level
readView balances, orders, historyLow
tradePlace and cancel ordersMedium
withdrawWithdraw fundsHigh
adminManage API keys, settingsHigh
// Create restricted API key
const key = await dex.createApiKey({
  name: 'Read-Only Bot',
  permissions: ['read'],
  ipWhitelist: ['192.168.1.0/24'],
  expiresAt: new Date('2025-12-31')
})

JWT Authentication

Token Flow

┌──────────┐     ┌──────────┐     ┌──────────┐
│  Client  │────►│   Auth   │────►│    API   │
│          │     │  Server  │     │  Server  │
└──────────┘     └──────────┘     └──────────┘
     │                │                │
     │ 1. Login       │                │
     │───────────────►│                │
     │                │                │
     │ 2. JWT + Refresh               │
     │◄───────────────│                │
     │                │                │
     │ 3. API Request with JWT        │
     │────────────────────────────────►│
     │                │                │
     │ 4. Response                     │
     │◄────────────────────────────────│

JWT Structure

// Access Token (short-lived: 15 minutes)
interface AccessTokenPayload {
  sub: string          // User ID
  aud: 'lx-dex-api'    // Audience
  iss: 'lx-dex-auth'   // Issuer
  iat: number          // Issued at
  exp: number          // Expiration
  permissions: string[] // Granted permissions
  tier: 'retail' | 'pro' | 'institutional'
}

// Refresh Token (long-lived: 7 days)
interface RefreshTokenPayload {
  sub: string
  jti: string          // Unique token ID (for revocation)
  iat: number
  exp: number
  family: string       // Token family (for rotation detection)
}

Token Refresh

async function refreshAccessToken(refreshToken: string): Promise<TokenPair> {
  const response = await fetch('https://auth.dex.lux.network/token/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken })
  })

  if (!response.ok) {
    // Refresh token expired or revoked
    throw new AuthenticationError('Session expired, please login again')
  }

  const { access_token, refresh_token } = await response.json()
  return { accessToken: access_token, refreshToken: refresh_token }
}

// Token refresh middleware
class TokenManager {
  private accessToken: string
  private refreshToken: string
  private expiresAt: number

  async getAccessToken(): Promise<string> {
    // Refresh 1 minute before expiry
    if (Date.now() > this.expiresAt - 60000) {
      const tokens = await refreshAccessToken(this.refreshToken)
      this.accessToken = tokens.accessToken
      this.refreshToken = tokens.refreshToken
      this.expiresAt = this.parseExpiry(tokens.accessToken)
    }
    return this.accessToken
  }
}

Cryptographic Signatures

Ed25519 Signatures (Classical)

package auth

import (
    "crypto/ed25519"
    "encoding/hex"
)

// Ed25519Signer for classical signatures
type Ed25519Signer struct {
    privateKey ed25519.PrivateKey
    publicKey  ed25519.PublicKey
}

// NewEd25519Signer creates signer from seed
func NewEd25519Signer(seed []byte) *Ed25519Signer {
    privateKey := ed25519.NewKeyFromSeed(seed)
    return &Ed25519Signer{
        privateKey: privateKey,
        publicKey:  privateKey.Public().(ed25519.PublicKey),
    }
}

// Sign signs a message
func (s *Ed25519Signer) Sign(message []byte) []byte {
    return ed25519.Sign(s.privateKey, message)
}

// Verify verifies a signature
func (s *Ed25519Signer) Verify(message, signature []byte) bool {
    return ed25519.Verify(s.publicKey, message, signature)
}

// SignOrder creates signed order for trading
func (s *Ed25519Signer) SignOrder(order *Order) (*SignedOrder, error) {
    // Serialize order deterministically
    orderBytes, err := order.Marshal()
    if err != nil {
        return nil, err
    }

    // Sign
    signature := s.Sign(orderBytes)

    return &SignedOrder{
        Order:     order,
        Signature: hex.EncodeToString(signature),
        PublicKey: hex.EncodeToString(s.publicKey),
        Algorithm: "ed25519",
    }, nil
}

Dilithium Signatures (Post-Quantum)

package auth

import (
    "github.com/cloudflare/circl/sign/dilithium/mode3"
)

// DilithiumSigner for post-quantum signatures
type DilithiumSigner struct {
    privateKey mode3.PrivateKey
    publicKey  mode3.PublicKey
}

// NewDilithiumSigner generates new key pair
func NewDilithiumSigner() (*DilithiumSigner, error) {
    pub, priv, err := mode3.GenerateKey(nil)
    if err != nil {
        return nil, err
    }
    return &DilithiumSigner{
        privateKey: *priv,
        publicKey:  *pub,
    }, nil
}

// Sign signs message with Dilithium
func (s *DilithiumSigner) Sign(message []byte) []byte {
    return mode3.Sign(&s.privateKey, message)
}

// Verify verifies Dilithium signature
func Verify(publicKey *mode3.PublicKey, message, signature []byte) bool {
    return mode3.Verify(publicKey, message, signature)
}

// SignatureSize returns Dilithium signature size (3,309 bytes for Level 3)
func (s *DilithiumSigner) SignatureSize() int {
    return mode3.SignatureSize // 3309 bytes
}
package auth

// HybridSigner combines Ed25519 + Dilithium
type HybridSigner struct {
    ed25519   *Ed25519Signer
    dilithium *DilithiumSigner
}

// HybridSignature contains both signatures
type HybridSignature struct {
    Ed25519Sig   []byte `json:"ed25519"`
    DilithiumSig []byte `json:"dilithium"`
}

// Sign creates hybrid signature (both must verify)
func (s *HybridSigner) Sign(message []byte) *HybridSignature {
    return &HybridSignature{
        Ed25519Sig:   s.ed25519.Sign(message),
        DilithiumSig: s.dilithium.Sign(message),
    }
}

// Verify checks both signatures
func (s *HybridSigner) Verify(message []byte, sig *HybridSignature) bool {
    // BOTH must verify for hybrid security
    ed25519Valid := s.ed25519.Verify(message, sig.Ed25519Sig)
    dilithiumValid := Verify(&s.dilithium.publicKey, message, sig.DilithiumSig)

    return ed25519Valid && dilithiumValid
}

WebAuthn / Passkeys

Registration Flow

// Server: Generate challenge
const challenge = crypto.getRandomValues(new Uint8Array(32))

// Client: Create credential
const credential = await navigator.credentials.create({
  publicKey: {
    challenge,
    rp: {
      name: 'LX',
      id: 'dex.lux.network'
    },
    user: {
      id: Uint8Array.from(userId, c => c.charCodeAt(0)),
      name: userEmail,
      displayName: userName
    },
    pubKeyCredParams: [
      { alg: -7, type: 'public-key' },   // ES256 (P-256)
      { alg: -8, type: 'public-key' },   // EdDSA
      { alg: -257, type: 'public-key' }  // RS256
    ],
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      residentKey: 'required'
    },
    timeout: 60000,
    attestation: 'direct'
  }
})

// Server: Verify and store credential
const verified = await verifyRegistration(credential)

Authentication Flow

// Server: Generate challenge
const challenge = crypto.getRandomValues(new Uint8Array(32))

// Client: Get assertion
const assertion = await navigator.credentials.get({
  publicKey: {
    challenge,
    rpId: 'dex.lux.network',
    allowCredentials: [{
      id: credentialId,
      type: 'public-key',
      transports: ['internal', 'hybrid']
    }],
    userVerification: 'required',
    timeout: 60000
  }
})

// Server: Verify assertion and issue session
const session = await verifyAssertion(assertion)

Security Best Practices

API Key Security

// DO: Store secrets securely
const apiSecret = process.env.LX_DEX_API_SECRET

// DON'T: Hardcode secrets
const apiSecret = 'sk_live_abc123...' // NEVER DO THIS

// DO: Rotate keys periodically
await dex.rotateApiKey(oldKeyId)

// DO: Use IP whitelisting
await dex.updateApiKey(keyId, {
  ipWhitelist: ['192.168.1.100', '10.0.0.0/8']
})

// DO: Set expiration
await dex.createApiKey({
  expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days
})

Signature Security

// DO: Use constant-time comparison
import "crypto/subtle"

func verifyMAC(expected, actual []byte) bool {
    return subtle.ConstantTimeCompare(expected, actual) == 1
}

// DON'T: Use regular comparison (timing attack vulnerable)
func verifyMAC(expected, actual []byte) bool {
    return bytes.Equal(expected, actual) // VULNERABLE
}

// DO: Validate timestamp freshness
const maxTimestampAge = 5 * time.Minute

func validateTimestamp(ts int64) error {
    age := time.Since(time.UnixMilli(ts))
    if age > maxTimestampAge || age < -maxTimestampAge {
        return ErrTimestampOutOfRange
    }
    return nil
}

Session Security

// DO: Use secure cookie settings
res.cookie('session', token, {
  httpOnly: true,     // Prevent XSS access
  secure: true,       // HTTPS only
  sameSite: 'strict', // CSRF protection
  maxAge: 900000,     // 15 minutes
  path: '/',
  domain: '.dex.lux.network'
})

// DO: Implement session binding
interface Session {
  userId: string
  ipHash: string        // Bind to IP
  userAgentHash: string // Bind to browser
  createdAt: number
}

// DO: Detect session anomalies
function detectAnomaly(session: Session, request: Request): boolean {
  const currentIpHash = hash(request.ip)
  const currentUaHash = hash(request.headers['user-agent'])

  return session.ipHash !== currentIpHash ||
         session.userAgentHash !== currentUaHash
}

Rate Limits by Authentication Level

LevelRequests/secOrders/secWebsocket Streams
Unauthenticated1001
API Key (Basic)100105
API Key (Pro)1,00010050
Institutional10,0001,000500

Troubleshooting

Common Errors

Error CodeMessageSolution
AUTH_001Invalid API keyCheck key is active and not expired
AUTH_002Invalid signatureVerify signing algorithm and payload
AUTH_003Timestamp expiredSync system clock, use NTP
AUTH_004IP not whitelistedAdd IP to key whitelist
AUTH_005Insufficient permissionsRequest key with required permissions
AUTH_006Rate limit exceededReduce request rate or upgrade tier
AUTH_007Session expiredRefresh token or re-authenticate

Debug Mode

// Enable request debugging (development only)
const dex = await DEX({
  debug: true,
  onRequest: (req) => {
    console.log('Request:', {
      method: req.method,
      path: req.path,
      timestamp: req.timestamp,
      signature: req.signature.substring(0, 16) + '...'
    })
  }
})