Ed25519 + Merkle Tree + UUIDv7 = Building Tamper-Proof Decision Logs
Source: Dev.to
TL;DR – Combine Ed25519 signatures, Merkle trees, and UUIDv7 identifiers to build an immutable, tamper‑evident audit trail for AI and algorithmic systems. A full Python implementation is provided below.
The Problem: AI Decisions at the Speed of Light
Your trading algorithm made 10 000 decisions in the last second.
Your ML model approved 500 loan applications.
Your autonomous system executed a critical maneuver.
Now someone asks: “What exactly happened at 14:32:07.847?”
You need to prove:
- What decision was made.
- When it was made (with cryptographic certainty).
- That nothing was altered after the fact.
Traditional logging fails: database records can be edited, timestamps forged, and log files tampered. When AI systems operate faster than human comprehension, we need cryptographic proof.
The Three Pillars
| Primitive | Purpose | Standard |
|---|---|---|
| UUIDv7 | Time‑ordered unique identifiers | RFC 9562 |
| Hash Chain | Tamper‑evident linking | SHA‑256 |
| Ed25519 | Digital signatures | RFC 8032 |
| Merkle Tree | Efficient batch verification | RFC 6962 |
Each solves a specific problem; together they form an unbreakable chain of evidence.
Pillar 1: UUIDv7 — Time‑Ordered Identity
UUIDv7 (RFC 9562) embeds a 48‑bit Unix timestamp in milliseconds directly into the identifier, giving:
- Lexicographic sorting = chronological sorting
- Embedded temporal proof independent of any separate
timestampfield - Uniqueness without central coordination
# uuidv7_example.py
import uuid
import time
def generate_uuidv7() -> str:
"""Generate a UUIDv7 identifier with embedded timestamp."""
# Current time in milliseconds
timestamp_ms = int(time.time() * 1000)
# UUIDv7 structure:
# - 48 bits: timestamp (ms)
# - 4 bits: version (7)
# - 12 bits: random
# - 2 bits: variant
# - 62 bits: random
# Python 3.12+ includes uuid7; for earlier versions use the `uuid7` package
return str(uuid.uuid.7())
# Example output: "019234ab-cdef-7000-8123-456789abcdef"
# ^^^^^^^^ timestamp embedded here
# uuidv7_utils.py
def extract_timestamp_from_uuidv7(uuid_str: str) -> int:
"""Extract the embedded millisecond timestamp from a UUIDv7."""
uuid_hex = uuid_str.replace("-", "")
# First 48 bits (12 hex chars) contain the timestamp
return int(uuid_hex[:12], 16)
def detect_timestamp_anomaly(event_id: str, claimed_timestamp_ms: int,
threshold_ms: int = 5000) -> bool:
"""
Return True if the claimed timestamp deviates from the UUIDv7 embedded
timestamp by more than `threshold_ms`.
"""
embedded_ts = extract_timestamp_from_uuidv7(event_id)
drift = abs(embedded_ts - claimed_timestamp_ms)
return drift > threshold_ms
Why this matters – If an event claims to have occurred at time T but the UUIDv7 timestamp says T + 5 minutes, the discrepancy is cryptographically evident.
Pillar 2: Hash Chains — Tamper‑Evident Linking
Each event stores the hash of the previous event, forming an immutable chain:
Event₁ → H(Event₁) → Event₂ → H(Event₂) → Event₃ → …
↓ ↓
prev_hash prev_hash
If any earlier event is altered, its hash changes, breaking the link to all subsequent events.
# hash_chain.py
import hashlib
import json
# Genesis hash: 64 zeros (256 bits)
GENESIS_HASH = "0" * 64
def canonicalize_json(obj: dict) -> str:
"""
RFC 8785 JSON Canonicalization:
- Lexicographic key ordering
- No whitespace
- Deterministic number formatting
"""
return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def compute_event_hash(header: dict, payload: dict, prev_hash: str,
algo: str = "sha256") -> str:
"""
Compute event hash as:
H( canonical(header) || canonical(payload) || prev_hash )
"""
data = (
canonicalize_json(header) +
canonicalize_json(payload) +
prev_hash
).encode("utf-8")
if algo == "sha256":
return hashlib.sha256(data).hexdigest()
elif algo == "sha3_256":
return hashlib.sha3_256(data).hexdigest()
else:
raise ValueError(f"Unsupported algorithm: {algo}")
# chain_validation.py
def validate_chain(events: list[dict]) -> tuple[bool, str]:
"""
Validate the integrity of an event chain.
Returns (is_valid, message).
"""
if not events:
return True, "Empty chain"
# First event must reference the genesis hash
if events[0]["security"]["prev_hash"] != GENESIS_HASH:
return False, "Genesis event has incorrect prev_hash"
# Verify each subsequent event
for i in range(1, len(events)):
cur = events[i]
prev = events[i - 1]
# Expected hash of the previous event
expected_prev_hash = compute_event_hash(
prev["header"], prev["payload"], prev["security"]["prev_hash"]
)
# Linkage check
if cur["security"]["prev_hash"] != expected_prev_hash:
return False, f"Chain broken at event {i}: prev_hash mismatch"
# Verify current event's own hash
computed_hash = compute_event_hash(
cur["header"], cur["payload"], cur["security"]["prev_hash"]
)
if cur["security"]["event_hash"] != computed_hash:
return False, f"Event {i} hash mismatch: content was modified"
return True, "Chain valid"
Pillar 3: Ed25519 Signatures — Authenticity and Non‑Repudiation
Hash chains guarantee integrity but not who created a record. Ed25519 (RFC 8032) provides fast, deterministic, 256‑bit security signatures.
# ed25519_signer.py
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey
)
from cryptography.hazmat.primitives import serialization
class EventSigner:
"""Ed25519 signer for VCP events."""
def __init__(self, private_key: Ed25519PrivateKey = None):
self.private_key = private_key or Ed25519PrivateKey.generate()
self.public_key = self.private_key.public_key()
def get_public_key_hex(self) -> str:
"""Export public key as a hex string."""
pk_bytes = self.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
return pk_bytes.hex()
def sign_event(self, event_hash: str) -> str:
"""Sign an event hash; returns hex‑encoded signature."""
signature = self.private_key.sign(bytes.fromhex(event_hash))
return signature.hex()
@staticmethod
def verify_signature(public_key_hex: str, event_hash: str,
signature_hex: str) -> bool:
"""Verify a signature; returns False on failure."""
try:
public_key = Ed25519PublicKey.from_public_bytes(
bytes.fromhex(public_key_hex)
)
public_key.verify(
bytes.fromhex(signature_hex),
bytes.fromhex(event_hash)
)
return True
except Exception:
return False
Putting It All Together
- Generate a UUIDv7 for the event – this supplies a cryptographic timestamp.
- Create the event payload (header, data, etc.) and compute its hash, chaining to the previous event’s hash.
- Sign the event hash with an Ed25519 private key; store the signature and the public‑key identifier alongside the event.
- Optionally batch many events into a Merkle tree (RFC 6962) for efficient collective verification.
The resulting log provides:
- Chronological ordering via UUIDv7.
- Tamper‑evidence via hash chaining (and optionally Merkle proofs).
- Authenticity & non‑repudiation via Ed25519 signatures.
This combination yields a robust, tamper‑proof decision log suitable for high‑throughput AI and autonomous systems.