Authentication
PolTrading uses two authentication mechanisms depending on the endpoint.
Overview
| Endpoint | Auth Type | Description |
|---|---|---|
GET /, GET /healthz | None | Public endpoints |
GET /v1/markets, GET /v1/quotes | Geo-gating only | Read market data |
POST /v1/orders | Signed payload | Envelope checked at the edge (market ID, deadline, size); the base64-decoded OrderRequest payload is forwarded to PolCore POST /orders byte-exact |
POST /internal/ingest/market | HMAC + Ed25519 | Feed ingestion |
Credential Management
| Credential | Storage | Rotation | Access |
|---|---|---|---|
| HMAC signing key | Environment variable (testnet; never committed) | Manual, documented procedure | Feed ingestion only |
| Ed25519 private key | Environment variable (testnet; never committed per AGENTS.md secrets rules) | Per-deployment | Feed signature verification |
| mTLS client certificate | File system (restricted permissions) | Automated via cert-manager | Outpost ↔ PolCore tunnel (mutual: pinned CA authenticates the backend on the outbound leg; client cert authenticates the outpost on the backend's intake leg) |
| GeoIP2 database | Local file (read-only) | Monthly update | Edge middleware |
All credentials are zeroed in memory when the service is sealed via scuttle.
HMAC-SHA256 (Feed Ingestion)
Used for authenticating feed data from the AI Feeder pipeline.
Required Headers
| Header | Format | Description |
|---|---|---|
X-Outpost-Timestamp | Unix epoch seconds | Must be within 300s of server time |
X-Outpost-Nonce | 16-128 char hex | Unique per request (replay protection) |
X-Outpost-Signature | v1=<hex-hmac> | HMAC-SHA256 signature |
Signing Formula
signature = HMAC-SHA256(
key = OUTPOST_INGEST_HMAC,
message = timestamp + "." + nonce + "." + body
)
Python Example
import hashlib
import hmac
import os
import time
def sign_request(hmac_key: str, body: bytes) -> dict:
timestamp = str(int(time.time()))
nonce = os.urandom(16).hex()
# Compute HMAC-SHA256
mac = hmac.new(
bytes.fromhex(hmac_key),
f"{timestamp}.{nonce}.".encode() + body,
hashlib.sha256,
).hexdigest()
return {
"X-Outpost-Timestamp": timestamp,
"X-Outpost-Nonce": nonce,
"X-Outpost-Signature": f"v1={mac}",
}
# Usage with feed ingestion
body = json.dumps({"conditionId": "0x...", "d": "...", "s": "...", "k": "ed25519-sha256"})
headers = sign_request(HMAC_KEY, body.encode())
response = requests.post(
"https://outpost.poltrading.com/internal/ingest/market",
headers=headers,
data=body,
)
Go Example
func signRequest(hmacKey string, body []byte) map[string]string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
nonce := make([]byte, 16)
rand.Read(nonce)
mac := hmac.New(sha256.New, []byte(hmacKey))
mac.Write([]byte(timestamp + "." + hex.EncodeToString(nonce) + "."))
mac.Write(body)
sig := "v1=" + hex.EncodeToString(mac.Sum(nil))
return map[string]string{
"X-Outpost-Timestamp": timestamp,
"X-Outpost-Nonce": hex.EncodeToString(nonce),
"X-Outpost-Signature": sig,
}
}
Verification Flow
- Parse
X-Outpost-Timestampas int64; reject if|server_time - ts| > 300 - Extract signature after
v1=prefix - Decode hex signature
- Compute expected HMAC; compare using constant-time
hmac.Equal - On success, claim nonce (dedup map, 600s TTL)
Ed25519 Content Signature
Used for signing market data content in signed envelopes.
Envelope Format
{
"d": "base64-encoded-document",
"s": "hex-encoded-ed25519-signature",
"k": "ed25519-sha256"
}
Signing (Producer)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import hashes, serialization
import base64, hashlib, json
def sign_envelope(private_key: Ed25519PrivateKey, document: bytes) -> dict:
digest = hashlib.sha256(document).digest()
sig = private_key.sign(digest)
return {
"d": base64.b64encode(document).decode(),
"s": sig.hex(),
"k": "ed25519-sha256",
}
Verification (Consumer)
- Decode
dfrom base64 to raw bytes - Compute
digest = SHA256(raw_bytes) - Decode hex
s(must be exactly 64 bytes) - Verify with
ed25519.Verify(publicKey, digest, signature)
Common Errors
| Error | Cause | Fix |
|---|---|---|
401 unauthorized | Bad HMAC signature | Check key and signing formula |
401 ingest_replay | Nonce already used | Generate unique nonce per request |
401 timestamp expired | Timestamp too old/new | Sync clock, check REPLAY_WINDOW_SECS |
451 restricted_jurisdiction | Geo-blocked country | Use VPN or different endpoint |
429 too many requests | Rate limit exceeded | Back off, check RPS_PER_IP |