FastEx

module
v0.0.0-...-2541f82 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2026 License: MIT

README ΒΆ

FastEx - Hardware-Authenticated Trading Exchange

Bachelor Thesis Project: Production-grade event-driven cryptocurrency exchange system with hardware-backed authentication and deterministic matching.


🎯 Project Overview

FastEx is a modern, security-first cryptocurrency/stocks exchange implementing industry-standard matching algorithms with zero-password authentication. Built using hardware-backed cryptographic signatures (WebAuthn-compatible), the system ensures secure trading without traditional password vulnerabilities.

Key Innovation: Hardware-based Ed25519 authentication with challenge-response protocol eliminates password storage and replay attacks.


System Architecture (as of Sprint 3)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Next.js    β”‚  (Future Frontend)
β”‚  Browser    β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ HTTPS + WebSocket
       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Gateway Service                                 β”‚
β”‚  - REST API (auth, account, order routing)       β”‚
β”‚  - WebSocket (real-time updates - later)         β”‚
β”‚  - Hardware-backed auth (Ed25519)                β”‚
β”‚  - JWT issuance + middleware                      β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ HTTP (internal)
       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Matching Engine Service (engine)                β”‚
β”‚  - Per-instrument in-memory order book           β”‚
β”‚  - Deterministic price-time matching             β”‚
β”‚  - WAL (append-only, crash recovery)             β”‚
β”‚  - REST API (orders, cancel, orderbook snapshot) β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ WAL tail / replay (outbox-style)
       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Kafka (Zookeeper-based, docker-compose)         β”‚
β”‚  - Domain event topics (v1)                      β”‚
β”‚  - Ordering per instrument via message key       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Persistence:
- PostgreSQL (Gateway domain): users + auth challenges
- WAL files (Engine domain): order lifecycle + trades (per instrument)

πŸ” Security Model

NO PASSWORDS. Hardware-backed authentication only.
Registration Flow
  1. Client generates Ed25519 key pair (private key never leaves device)
  2. POST /auth/register with {username, public_key_hex}
  3. Server stores public key in PostgreSQL
Login Flow (Challenge-Response)
  1. POST /auth/challenge with {username}
  2. Server returns {challenge: "base64_random_bytes"}
  3. Client signs challenge: signature = sign(private_key, challenge)
  4. POST /auth/verify with {username, challenge, signature, timestamp}
  5. Server verifies:
    • Signature using stored public key
    • Challenge not expired/reused
    • Timestamp skew < 5 minutes
    • Deletes challenge (one-time use)
  6. Returns JWT: {token: "eyJhbGciOi..."}
  7. Client includes JWT in Authorization: Bearer <token> header
Replay Attack Prevention
  • βœ… Challenge used once then deleted
  • βœ… Challenge expires in 5 minutes
  • βœ… Signature includes timestamp
  • βœ… JWT expires in 15 minutes

πŸ“Š Matching Engine

Price-Time Priority with FIFO Execution
Order Book Structure
  • Two-Sided Book: Separate buy side (bids) and sell side (asks)
  • Price Levels: Orders grouped by price, sorted for optimal matching
    • Buy side: Highest price first (best bid at top)
    • Sell side: Lowest price first (best ask at top)
  • FIFO Queue: Orders at same price level execute in arrival order (first-in, first-out)
Order Matching Flow
  1. Client submits order: BUY 10 BTC @ $50,000 (via authenticated endpoint)
  2. Write-Ahead Log: Order written to disk FIRST (crash safety)
  3. Matching Engine:
    • Checks opposite side (sell orders) for compatible prices
    • Matches best price first (lowest sell price for buy orders)
    • Executes at resting order's price (maker sets price, taker pays)
    • Generates trades: Trade: 10 BTC @ $49,500 (if seller offered $49,500)
  4. Write-Ahead Log: Trades written to disk
  5. Order Book Update:
    • Fully filled orders removed from book
    • Partially filled orders remain with updated quantity
    • Unmatched quantity added to book as new resting order
  6. Response: Client receives trade confirmations and remaining order status
Execution Price Rules
  • Buy order matches sells at price ≀ buy limit
  • Sell order matches buys at price β‰₯ sell limit
  • Trade always executes at the resting order's price (existing book price)
  • Example: Buy at $52,000 matches sell at $50,000 β†’ Trade executes at $50,000
Crash Recovery
  • Write-Ahead Log records every operation before execution
  • On restart: Replay log sequentially to rebuild exact order book state
  • Deterministic replay ensures identical state reconstruction
  • Operations logged: OrderPlaced, TradeExecuted, OrderCancelled

WAL-backed Kafka Writer (Outbox-style)

Kafka is used as an integration bus for downstream services. The matching engine’s WAL is the single source of truth.

Why WAL-backed publishing?

Publishing directly to Kafka from the matching path can lose events when Kafka is down or when the process crashes between β€œstate change” and β€œpublish”.

Using WAL-backed publishing ensures:

  • if an event exists in Kafka, it must have been committed to WAL first
  • if Kafka is temporarily unavailable, events will be published later from WAL
  • the engine remains correct even when Kafka is down (WAL + recovery still work)
Kafka Topics (v1)
  • order.placed.v1
  • trade.executed.v1
  • order.canceled.v1
Writer responsibilities

The WAL-backed Kafka writer (publisher) is responsible for:

  • reading committed WAL entries for an instrument
  • converting WAL entries into domain events
  • publishing to Kafka in WAL order
  • checkpointing progress so it can resume after restarts
Writer Flow (per instrument)

For each configured instrument (ex: BTC, AAPL), one publisher loop runs:

  1. Load cursor

    • The publisher reads a cursor file:
      • <WAL_DIR>/cursors/BTC.cursor
      • <WAL_DIR>/cursors/AAPL.cursor
    • The cursor stores: last_published_sequence_num
    • If cursor does not exist β†’ start from 0
  2. Read WAL entries after cursor

    • The publisher reads the instrument WAL file:
      • <WAL_DIR>/BTC.wal
      • <WAL_DIR>/AAPL.wal
    • It filters entries where sequence_num > last_published_sequence_num
  3. Transform WAL entry β†’ Kafka event

    • ORDER_PLACED β†’ publish to order.placed
    • TRADE_EXECUTED β†’ publish to trade.executed
    • ORDER_CANCELED β†’ publish to order.canceled
  4. Publish to Kafka

    • The Kafka message key is the instrument (BTC, AAPL).
    • This ensures all events for a single instrument go to the same partition, preserving ordering for consumers.
  5. Advance cursor (checkpoint)

    • After a successful publish, the publisher writes the new cursor value:
      • cursor = entry.sequence_num
    • Cursor is written atomically (write temp file β†’ rename) to avoid corruption.
  6. Repeat / poll

    • The publisher runs continuously (polling/tailing behavior).
    • If Kafka is down, publishing fails and the cursor does not advance.
    • On the next retry, the publisher will attempt the same WAL entry again.
Delivery semantics
  • The writer is designed for at-least-once delivery.
  • Duplicates are possible in edge cases (e.g., published to Kafka but crashed before cursor write).
  • Downstream consumers must be idempotent:
    • dedupe trade.executed.v1 using trade_id
    • dedupe order events using order_id
End-to-end event lifecycle (one order)
  1. Engine appends ORDER_PLACED to WAL (durable).
  2. Engine matches and appends TRADE_EXECUTED entries (durable).
  3. WAL publisher observes new WAL entries (sequence > cursor).
  4. WAL publisher publishes corresponding Kafka events (instrument-keyed).
  5. WAL publisher checkpoints cursor so it won’t republish on restart.

Settlement Service

Purpose

Settlement is the first downstream service that consumes matching engine events and turns them into accounting state in PostgreSQL.

It consumes trade events and produces:

  • an append-only ledger (audit trail, double-entry bookkeeping)
  • fast-read balances (materialized view of ledger, per user + asset)
  • idempotency tracking (so Kafka at-least-once delivery is safe)

This service is designed to be deterministic and safe under retries.


Inputs (Kafka)

Topic
  • trade.executed
Event shape (JSON)

Fields expected (based on engine publisher):

  • trade_id (UUID)
  • instrument (string: BTC, AAPL, etc.)
  • buyer_user_id (UUID)
  • seller_user_id (UUID)
  • price (int64)
  • quantity (int64)

Assumption (Sprint 4 simplification):

  • all monetary settlement is in USD
  • trade notional is price * quantity (int64 units)
  • price/quantity are already β€œsmallest units” (integers); no floats

Storage (PostgreSQL)

balances

One row per (user_id, asset).

  • asset can be:
    • USD
    • an instrument symbol like BTC, AAPL

Columns:

  • available NUMERIC(30,10)
  • locked NUMERIC(30,10) (reserved funds; used in later sprints)
ledger_entries

Append-only journal.

Columns:

  • trade_id (UUID)
  • user_id (UUID)
  • asset (string)
  • amount NUMERIC(30,10) signed
    • +X means credit (user receives)
    • -X means debit (user pays)
processed_trades

Idempotency gate.

  • trade_id is unique/PK
  • status:
    • APPLIED (ledger + balances updated)
    • REJECTED (permanent failure, e.g. insufficient funds)
  • optional reason for rejected trades

End-to-end processing flow (per trade.executed message)

Step 0 β€” Parse event

Settlement parses JSON into a TradeExecutedEvent.

If required fields are missing, it marks the trade as:

  • processed_trades.status = REJECTED
  • reason = INVALID_EVENT: ...
Step 1 β€” Start DB transaction

All work is done in a single DB transaction to guarantee atomicity.

Step 2 β€” Idempotency check

Settlement checks if trade_id already exists in processed_trades.

  • if yes β†’ SKIP (do nothing)
  • if no β†’ continue
Step 3 β€” Row locking + insufficient funds check

Settlement ensures relevant balances rows exist and locks them using:

  • SELECT ... FOR UPDATE

Then it checks:

  • buyer USD.available >= price*quantity
  • seller <instrument>.available >= quantity

If any check fails:

  • insert processed_trades(trade_id, status='REJECTED', reason='INSUFFICIENT_FUNDS ...')
  • commit (no ledger, no balance update)
Step 4 β€” Double-entry ledger write (4 entries)

For each trade we write exactly four entries:

Party Asset Amount
buyer instrument +quantity
buyer USD -(price*quantity)
seller instrument -quantity
seller USD +(price*quantity)

This is standard double-entry accounting: every debit has a matching credit.

Step 5 β€” Double-entry validation invariant

Settlement validates accounting correctness:

  • SUM(amount) for the instrument is 0
  • SUM(amount) for USD is 0

If not true, settlement fails the transaction (this indicates a bug).

Step 6 β€” Update balances

Balances are updated via UPSERT + increment:

  • buyer +instrument, buyer -USD
  • seller -instrument, seller +USD
Step 7 β€” Mark trade processed

Settlement inserts:

  • processed_trades(trade_id, status='APPLIED')
Step 8 β€” Commit

Transaction commits.


Delivery semantics

  • Kafka consumption is at-least-once
  • Settlement processing is idempotent by trade_id
  • Duplicates do not change balances because processed_trades is the gate

βœ… Sprint 1 Features (Completed)

Gateway Service
  • βœ… REST API with CORS configuration for browser clients β€” browser-friendly REST entrypoints
  • βœ… WebSocket support with JWT authentication β€” realtime channel foundation (auth-protected)
  • βœ… Health check endpoints β€” service health + docker orchestration readiness
  • βœ… Graceful shutdown handling β€” clean server shutdown and resource cleanup
Hardware-Backed Authentication
  • βœ… Ed25519 signature verification (WebAuthn-compatible) β€” public-key verification server-side
  • βœ… Challenge-response protocol with cryptographic security β€” prove key ownership without secrets on server
  • βœ… No password storage (asymmetric cryptography only) β€” eliminates password database risk
  • βœ… Replay attack prevention (one-time challenge use + expiration) β€” challenge TTL + single-use enforcement
JWT Authorization
  • βœ… Token-based authentication with configurable expiration β€” short-lived access tokens
  • βœ… JWT middleware for protected endpoints β€” centralized auth enforcement
  • βœ… Authorization header transport strategy β€” Authorization: Bearer <token>
PostgreSQL Integration
  • βœ… User registration and public key storage β€” persist identity + public keys
  • βœ… Challenge management with automatic cleanup β€” store/expire login challenges
  • βœ… Connection pooling and health checks β€” stable DB connectivity
  • βœ… Schema migrations on startup β€” deterministic DB schema bootstrapping
Infrastructure
  • βœ… Docker Compose deployment β€” reproducible local environment
  • βœ… Structured logging with configurable levels β€” consistent logs for debugging
  • βœ… Environment-based configuration β€” configurable services without code changes
  • βœ… Production-ready error handling β€” explicit error responses + logging

βœ… Sprint 2 Features (Completed)

Completed Components
Order Data Models
  • βœ… Order types (Limit, Market) β€” standard order representations
  • βœ… Order sides (Buy, Sell) β€” bid/ask direction modeling
  • βœ… Order lifecycle states (New, Open, Partial, Filled, Cancelled, Rejected) β€” clear state transitions
  • βœ… Trade data structures with buyer/seller tracking β€” trade events include both parties
  • βœ… Price representation in smallest units (avoiding floating-point errors) β€” integer prices/qty for correctness
Order Book Data Structure
  • βœ… Two-sided order book (Buy side + Sell side) β€” separate bid/ask books
  • βœ… Price level queues with FIFO ordering β€” time priority within price
  • βœ… Efficient operations: O(log n) insert/delete, O(1) best price lookup β€” performance-oriented structure
  • βœ… Lazy sorting for amortized performance β€” avoid unnecessary resorting
  • βœ… Automatic cleanup of empty price levels β€” keeps book compact
  • βœ… Thread-safe with read/write mutex β€” safe concurrent reads/writes
Matching Algorithm
  • βœ… Price-time priority matching (industry standard) β€” fair execution model
  • βœ… FIFO execution at same price level β€” deterministic ordering
  • βœ… Maker/taker model (execution at resting order's price) β€” canonical execution rule
  • βœ… Partial fill support with order state tracking β€” supports multi-fill orders
  • βœ… Multi-level matching (single order matches multiple resting orders) β€” realistic liquidity consumption
  • βœ… Full match, partial match, and no-match scenarios β€” complete execution outcomes
  • βœ… Atomic matching operations (lock-protected for determinism) β€” consistent results under concurrency
Write-Ahead Log (WAL)
  • βœ… Append-only log for crash recovery β€” durable source of truth
  • βœ… JSON-based entry format (newline-delimited) β€” easy debugging + simple parsing
  • βœ… Entry types: OrderPlaced, TradeExecuted, OrderCancelled β€” complete lifecycle coverage
  • βœ… Monotonic sequence numbering β€” strict ordering of state transitions
  • βœ… fsync after each write (durability guarantee) β€” survives crashes
  • βœ… Concurrent write safety with mutex protection β€” safe multi-request usage
  • βœ… Log replay for state reconstruction β€” deterministic restart recovery
Engine Integration
  • βœ… Orchestration layer combining OrderBook + WAL β€” cohesive engine component
  • βœ… Write-ahead pattern: log BEFORE mutate β€” correctness-first mutation ordering
  • βœ… Deterministic recovery from crash β€” replay produces same resulting book
  • βœ… Proper order lifecycle management during replay β€” consistent filled/partial/open handling

βœ… Sprint 3 Features (Completed) β€” Kafka Integration (WAL-backed)

  • βœ… Kafka + Zookeeper in Docker Compose β€” local event backbone
  • βœ… Topics for domain events β€” order.placed, trade.executed, order.canceled
  • βœ… WAL-backed Kafka writer (outbox-style) β€” events published only after WAL commit
  • βœ… Per-instrument ordering via Kafka key β€” key = instrument (BTC/AAPL), ordered consumption per instrument
  • βœ… Cursor checkpointing β€” publisher resumes from last published WAL sequence after restart
  • βœ… At-least-once delivery model β€” duplicates possible; downstream consumers must be idempotent

πŸš€ Future Sprints

Sprint 4: Settlement Service
  • Kafka consumer for TradeExecuted events
  • PostgreSQL balance management
  • Idempotent transaction processing
  • Double-entry bookkeeping validation
Sprint 5: Market Data Service
  • Order book snapshots and deltas
  • OHLCV candle aggregation
  • WebSocket real-time feeds
  • Trade history API
Sprint 6: End-to-End Integration
  • Full request flow: Gateway β†’ Matching β†’ Kafka β†’ Settlement
  • WebSocket order status updates
  • Rate limiting and circuit breakers
  • Load testing (target: 1000+ orders/sec)
Sprint 7: Observability (Prometheus, Grafana)
  • Metrics: latency histograms, throughput, Kafka lag
  • Distributed tracing with correlation IDs
  • Centralized logging
  • Alerting rules
Sprint 8: Advanced Features + Thesis Defense
  • Advanced order types (Stop-Loss, Post-Only)
  • Order amendment support
  • Performance benchmarking and optimization
  • Architecture documentation and threat modeling
  • Thesis defense preparation
Sprint 9: Frontend Development (Next.js)
  • User interface for registration and login
  • WebAuthn/hardware key integration in browser
  • Real-time order book visualization
  • Order submission interface
  • Live trade feed and order status updates
  • Portfolio and balance display
  • Responsive design for mobile and desktop
  • WebSocket integration for real-time data

🎨 System Design Highlights

Matching Engine
  • Deterministic execution for reproducible behavior
  • Crash-safe through WAL replay
  • Price-time priority ensures fairness
  • Lock-based concurrency (one match at a time per instrument)
  • Future: Per-instrument goroutines for horizontal scalability
Data Integrity
  • Write-Ahead Logging prevents data loss
  • Atomic operations ensure consistency
  • Idempotent operations for retry safety
  • Sequence numbers for ordering guarantees
Performance Considerations
  • In-memory order book for low latency
  • Lazy sorting for amortized O(1) inserts
  • Connection pooling for database efficiency
  • fsync tuning tradeoff (durability vs throughput)

πŸ› οΈ Technology Stack

  • Language: Go 1.21+
  • Database: PostgreSQL 15+
  • Authentication: Ed25519 cryptographic signatures
  • API: REST + WebSocket
  • Deployment: Docker Compose
  • Future: Kafka, Prometheus, Grafana

πŸ“¦ Getting Started

Prerequisites
  • Go 1.21 or higher
  • Docker & Docker Compose
  • PostgreSQL 15+ (via Docker)
Installation
# Clone the repository
git clone https://github.com/AlexHornet76/FastEx.git
cd FastEx

# Start PostgreSQL with Docker Compose
docker-compose up -d postgres

# Run the gateway service
cd gateway
go run cmd/main.go

# Run the matching engine (future)
cd engine
go run cmd/main.go
Running Tests
# Test WAL implementation
go test ./engine/internal/wal -v

# Test matching engine
go test ./engine/internal/matching -v

# Test order book
go test ./engine/internal/orderbook -v

πŸ“ License

MIT License (for thesis purposes)


πŸ‘€ Author

Alex-Andrei Hornet
Bachelor Thesis Project - 2026


πŸ™ Acknowledgments

This project implements industry-standard exchange architecture patterns used by major cryptocurrency exchanges, adapted for educational and research purposes.

Directories ΒΆ

Path Synopsis
kafka-consumer command
keygen command
test-client command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL