DEV Community

Cover image for Building Tamper-Proof Audit Trails: A Deep Dive into Cryptographic Evidence for Trading Disputes

Building Tamper-Proof Audit Trails: A Deep Dive into Cryptographic Evidence for Trading Disputes

When CSV logs can be edited and screenshots can be faked, how do you prove what really happened?


The $50,000 Email Thread

Last year, a trader on Reddit shared a nightmare scenario. After three months of profitable trading with a prop firm, his $47,000 payout was denied. The reason? "Our records show you violated the maximum drawdown limit on October 15th."

The trader had screenshots showing no such violation. The prop firm had CSV exports showing otherwise. After 200+ emails, countless screenshots, and three months of disputes, the trader gave up.

His word against theirs. No technical proof. No recourse.

This isn't an isolated incident. In 2024-2025, over 80 prop trading firms collapsed or faced serious fraud allegations. Thousands of traders lost payouts they believed they earned. The fundamental problem? Trust-based record keeping in an adversarial environment.

Today, I'm open-sourcing a proof-of-concept that solves this problem using cryptographic audit trails. No blockchain required. No third-party services. Just math.

GitHub Repository: veritaschain/vcp-payout-dispute-poc


Why Traditional Logs Fail

Before we dive into the solution, let's understand why traditional logging fails in dispute scenarios:

┌─────────────────────────────────────────────────────────────────┐
│                    TRADITIONAL LOGGING                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Trader                              Prop Firm                  │
│  ──────                              ─────────                  │
│  MT4/MT5 Statement                   Server Database            │
│  (can be edited)                     (can be edited)            │
│                                                                 │
│  Screenshots                         CSV Exports                │
│  (can be photoshopped)               (can be regenerated)       │
│                                                                 │
│  Broker Confirmation                 "Our records show..."      │
│  (not your broker)                   (unverifiable)             │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│  DISPUTE OUTCOME: Whoever controls the database wins.           │
└─────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The core issue is unilateral control. Whichever party controls the authoritative record can modify it. There's no cryptographic binding between what happened and what's recorded.


The Solution: Three-Layer Cryptographic Architecture

The VeritasChain Protocol v1.1 introduces a Three-Layer Architecture for tamper-evident audit trails:

┌─────────────────────────────────────────────────────────────────────┐
│  LAYER 3: External Verifiability                                    │
│  ─────────────────────────────────                                  │
│  • Digital Signatures (Ed25519) - Proves WHO created the record    │
│  • External Anchoring - Proves WHEN the record existed             │
│  • Third-party verification without trusting the producer          │
├─────────────────────────────────────────────────────────────────────┤
│  LAYER 2: Collection Integrity                                      │
│  ────────────────────────────────                                   │
│  • Merkle Tree (RFC 6962) - Proves COMPLETENESS of event set       │
│  • Audit paths for each event                                       │
│  • Detects additions, deletions, and reordering                    │
├─────────────────────────────────────────────────────────────────────┤
│  LAYER 1: Event Integrity                                           │
│  ───────────────────────────                                        │
│  • SHA-256 Event Hash - Proves WHAT was recorded                   │
│  • Optional hash chain for real-time detection                      │
│  • Single-bit modification detection                                │
└─────────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Let's implement each layer.


Layer 1: Event Integrity

The foundation is simple: hash everything.

import hashlib
import json

def canonicalize_json(obj):
    """RFC 8785 JSON Canonicalization Scheme (simplified)"""
    return json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)

def calculate_event_hash(header, payload, vcp_xref, policy_id, prev_hash=None):
    """
    Calculate event hash per VCP v1.1 Section 6.1.1

    The hash covers ALL meaningful content:
    - header: event metadata (timestamp, type, IDs)
    - payload: the actual trade data
    - vcp_xref: cross-reference information
    - policy_id: which policy governs this event
    - prev_hash: optional link to previous event
    """
    components = [
        canonicalize_json(header),
        canonicalize_json(payload),
        canonicalize_json(vcp_xref),
        canonicalize_json(policy_id)
    ]

    if prev_hash and prev_hash != "0" * 64:
        components.append(prev_hash)

    hash_input = ''.join(components).encode('utf-8')
    return hashlib.sha256(hash_input).hexdigest()
Enter fullscreen mode Exit fullscreen mode

Why this matters: If the prop firm changes the execution price from 2658.20 to 2655.50, the hash changes completely:

Original:  16351b5fc03b0ef191f1577a1eeecc14b5db9fc5dbd0e19d7910d2414bb3aa81
Tampered:  a7c9e8d4f2b1a0e3c5d7f9b2a4c6e8d0f2b4a6c8e0d2f4a6b8c0e2d4f6a8b0c2
Enter fullscreen mode Exit fullscreen mode

There's no way to modify the content and keep the same hash. SHA-256 provides 128-bit security against collision attacks.

Optional: Hash Chaining

For real-time tamper detection, each event can reference the previous event's hash:

events = []
prev_hash = "0" * 64  # Genesis

for trade in trades:
    event_hash = calculate_event_hash(
        header=trade['header'],
        payload=trade['payload'],
        vcp_xref=trade['vcp_xref'],
        policy_id=trade['policy'],
        prev_hash=prev_hash
    )

    trade['security']['event_hash'] = event_hash
    trade['security']['prev_hash'] = prev_hash

    events.append(trade)
    prev_hash = event_hash  # Chain continues
Enter fullscreen mode Exit fullscreen mode

If someone tries to insert, delete, or reorder events, the chain breaks.


Layer 2: Collection Integrity with Merkle Trees

Individual event hashes prove each event's integrity. But how do we prove that no events were added or removed?

Enter the Merkle Tree (RFC 6962).

class MerkleTree:
    """RFC 6962 compliant Merkle Tree with domain separation"""

    @staticmethod
    def leaf_hash(data: bytes) -> bytes:
        """Leaf nodes: SHA256(0x00 || data)"""
        return hashlib.sha256(b'\x00' + data).digest()

    @staticmethod
    def node_hash(left: bytes, right: bytes) -> bytes:
        """Internal nodes: SHA256(0x01 || left || right)"""
        return hashlib.sha256(b'\x01' + left + right).digest()

    def __init__(self, leaves: list):
        self.leaves = leaves
        self.levels = []
        self._build_tree()

    def _build_tree(self):
        # Compute leaf hashes with domain separation
        leaf_hashes = [self.leaf_hash(leaf) for leaf in self.leaves]

        # Pad to power of 2 for consistent proof lengths
        n = len(leaf_hashes)
        next_pow2 = 1
        while next_pow2 < n:
            next_pow2 *= 2
        while len(leaf_hashes) < next_pow2:
            leaf_hashes.append(leaf_hashes[-1])

        current_level = leaf_hashes
        self.levels.append(current_level)

        # Build tree bottom-up
        while len(current_level) > 1:
            next_level = []
            for i in range(0, len(current_level), 2):
                left = current_level[i]
                right = current_level[i + 1]
                next_level.append(self.node_hash(left, right))
            self.levels.append(next_level)
            current_level = next_level

        self.root = current_level[0]
Enter fullscreen mode Exit fullscreen mode

The Merkle Root is a single 32-byte hash that represents the entire collection of events. Change any event, add an event, remove an event—the root changes.

Merkle Proofs: Proving Inclusion

The magic of Merkle trees is that you can prove any single event is part of the collection without revealing other events:

def get_proof(self, index: int) -> list:
    """Generate inclusion proof for leaf at index"""
    proof = []
    idx = index

    for level in self.levels[:-1]:
        sibling_idx = idx ^ 1  # XOR to get sibling
        sibling_hash = level[sibling_idx].hex()
        position = "left" if sibling_idx < idx else "right"

        proof.append({"hash": sibling_hash, "position": position})
        idx //= 2

    return proof

def verify_proof(event_hash, merkle_root, proof):
    """Verify that an event is included in the tree"""
    current = MerkleTree.leaf_hash(bytes.fromhex(event_hash))

    for step in proof:
        sibling = bytes.fromhex(step['hash'])
        if step['position'] == 'left':
            current = MerkleTree.node_hash(sibling, current)
        else:
            current = MerkleTree.node_hash(current, sibling)

    return current.hex() == merkle_root
Enter fullscreen mode Exit fullscreen mode

For 23 events, the proof is just 5 hashes (log₂ of 32, the next power of 2). Verification is O(log n).

Why domain separation? The 0x00 prefix for leaves and 0x01 prefix for internal nodes prevents second preimage attacks where an attacker could construct a fake proof.


Layer 3: External Verifiability

So far, we have hashes and Merkle trees. But who says these weren't generated yesterday and backdated? We need:

  1. Digital Signatures - Prove authorship
  2. External Anchoring - Prove existence at a specific time

Digital Signatures

def sign_event(event_hash: str, private_key: bytes) -> str:
    """Sign the event hash with Ed25519"""
    # In production, use a real crypto library
    signing_key = Ed25519SigningKey(private_key)
    signature = signing_key.sign(bytes.fromhex(event_hash))
    return base64.b64encode(signature).decode('ascii')
Enter fullscreen mode Exit fullscreen mode

The signature proves that the holder of the private key created this specific hash. Without the key, you cannot forge a signature.

External Anchoring

The Merkle root needs to be anchored to an external, immutable timeline. Options include:

Method Description Cost Trust
OpenTimestamps Bitcoin-backed timestamps Free Decentralized
RFC 3161 TSA Traditional timestamp authorities Varies Centralized
Public Blockchain Direct on-chain anchoring Gas fees Decentralized

For the PoC, we simulate OpenTimestamps:

def create_anchor_record(merkle_root, events, signer):
    """Create an external anchor record"""
    return {
        "anchor_id": f"anchor-{hash(merkle_root)[:16]}",
        "merkle_root": merkle_root,
        "signature": signer.sign(merkle_root),
        "timestamp": time.time_ns(),
        "anchor_target": {
            "type": "PUBLIC_SERVICE",
            "identifier": "opentimestamps.org",
            "proof": f"ots-{hash(merkle_root + str(time.time()))[:32]}"
        },
        "event_count": len(events),
        "first_event_id": events[0]['header']['event_id'],
        "last_event_id": events[-1]['header']['event_id']
    }
Enter fullscreen mode Exit fullscreen mode

Once anchored, even if your local copy is destroyed, the anchor proves that specific Merkle root existed at that time.


The Secret Weapon: VCP-XREF Dual Logging

Here's where it gets interesting. What if both parties maintain their own VCP logs?

┌─────────────────────┐          ┌─────────────────────┐
│   TRADER'S VCP LOG  │          │  PROP FIRM VCP LOG  │
│   (Your Evidence)   │          │  (Their Evidence)   │
└─────────┬───────────┘          └─────────┬───────────┘
          │                                │
          │  Shared: CrossReferenceID      │
          │  Shared: OrderID               │
          │  Shared: Timestamp ±100ms      │
          │                                │
          ▼                                ▼
┌─────────────────────────────────────────────────────┐
│              CROSS-REFERENCE VERIFICATION           │
│                                                     │
│  For each (CrossReferenceID, EventType) pair:      │
│    1. Match trader event with prop firm event      │
│    2. Compare: execution_price, quantity, side     │
│    3. Flag any discrepancies                       │
│                                                     │
│  Result: MATCHED | DISCREPANCY (with proof)        │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The VCP-XREF extension adds cross-reference fields to each event:

{
  "vcp_xref": {
    "version": "1.1",
    "cross_reference_id": "550e8400-e29b-41d4-a716-446655440001",
    "party_role": "INITIATOR",
    "counterparty_id": "propfirm-alpha.com",
    "shared_event_key": {
      "order_id": "ORD-2025-001234",
      "timestamp": 1735689600000000000,
      "tolerance_ms": 100
    },
    "reconciliation_status": "PENDING"
  }
}
Enter fullscreen mode Exit fullscreen mode

The guarantee: Unless both parties collude, any discrepancy is detectable. If the prop firm says execution was at $2655.50 but the trader's log says $2658.20, the cross-reference verification will flag it—with cryptographic proof from both sides.


Running the PoC

Enough theory. Let's see it work.

Clone and Generate Events

git clone https://github.com/veritaschain/vcp-payout-dispute-poc.git
cd vcp-payout-dispute-poc

# Generate 46 VCP v1.1 compliant events (23 per party)
python scripts/generate_events_v1_1.py
Enter fullscreen mode Exit fullscreen mode

Output:

VCP v1.1 Compliant Event Generator
============================================================

Generated 23 trader events
Generated 23 propfirm events

Layer 1 - Event Integrity:
  ✓ EventHash (SHA-256): All 46 events
  ✓ PrevHash (hash chain): Enabled

Layer 2 - Collection Integrity:
  ✓ Merkle Tree (RFC 6962): Built
  ✓ Merkle Root (Trader):   1b8d2878dcb13c6de88a9225f64d7a47...
  ✓ Merkle Root (PropFirm): e29fe887dd16ff1c5515c92f6a3d1872...

Layer 3 - External Verifiability:
  ✓ Digital Signatures (Ed25519): All events signed
  ✓ External Anchor: OpenTimestamps (simulated)
Enter fullscreen mode Exit fullscreen mode

Verify Both Logs Match

python verifier/verify.py \
    --trader evidence/trader_events.jsonl \
    --propfirm evidence/propfirm_events.jsonl \
    --anchor evidence/anchor_records.json
Enter fullscreen mode Exit fullscreen mode

Output:

======================================================================
        VCP v1.1 PAYOUT DISPUTE VERIFICATION
======================================================================

Layer 1: Event Integrity
  (EventHash, PrevHash)

  ✓ Trader event hashes:   23/23 valid
  ✓ PropFirm event hashes: 23/23 valid
  ✓ Trader hash chain:     VALID
  ✓ PropFirm hash chain:   VALID

Layer 2: Collection Integrity
  (Merkle Tree, RFC 6962)

  ✓ Trader Merkle tree:    VALID
  ✓ PropFirm Merkle tree:  VALID

Layer 3: External Verifiability
  (Digital Signatures, External Anchor)

  ✓ Trader anchor:         VALID
  ✓ PropFirm anchor:       VALID

VCP-XREF: Cross-Reference Verification
  Trader events:    23
  PropFirm events:  23
  Matched pairs:    23

✓ NO DISCREPANCIES DETECTED

This verification is MATHEMATICALLY PROVABLE per VCP v1.1.
Enter fullscreen mode Exit fullscreen mode

Demonstrate Tamper Detection

Now let's see what happens when the prop firm tries to cheat:

# Simulate prop firm editing an execution price
python scripts/tamper_demo.py
Enter fullscreen mode Exit fullscreen mode

This script modifies one execution price from 2658.20 to 2655.50 in the prop firm's log.

# Verify and detect the tampering
python verifier/verify.py \
    --trader evidence/trader_events.jsonl \
    --propfirm evidence/propfirm_tampered.jsonl
Enter fullscreen mode Exit fullscreen mode

Output:

Layer 1: Event Integrity
  ✓ Trader event hashes:   23/23 valid
  ✗ PropFirm event hashes: 22/23 valid   ← HASH MISMATCH DETECTED

✗ DISCREPANCIES DETECTED

  CRITICAL: 1
    • [EXE] ORD-2025-001002: execution_price
      ├─ Trader:   2658.20
      └─ PropFirm: 2655.50

These issues are CRYPTOGRAPHICALLY PROVABLE per VCP v1.1.
Enter fullscreen mode Exit fullscreen mode

The tampering is detected at multiple layers:

  1. Layer 1: The event hash no longer matches the content
  2. VCP-XREF: The cross-reference shows different values

The trader now has mathematical proof that the prop firm's record was modified.


The Event Structure

Here's what a complete VCP v1.1 event looks like:

{
  "header": {
    "event_id": "019abc01-0004-7c82-9d1b-111111110004",
    "trace_id": "019abc01-0000-7000-8000-aaaaaaaaaaaa",
    "timestamp_int": "1735689600180000000",
    "timestamp_iso": "2025-01-01T08:00:00.180Z",
    "event_type": "EXE",
    "event_type_code": 4,
    "symbol": "XAUUSD",
    "account_id": "trader_john_doe_001"
  },
  "payload": {
    "trade_data": {
      "order_id": "ORD-2025-001001",
      "exec_id": "EXEC-001001",
      "execution_price": "2651.50",
      "executed_qty": "1.00",
      "commission": "7.50"
    }
  },
  "vcp_xref": {
    "version": "1.1",
    "cross_reference_id": "550e8400-e29b-41d4-a716-446655440001",
    "party_role": "INITIATOR",
    "counterparty_id": "propfirm-alpha.com",
    "shared_event_key": {
      "order_id": "ORD-2025-001001",
      "timestamp": 1735689600000000000,
      "tolerance_ms": 100
    }
  },
  "policy_identification": {
    "version": "1.1",
    "policy_id": "org.veritaschain.poc:trader-001",
    "conformance_tier": "SILVER"
  },
  "security": {
    "version": "1.1",
    "event_hash": "16351b5fc03b0ef191f1577a1eeecc14...",
    "prev_hash": "0000000000000000000000000000000000000000...",
    "signature": "W2A54vt+ftLFgA5bMN4TlLTSHC7xxQBh...",
    "merkle_root": "1b8d2878dcb13c6de88a9225f64d7a47...",
    "merkle_index": 3,
    "merkle_proof": [
      {"hash": "abc123...", "position": "right"},
      {"hash": "def456...", "position": "right"}
    ],
    "anchor_reference": "anchor-12ddced20c534e02"
  }
}
Enter fullscreen mode Exit fullscreen mode

Why Not Just Use Blockchain?

You might ask: "Why not just write everything to Ethereum?"

Cost: Writing 23 events to Ethereum would cost significant gas fees. For high-frequency trading with thousands of events per day, this is prohibitive.

Latency: Blockchain confirmation times (seconds to minutes) don't work for trading where milliseconds matter.

Privacy: Trading strategies are proprietary. Broadcasting every trade to a public blockchain is unacceptable.

The VCP approach is different:

  1. Local logging is free and instant
  2. Only the Merkle root gets anchored (32 bytes, once per batch)
  3. Privacy preserved: Individual trades are never exposed
  4. Same guarantees: Tampering is still mathematically detectable

Implementing This in Your System

The PoC uses Python's standard library only—no dependencies. But for production:

For MT4/MT5 (MQL5)

// Simplified VCP event creation
void LogVCPEvent(string eventType, string orderID, double price) {
    string header = StringFormat(
        "{\"event_id\":\"%s\",\"event_type\":\"%s\",\"timestamp_int\":\"%I64d\"}",
        GenerateUUIDv7(), eventType, GetTickCount64() * 1000000
    );

    string payload = StringFormat(
        "{\"trade_data\":{\"order_id\":\"%s\",\"price\":\"%.5f\"}}",
        orderID, price
    );

    // Send to VCP sidecar via named pipe or file
    SendToSidecar(header, payload);
}
Enter fullscreen mode Exit fullscreen mode

For Python Trading Systems

from vcp_sidecar import VCPLogger

logger = VCPLogger(
    party_id="trader-001",
    counterparty_id="broker.com",
    conformance_tier="SILVER"
)

@logger.track_execution
def execute_order(order):
    result = broker.submit(order)
    return result  # Automatically logged with VCP-XREF
Enter fullscreen mode Exit fullscreen mode

For FIX Protocol

The VCP "sidecar" pattern means you don't modify your existing FIX engine. A separate process captures FIX messages and generates VCP events:

┌────────────┐     ┌────────────┐     ┌────────────┐
│ FIX Engine │────▶│ VCP Sidecar│────▶│ VCP Log    │
└────────────┘     └────────────┘     └────────────┘
                         │
                         ▼
                  ┌────────────┐
                  │ External   │
                  │ Anchor     │
                  └────────────┘
Enter fullscreen mode Exit fullscreen mode

The Bigger Picture

This PoC focuses on trading disputes, but the Three-Layer Architecture applies to any system where:

  • Multiple parties interact
  • Records can be disputed
  • Trust is limited

Applications include:

  • AI Decision Logging: Prove what an AI system decided and why
  • Supply Chain: Prove provenance of goods
  • Healthcare: Prove treatment records weren't altered
  • Legal: Prove document existence at specific times

The VeritasChain Protocol is being standardized through the IETF SCITT Working Group. The draft specification is available at datatracker.ietf.org/doc/draft-kamimura-scitt-vcp.


Try It Yourself

The complete PoC is available on GitHub:

🔗 Repository: github.com/veritaschain/vcp-payout-dispute-poc

git clone https://github.com/veritaschain/vcp-payout-dispute-poc.git
cd vcp-payout-dispute-poc

# Generate events
python scripts/generate_events_v1_1.py

# Verify (should pass)
python verifier/verify.py -t evidence/trader_events.jsonl -p evidence/propfirm_events.jsonl

# Demonstrate tampering
python scripts/tamper_demo.py

# Verify tampered log (should fail)
python verifier/verify.py -t evidence/trader_events.jsonl -p evidence/propfirm_tampered.jsonl
Enter fullscreen mode Exit fullscreen mode

Requirements: Python 3.9+ (standard library only, no pip install needed)


Conclusion

Trust-based record keeping has failed. When disputes happen, CSV logs are not evidence—they're just data that can be edited.

The VCP v1.1 Three-Layer Architecture provides:

Layer Guarantee
Layer 1 Any modification to content is detected
Layer 2 Any addition/removal of events is detected
Layer 3 Third parties can verify without trusting the producer
VCP-XREF Discrepancies between parties are cryptographically provable

The math doesn't lie. The math doesn't forget. The math doesn't care who has more lawyers.

When payout disputes happen, CSV logs are not evidence. Cryptographic logs are.


The VeritasChain Protocol is developed by the VeritasChain Standards Organization (VSO), a vendor-neutral standards body. For more information: veritaschain.org

Questions? Reach out: technical@veritaschain.org

Top comments (0)