Adding Cryptographic Audit Trails to FIX Without Touching Your Trading Engine
Source: Dev.to
Why Traditional Logging Falls Short
┌─────────────────────────────────────────┐
│ Traditional Logging │
├─────────────────────────────────────────┤
│ ✗ Admin can modify logs │
│ ✗ No proof of completeness │
│ ✗ Timestamp ≠ proof of time │
│ ✗ Auditor must trust submitter │
│ ✗ Deletion is undetectable │
└─────────────────────────────────────────┘
MiFID II RTS 25 requires clock sync to 100 µs. Even with perfect timestamps there is no cryptographic proof that:
- the log wasn’t modified,
- no entries were deleted,
- 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.
What the sidecar does
| Step | Action | Purpose |
|---|---|---|
| Receive | Event copies asynchronously (no blocking) | Non‑intrusive |
| Hash | SHA‑256 per event | Tamper‑evidence |
| Chain | Link to previous hash | Detect deletions / re‑ordering |
| Sign | Ed25519 signature | Non‑repudiation |
| Anchor | Batch Merkle roots (periodic) | Efficient external verification |
Show Me The Code
FIX ExecutionReport (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|
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..."
}
}
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)
- Delete Event 2 → Chain breaks at Event 3.
- Modify Event 1 → Hash changes, Event 2’s
prev_hashmismatches. - Insert fake event → Signature verification fails.
Mapping to FIX Tags (optional embedding)
| 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 the FIX spec, perfect for a PoC without formal approval.
Why Not Just Use a Public Blockchain?
- Latency: 12 + seconds per block vs. nanosecond trading.
- Cost: Gas fees explode at 10 K+ events/day.
- Privacy: Order flow on a public ledger is undesirable.
- Complexity: Your ops team will love you (/s).
Sidecar advantages
- Local hash chains → instant verification.
- Periodic Merkle‑root anchoring (optional, batched).
- Private by default.
- Runs alongside existing infrastructure with zero code changes to the FIX engine.
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 | Full, tamper‑evident event chain |
| SEC Rule 17a‑4 | Retention & integrity | Cryptographic hashes & signatures satisfy “integrity” |
| EMIR | Transaction reporting | Event IDs & trace IDs enable end‑to‑end traceability |
SIG → ORD → EXE chain
Regulatory Context
- EU AI Act – Art. 12 – Automatic logging, cryptographic completeness proof
- SEC Rule 17a‑4 – Tamper‑evident records, hash chain + signatures
The EU AI Act deadline for high‑risk AI systems is August 2027.
Algo‑trading systems are likely in scope.
Getting Started
1. Minimal Python implementation
import hashlib
import json
from datetime import datetime, timezone
import uuid
def generate_uuid_v7() -> str:
"""Placeholder for a UUID‑v7 generator."""
return str(uuid.uuid4())
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
)
2. Hook into your FIX engine
Most FIX engines expose callback hooks. Below is a QuickFIX example:
void Application::onMessage(const FIX44::ExecutionReport& msg,
const FIX::SessionID& session) {
// Normal processing
processExecution(msg);
// Async emit to VCP side‑car (non‑blocking)
vcpSidecar.emitAsync("EXE", extractPayload(msg));
}
Zero impact on your critical path.
The Full Specification
| Item | Link |
|---|---|
| Spec | VCP v1.0 |
| IETF Draft | draft‑kamimura‑scitt‑vcp |
| GitHub | github.com/veritaschain |
| License | CC BY 4.0 (spec), Apache 2.0 (code) |
No vendor lock‑in, no proprietary formats—just cryptographic proof.
TL;DR
| What | How |
|---|---|
| Problem | Trading logs can be modified, deleted, or faked |
| Solution | Hash chain + signatures in a side‑car process |
| Impact on FIX | Zero – messages flow unchanged |
| Impact on latency | Zero – async event tap |
| Verification | Anyone can verify without trusting the submitter |
The next time an auditor asks “Can you prove it?” you can answer with a cryptographic proof instead of “trust us”.
Questions?
- Technical:
- Reference material:
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. 👇