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
- Client generates Ed25519 key pair (private key never leaves device)
- POST
/auth/registerwith{username, public_key_hex} - Server stores public key in PostgreSQL
Login Flow (Challenge-Response)
- POST
/auth/challengewith{username} - Server returns
{challenge: "base64_random_bytes"} - Client signs challenge:
signature = sign(private_key, challenge) - POST
/auth/verifywith{username, challenge, signature, timestamp} - Server verifies:
- Signature using stored public key
- Challenge not expired/reused
- Timestamp skew < 5 minutes
- Deletes challenge (one-time use)
- Returns JWT:
{token: "eyJhbGciOi..."} - 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
- Client submits order:
BUY 10 BTC @ $50,000(via authenticated endpoint) - Write-Ahead Log: Order written to disk FIRST (crash safety)
- 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)
- Write-Ahead Log: Trades written to disk
- 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
- 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.v1trade.executed.v1order.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:
-
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
- The publisher reads a cursor file:
-
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
- The publisher reads the instrument WAL file:
-
Transform WAL entry β Kafka event
ORDER_PLACEDβ publish toorder.placedTRADE_EXECUTEDβ publish totrade.executedORDER_CANCELEDβ publish toorder.canceled
-
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.
- The Kafka message key is the instrument (
-
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.
- After a successful publish, the publisher writes the new cursor value:
-
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.v1usingtrade_id - dedupe order events using
order_id
- dedupe
End-to-end event lifecycle (one order)
- Engine appends
ORDER_PLACEDto WAL (durable). - Engine matches and appends
TRADE_EXECUTEDentries (durable). - WAL publisher observes new WAL entries (sequence > cursor).
- WAL publisher publishes corresponding Kafka events (instrument-keyed).
- 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).
assetcan be:USD- an instrument symbol like
BTC,AAPL
Columns:
availableNUMERIC(30,10)lockedNUMERIC(30,10) (reserved funds; used in later sprints)
ledger_entries
Append-only journal.
Columns:
trade_id(UUID)user_id(UUID)asset(string)amountNUMERIC(30,10) signed+Xmeans credit (user receives)-Xmeans debit (user pays)
processed_trades
Idempotency gate.
trade_idis unique/PKstatus:APPLIED(ledger + balances updated)REJECTED(permanent failure, e.g. insufficient funds)
- optional
reasonfor 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 = REJECTEDreason = 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 0SUM(amount)forUSDis 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_tradesis 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
TradeExecutedevents - 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.