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. │
└─────────────────────────────────────────────────────────────────┘
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 │
└─────────────────────────────────────────────────────────────────────┘
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()
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
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
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]
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
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:
- Digital Signatures - Prove authorship
- 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')
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']
}
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) │
└─────────────────────────────────────────────────────┘
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"
}
}
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
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)
Verify Both Logs Match
python verifier/verify.py \
--trader evidence/trader_events.jsonl \
--propfirm evidence/propfirm_events.jsonl \
--anchor evidence/anchor_records.json
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.
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
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
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.
The tampering is detected at multiple layers:
- Layer 1: The event hash no longer matches the content
- 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"
}
}
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:
- Local logging is free and instant
- Only the Merkle root gets anchored (32 bytes, once per batch)
- Privacy preserved: Individual trades are never exposed
- 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);
}
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
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 │
└────────────┘
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
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)