Back to Documentation
ArchitectureUpdated 2026-04-02

Proof Chain

Ed25519-signed, hash-chained audit trail: how proofs are created, verified, and why tamper detection works.

Proof Chain

The Proof Chain is the immutable audit trail of every governance decision in the Vorion stack. Every ALLOW, DENY, ESCALATE, and DEGRADE decision produces a proof record that is hash-chained and cryptographically signed.

The proof chain is the complement to the Live Agent Anchor. The anchor is mutable (current state). The proof chain is immutable (complete history). Together they provide real-time governance with cryptographic accountability.

Implemented in @vorionsys/proof-plane.


How It Works

Each proof event contains three cryptographic primitives:

  1. SHA-256 content hash -- A hash of the event's contents
  2. SHA3-256 integrity anchor -- A second hash algorithm for defense in depth
  3. Ed25519 digital signature -- Cryptographic proof of authorship

And one chain linkage:

  1. Previous hash -- The SHA-256 hash of the preceding event
graph LR
    subgraph "Event 1 (genesis)"
        E1H[eventHash: sha256:abc...]
        E1P[previousHash: null]
        E1S[signature: Ed25519]
    end

    subgraph "Event 2"
        E2H[eventHash: sha256:def...]
        E2P[previousHash: sha256:abc...]
        E2S[signature: Ed25519]
    end

    subgraph "Event 3"
        E3H[eventHash: sha256:ghi...]
        E3P[previousHash: sha256:def...]
        E3S[signature: Ed25519]
    end

    E1H -->|referenced by| E2P
    E2H -->|referenced by| E3P

The first event in a chain has previousHash: null. Every subsequent event references the prior event's hash. Modifying any event invalidates every event that follows it.


Creating a Proof

import { createProofService } from '@vorionsys/atsf-core';

const proofService = createProofService();

const proof = await proofService.create({
  intent: {
    parsedAction: 'write_external_api',
    riskLevel: 'MEDIUM',
    capabilities: ['write_data', 'external_network'],
  },
  decision: 'ALLOW',
  outcome: 'SUCCESS',
  agentId: 'data-sync-bot',
  riskLevel: 'MEDIUM',
  inputs: {
    endpoint: 'https://partner.example.com/api/customers',
    recordCount: 42,
  },
  outputs: {
    statusCode: 200,
    recordsUpdated: 42,
  },
});

The proof service:

  1. Serializes the event with deterministic key ordering
  2. Computes the SHA-256 content hash
  3. Computes the SHA3-256 integrity anchor
  4. Retrieves the previous event's hash for chain linkage
  5. Signs the event with Ed25519
  6. Stores the complete proof record

Hash Computation

Hashing uses deterministic serialization to ensure the same event always produces the same hash, regardless of property insertion order.

// From packages/proof-plane/src/events/hash-chain.ts

// Fields included in the hash (excludes eventHash itself and recordedAt)
interface HashableEventData {
  eventId: string;
  eventType: string;
  correlationId: string;
  agentId?: string;
  payload: ProofEventPayload;
  previousHash: string | null;
  occurredAt: string;
  signedBy?: string;
  signature?: string;
}

// Recursive key sorting for deterministic JSON
function sortObjectKeys(obj: unknown): unknown {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(sortObjectKeys);
  const sorted: Record<string, unknown> = {};
  for (const key of Object.keys(obj).sort()) {
    sorted[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);
  }
  return sorted;
}

// Hash = SHA-256 of sorted, serialized event data
async function computeEventHash(event): Promise<string> {
  const hashable = getHashableData(event);
  const sorted = sortObjectKeys(hashable);
  const serialized = JSON.stringify(sorted);
  return sha256(serialized);
}

The dual-hash approach (SHA-256 primary + SHA3-256 anchor) provides defense in depth. If a vulnerability is found in one hash algorithm, the other still protects chain integrity.


Ed25519 Signatures

Every proof event is signed with Ed25519, providing:

  • 128-bit security level
  • 64-byte signatures (compact)
  • Fast signing and verification
  • Deterministic -- no random nonce, same input always produces same signature

Key Management

import { generateSigningKeyPair, createSigningService } from '@vorionsys/proof-plane';

// Generate a key pair for a service
const keyPair = await generateSigningKeyPair('proof-plane-prod');
// keyPair.publicKey  -- share with verifiers
// keyPair.privateKey -- keep secret

// Create a signing service
const signingService = createSigningService({
  serviceId: 'proof-plane-prod',
  privateKey: keyPair.privateKey,
  keyId: keyPair.keyId,
  trustedKeys: [
    { publicKey: keyPair.publicKey, keyId: keyPair.keyId, owner: 'proof-plane-prod' },
  ],
});

Signing an Event

// Sign during proof creation
const signature = await signingService.sign(event);
// Returns base64-encoded Ed25519 signature

The signable data includes the event contents and the signedBy field, but excludes the signature itself and computed fields like eventHash:

interface SignableEventData {
  eventId: string;
  eventType: string;
  correlationId: string;
  agentId?: string;
  payload: ProofEventPayload;
  previousHash: string | null;
  occurredAt: string;
  signedBy: string;
}

Verification

Chain verification checks three things for every event:

  1. Hash integrity -- The stored hash matches a recomputed hash of the contents
  2. Dual-hash integrity -- The SHA3-256 anchor also matches (if present)
  3. Chain linkage -- The previousHash matches the prior event's eventHash
import { verifyChainWithDetails } from '@vorionsys/proof-plane';

// Verify an entire chain
const result = await verifyChainWithDetails(events);

console.log(result);
// {
//   valid: true,
//   verifiedCount: 1247,
//   totalEvents: 1247,
//   firstEventId: 'evt-001',
//   lastEventId: 'evt-1247',
// }

If any event is tampered with, verification reports exactly where the chain broke:

// Tampered chain
const result = await verifyChainWithDetails(tamperedEvents);
// {
//   valid: false,
//   verifiedCount: 842,
//   totalEvents: 1247,
//   brokenAtIndex: 842,
//   brokenAtEventId: 'evt-843',
//   error: 'Event evt-843 has invalid SHA-256 hash',
// }

Signature Verification

Signatures are verified independently of the hash chain:

const sigResult = await signingService.verify(event);
// {
//   valid: true,
//   signer: 'proof-plane-prod',
//   verifiedAt: '2026-04-02T10:30:00Z',
// }

Batch verification checks all events at once:

import { verifyEventSignatures } from '@vorionsys/proof-plane';

const batchResult = await verifyEventSignatures(events, signingService);
// {
//   totalEvents: 1247,
//   validCount: 1247,
//   invalidCount: 0,
//   unsignedCount: 0,
//   success: true,
// }

Why Tamper Detection Works

The proof chain is tamper-evident because of three interlocking properties:

1. Hash Chain Integrity

Modifying any event changes its content hash. Since the next event's previousHash references the original hash, the chain breaks at the modification point.

Original:  E1(hash=abc) -> E2(prev=abc, hash=def) -> E3(prev=def)
Tampered:  E1(hash=xyz) -> E2(prev=abc, hash=def) -> E3(prev=def)
                              ^-- broken: prev=abc != xyz

To hide a modification, you would need to recompute every subsequent event's hash -- which requires the signing key for every event.

2. Ed25519 Signatures

Even if an attacker recomputes hashes, they cannot produce valid signatures without the private signing key. The signature covers the event contents including the previousHash, so rewriting the chain requires the key.

3. Dual-Hash Defense

SHA-256 and SHA3-256 use different algorithms. A collision attack against one would not affect the other. Both must validate for the chain to be considered intact.


Chain Per Agent

Each agent has its own proof chain. The genesis event (first action by that agent) has previousHash: null. All subsequent events chain from there.

This means:

  • Agent chains are independent -- one agent's chain cannot affect another's
  • Verification can target a single agent's history
  • Chain length scales with agent activity, not system activity

Proof Bridge

The Proof Bridge connects the local ATSF proof chain with the Cognigate cloud proof chain. Decisions made locally are forwarded to the centralized proof plane for cross-agent and cross-tenant auditability.

Local ATSF Engine -> Proof Bridge -> Cognigate Proof Plane
                                            |
                                    Optional: Polygon anchoring

For highest assurance, proofs can be anchored to the Polygon blockchain, providing cross-system trust verification. This is optional and configurable.


Data Lifecycle

| Stage | Duration | Storage | |-------|----------|---------| | Hot | 90 days | PostgreSQL, fast queries | | Warm | 2 years | TimescaleDB, compressed | | Cold | 7 years | S3/Blob, compliance archive |

Proofs are never deleted -- only moved to cheaper storage. The 7-year retention matches common regulatory requirements (SOC 2, GDPR accountability, EU AI Act).


Next Steps