DEV Community

Cover image for Adding Cryptographic Audit Trails to FIX Without Touching Your Trading Engine

Adding Cryptographic Audit Trails to FIX Without Touching Your Trading Engine

The Problem No One Talks About

You've built a solid algo trading system. FIX messages flow perfectly. Executions are sub-millisecond. Life is good.

Then the auditor calls.

"Can you prove this order wasn't modified after the fact?"

"Can you show the complete chain from signal to execution?"

"How do we know these logs are complete?"

Your answer: "Trust us, we have logs."

Their response: 😐


Why Traditional Logging Falls Short

Here's the uncomfortable truth about trading system logs:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Traditional Logging            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  βœ— Admin can modify logs                β”‚
β”‚  βœ— No proof of completeness             β”‚
β”‚  βœ— Timestamp β‰  proof of time            β”‚
β”‚  βœ— Auditor must trust submitter         β”‚
β”‚  βœ— Deletion is undetectable             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

MiFID II RTS 25 requires clock sync to 100ΞΌs. But even if your timestamps are perfect, there's no cryptographic proof that:

  1. The log wasn't modified
  2. No entries were deleted
  3. The sequence is complete

The Sidecar Solution

What if you could add cryptographic proof to every trading event without touching your FIX engine?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Trading System                       β”‚
β”‚                                                         β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚
β”‚   β”‚  Algo    │────▢│  Order   │────▢│   FIX    │──────▢│ Venue
β”‚   β”‚  Engine  β”‚     β”‚  Manager β”‚     β”‚  Engine  β”‚       β”‚
β”‚   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜       β”‚
β”‚        β”‚                β”‚                β”‚              β”‚
β”‚        β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚        β”‚    β”‚  Event Tap (async copy)                   β”‚
β”‚        β–Ό    β–Ό                                           β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚   β”‚         VCP Sidecar Process         β”‚              β”‚
β”‚   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚              β”‚
β”‚   β”‚  β”‚ Collect β”‚β–Άβ”‚  Hash   β”‚β–Άβ”‚ Sign  β”‚  │──▢ Auditor   β”‚
β”‚   β”‚  β”‚ Events  β”‚ β”‚  Chain  β”‚ β”‚Ed25519β”‚  β”‚              β”‚
β”‚   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚              β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key: FIX messages flow unchanged. Zero latency impact.
Enter fullscreen mode Exit fullscreen mode

The sidecar:

  • Receives event copies asynchronously (no blocking)
  • Hashes each event with SHA-256
  • Chains to previous hash (tamper detection)
  • Signs with Ed25519 (non-repudiation)
  • Anchors batches in Merkle trees (efficient verification)

Show Me The Code

Here's what a FIX ExecutionReport looks like alongside its VCP audit event:

FIX Message (unchanged)

8=FIX.4.4|9=256|35=8|49=BROKER|56=CLIENT|
11=ORD-2025-001|37=EXE-12345|17=EXEC-67890|
150=F|39=2|55=XAUUSD|54=1|38=100|44=2650.50|
14=100|151=0|31=2650.45|32=100|6=2650.45|
60=20251222-14:30:05.120|10=078|
Enter fullscreen mode Exit fullscreen mode

VCP Audit Event (parallel)

{
  "header": {
    "event_id": "01934e3a-7b2c-7f93-8f2a-1234567890ab",
    "trace_id": "01934e3a-6a1b-7c82-9d1b-0987654321dc",
    "timestamp": "2025-12-22T14:30:05.120000000Z",
    "event_type": "EXE",
    "clock_sync": "PTP_SYNCED",
    "symbol": "XAUUSD"
  },
  "payload": {
    "order_id": "EXE-12345",
    "side": "BUY",
    "price": "2650.50",
    "quantity": "100",
    "filled_qty": "100"
  },
  "security": {
    "event_hash": "sha256:8f2a7b3c4d5e6f...",
    "prev_hash": "sha256:a1b2c3d4e5f6...",
    "signature": "ed25519:7g8h9i0j..."
  }
}
Enter fullscreen mode Exit fullscreen mode

The prev_hash links to the previous event. Break the chain? Instantly detectable.


The Hash Chain Explained

Event 1 (SIG)          Event 2 (ORD)          Event 3 (EXE)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ hash: a1b2c3 β”‚       β”‚ hash: d4e5f6 β”‚       β”‚ hash: g7h8i9 β”‚
β”‚ prev: 000000 │──────▢│ prev: a1b2c3 │──────▢│ prev: d4e5f6 β”‚
β”‚ sig: xxxxx   β”‚       β”‚ sig: yyyyy   β”‚       β”‚ sig: zzzzz   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚                      β”‚                      β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                   Merkle Root: m1n2o3
                   (anchored periodically)
Enter fullscreen mode Exit fullscreen mode

Try to:

  • Delete Event 2? Chain breaks at Event 3.
  • Modify Event 1? Hash changes, Event 2's prev_hash doesn't match.
  • Insert fake event? Signature verification fails.

Mapping to FIX Tags

For implementations that want audit references embedded in FIX messages (optional):

Tag Name Description
20001 AuditEventHash SHA-256 hash (first 16 chars)
20002 AuditPrevHash Link to previous event
20003 AuditTraceID UUID v7 linking SIG→ORD→EXE
20004 ClockSyncStatus 0=Unknown, 1=NTP, 2=PTP, 3=GPS
20005 AuditMerkleRoot Batch integrity proof

Tags 20001-20999 are user-defined per FIX spec. Perfect for PoC without formal approval.


Why Not Just Use Blockchain?

I hear you. "Just put it on Ethereum!"

Problems:

  1. Latency: 12+ seconds per block vs. nanosecond trading
  2. Cost: Gas fees add up fast at 10K+ events/day
  3. Privacy: You don't want order flow on a public chain
  4. Complexity: Your ops team will love you (/s)

The sidecar approach:

  • Local hash chains (instant)
  • Periodic Merkle root anchoring (optional, batched)
  • Private by default
  • Runs alongside existing infra

What About Regulations?

Regulation Requirement How This Helps
MiFID II RTS 25 Clock sync ≀100ΞΌs clock_sync field proves sync status
MiFID II RTS 6 Algo audit trails Complete SIG→ORD→EXE chain
EU AI Act Art. 12 Automatic logging Cryptographic completeness proof
SEC 17a-4 Tamper-evident records Hash chain + signatures

The EU AI Act deadline for high-risk AI systems is August 2027. Algo trading systems? Likely in scope.


Getting Started

1. Minimal Python Implementation

import hashlib
import json
from datetime import datetime, timezone

def create_event(event_type: str, payload: dict, prev_hash: str) -> dict:
    header = {
        "event_id": generate_uuid_v7(),
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "event_type": event_type,
        "clock_sync": "NTP_SYNCED"
    }

    # Canonical JSON for consistent hashing
    canonical = json.dumps({"header": header, "payload": payload}, 
                          sort_keys=True, separators=(',', ':'))
    event_hash = hashlib.sha256(canonical.encode()).hexdigest()

    return {
        "header": header,
        "payload": payload,
        "security": {
            "event_hash": event_hash,
            "prev_hash": prev_hash
        }
    }

# Chain events
prev = "0" * 64
sig_event = create_event("SIG", {"algo": "momentum-v2", "confidence": 0.87}, prev)
prev = sig_event["security"]["event_hash"]

ord_event = create_event("ORD", {"symbol": "XAUUSD", "side": "BUY", "qty": 100}, prev)
prev = ord_event["security"]["event_hash"]

exe_event = create_event("EXE", {"fill_price": 2650.45, "fill_qty": 100}, prev)
Enter fullscreen mode Exit fullscreen mode

2. Hook Into Your FIX Engine

Most FIX engines have callback hooks. QuickFIX example:

void Application::onMessage(const FIX44::ExecutionReport& msg, 
                            const FIX::SessionID& session) {
    // Normal processing
    processExecution(msg);

    // Async emit to VCP sidecar (non-blocking)
    vcpSidecar.emitAsync("EXE", extractPayload(msg));
}
Enter fullscreen mode Exit fullscreen mode

Zero impact on your critical path.


The Full Spec

Everything here is based on an open specification:

No vendor lock-in. No proprietary formats. Just cryptographic proof.


TL;DR

What How
Problem Trading logs can be modified, deleted, faked
Solution Hash chain + signatures in sidecar process
Impact on FIX Zero. Messages flow unchanged.
Impact on latency Zero. Async event tap.
Verification Anyone can verify without trusting submitter

The next time an auditor asks "can you prove it?", your answer changes from "trust us" to "here's the cryptographic proof."


Questions?

For reference, the materials are available here:
https://github.com/veritaschain/vcp-docs/tree/main/standards

If you're building algo trading systems and thinking about audit trails, I'd love to hear what challenges you're facing. Drop a comment below. πŸ‘‡

Top comments (0)