Skip to main content

Command Palette

Search for a command to run...

Verify ProofLedger Blockchain Timestamps in Python

Updated
5 min read
C
Founder, Fulcrum Enterprises. Building the foundation for an AI driven future.

Your claims system receives a timestamped photo. The adjuster says it was taken before the storm. The metadata says yesterday. Who's right? When building software for insurance or legal contexts, you need cryptographic proof, not file properties. Here's how to programmatically verify a ProofLedger blockchain timestamp from scratch.

Getting Proof Data from the API

ProofLedger anchors SHA-256 hashes to both Polygon and Bitcoin. Each proof gets a unique ID and a public verification URL. The API returns the anchored hash, block numbers, transaction hashes, and Merkle proofs for both chains.

import requests
import hashlib
import json
from typing import Dict, Any, Tuple

def get_proof_data(proof_id: str) -> Dict[str, Any]:
    """Fetch proof data from ProofLedger API"""
    url = f"https://api.proofledger.io/v1/proofs/{proof_id}"
    
    response = requests.get(url)
    response.raise_for_status()
    
    return response.json()

# Example proof data structure
example_proof = {
    "id": "pl_ABC123XYZ789",
    "anchored_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "polygon": {
        "tx_hash": "0x1234567890abcdef...",
        "block_number": 45123456,
        "merkle_proof": ["0xabc123...", "0xdef456..."],
        "merkle_root": "0x789xyz..."
    },
    "bitcoin": {
        "tx_hash": "a1b2c3d4e5f6...",
        "block_height": 825000,
        "merkle_proof": ["abc123...", "def456..."],
        "merkle_root": "789xyz..."
    },
    "timestamp": "2024-03-15T14:30:00Z"
}

The anchored_hash is what matters. This SHA-256 hash was computed from the original file and written to both blockchains. To verify the timestamp, you need to prove three things: the file produces that hash, the hash exists in the claimed blocks, and the Merkle proofs are valid.

File Hashing and Hash Verification

First, recompute the SHA-256 of your file. This must match the anchored_hash exactly. Any difference means the file was modified after timestamping.

def compute_file_hash(file_path: str) -> str:
    """Compute SHA-256 hash of file in chunks to handle large files"""
    sha256_hash = hashlib.sha256()
    
    with open(file_path, "rb") as f:
        # Read in 64kb chunks to avoid memory issues with large files
        for chunk in iter(lambda: f.read(65536), b""):
            sha256_hash.update(chunk)
    
    return sha256_hash.hexdigest()

def verify_file_integrity(file_path: str, expected_hash: str) -> bool:
    """Verify file hasn't been modified since timestamping"""
    computed_hash = compute_file_hash(file_path)
    return computed_hash.lower() == expected_hash.lower()

# Usage
if verify_file_integrity("evidence.jpg", proof_data["anchored_hash"]):
    print("File integrity verified - hash matches anchored value")
else:
    print("File modified - hash doesn't match blockchain anchor")
    exit(1)

This step catches the most common issue: someone modified the file after timestamping. In legal contexts, this breaks chain of custody. The hash comparison is binary - either the file is exactly what was anchored, or it isn't.

Merkle Proof Validation

The Merkle proof shows your hash exists in the claimed blockchain block without downloading the entire block. Each proof is an array of sibling hashes. You reconstruct the Merkle root by combining your hash with each sibling, then verify that root matches what's in the block header.

def verify_merkle_proof(target_hash: str, merkle_proof: list, merkle_root: str) -> bool:
    """Verify target hash exists in Merkle tree with given proof"""
    current_hash = target_hash.lower()
    
    for proof_hash in merkle_proof:
        proof_hash = proof_hash.lower()
        
        # Combine hashes in lexicographic order
        if current_hash < proof_hash:
            combined = current_hash + proof_hash
        else:
            combined = proof_hash + current_hash
        
        # Hash the combination
        current_hash = hashlib.sha256(combined.encode()).hexdigest()
    
    return current_hash == merkle_root.lower()

def verify_blockchain_anchors(proof_data: Dict[str, Any], anchored_hash: str) -> Tuple[bool, bool]:
    """Verify hash exists in both Polygon and Bitcoin blocks"""
    polygon_valid = verify_merkle_proof(
        anchored_hash,
        proof_data["polygon"]["merkle_proof"],
        proof_data["polygon"]["merkle_root"]
    )
    
    bitcoin_valid = verify_merkle_proof(
        anchored_hash,
        proof_data["bitcoin"]["merkle_proof"],
        proof_data["bitcoin"]["merkle_root"]
    )
    
    return polygon_valid, bitcoin_valid

# Verify both chains
polygon_ok, bitcoin_ok = verify_blockchain_anchors(proof_data, proof_data["anchored_hash"])

if polygon_ok and bitcoin_ok:
    print("Dual-chain verification successful")
elif polygon_ok:
    print("Polygon verification successful, Bitcoin pending/failed")
elif bitcoin_ok:
    print("Bitcoin verification successful, Polygon failed")
else:
    print("Both chain verifications failed")

ProofLedger uses dual-chain anchoring because legal contexts value redundancy. Polygon provides instant finality. Bitcoin provides maximum immutability. Both chains must validate for full verification, but Polygon-only is acceptable for time-sensitive decisions.

Complete Verification Pipeline

Here's the full verification function that ties everything together. This is what you'd integrate into a claims system, document management platform, or forensic toolkit.

import datetime
from enum import Enum
from dataclasses import dataclass

class VerificationStatus(Enum):
    VERIFIED = "verified"
    FILE_MODIFIED = "file_modified"
    BLOCKCHAIN_FAILED = "blockchain_failed"
    API_ERROR = "api_error"

@dataclass
class VerificationResult:
    status: VerificationStatus
    timestamp: str
    polygon_verified: bool
    bitcoin_verified: bool
    message: str

def verify_proofledger_timestamp(file_path: str, proof_id: str) -> VerificationResult:
    """Complete ProofLedger timestamp verification"""
    try:
        # Get proof data from API
        proof_data = get_proof_data(proof_id)
        anchored_hash = proof_data["anchored_hash"]
        timestamp = proof_data["timestamp"]
        
        # Verify file integrity
        if not verify_file_integrity(file_path, anchored_hash):
            return VerificationResult(
                status=VerificationStatus.FILE_MODIFIED,
                timestamp=timestamp,
                polygon_verified=False,
                bitcoin_verified=False,
                message="File hash doesn't match anchored value - file was modified"
            )
        
        # Verify blockchain anchors
        polygon_ok, bitcoin_ok = verify_blockchain_anchors(proof_data, anchored_hash)
        
        if polygon_ok and bitcoin_ok:
            return VerificationResult(
                status=VerificationStatus.VERIFIED,
                timestamp=timestamp,
                polygon_verified=True,
                bitcoin_verified=True,
                message=f"Timestamp verified on both chains: {timestamp}"
            )
        elif polygon_ok or bitcoin_ok:
            return VerificationResult(
                status=VerificationStatus.VERIFIED,
                timestamp=timestamp,
                polygon_verified=polygon_ok,
                bitcoin_verified=bitcoin_ok,
                message=f"Partial verification - {'Polygon' if polygon_ok else 'Bitcoin'} confirmed: {timestamp}"
            )
        else:
            return VerificationResult(
                status=VerificationStatus.BLOCKCHAIN_FAILED,
                timestamp=timestamp,
                polygon_verified=False,
                bitcoin_verified=False,
                message="Merkle proof validation failed on both chains"
            )
            
    except requests.RequestException as e:
        return VerificationResult(
            status=VerificationStatus.API_ERROR,
            timestamp="",
            polygon_verified=False,
            bitcoin_verified=False,
            message=f"API error: {str(e)}"
        )

# Usage in a claims system
result = verify_proofledger_timestamp("claim_photos/damage_001.jpg", "pl_ABC123XYZ789")

if result.status == VerificationStatus.VERIFIED:
    print(f"Evidence timestamp verified: {result.timestamp}")
    print(f"Admissible under FRE 901(b)(9): Yes")
else:
    print(f"Verification failed: {result.message}")
    print(f"Admissible under FRE 901(b)(9): No")

Integration with Legal/Insurance Systems

Courts accept blockchain timestamps under Federal Rule of Evidence 901(b)(9) when you can prove the process that generates the evidence. The verification code above gives you that process proof. You're not asking a judge to trust metadata - you're showing cryptographic validation.

For claims systems, wrap this verification in your intake pipeline. Before accepting evidence, run the timestamp check. Log the verification result with the claim file. When disputes arise, you have machine-readable proof the evidence existed at a specific time.

The verification is deterministic and offline-capable. Once you download the proof data, you can verify it without API calls. That's crucial for legal contexts where you need to demonstrate the verification process in court.

Install the dependencies and start verifying:

pip install requests

Clone the complete example with error handling and edge cases: github.com/fulcrum-enterprises/proofledger-verify-python

The blockchain doesn't lie. The Merkle proof doesn't change. When evidence documentation becomes disputed evidence in court, cryptographic verification beats metadata fields every time.