Docs/Concepts

Concepts

The cryptographic primitives and data structures that make every agent action provable.

Hash Chains

Every agent action produces a receipt containing a SHA-256 hash of the action data plus the hash of the previous receipt (previousHash). The first receipt in a session uses "0" as its previous hash. This creates a tamper-evident chain — modifying any receipt breaks all subsequent hashes.

text
Receipt 1: hash = SHA256(data₁ + "0")
Receipt 2: hash = SHA256(data₂ + hash₁)
Receipt 3: hash = SHA256(data₃ + hash₂)

Because each hash depends on the previous one, inserting, removing, or altering any receipt in the chain produces a mismatch that is immediately detectable during verification.

Ed25519 Signing

Every receipt hash is signed with the agent's Ed25519 private key using @noble/ed25519. This proves the receipt was created by the claimed agent — no one else possesses the private key needed to produce a valid signature.

Keys are generated via Invariance.generateKeypair(), which returns a 32-byte private key and its corresponding 32-byte public key, both hex-encoded.

keypair.tstypescript
import { Invariance } from '@invariance/sdk';

const { privateKey, publicKey } = Invariance.generateKeypair();
// privateKey: 64-char hex string (32 bytes)
// publicKey:  64-char hex string (32 bytes)

Receipt Structure

Each receipt captures a single agent action with full cryptographic context:

receipt.tstypescript
interface Receipt {
  id: string;            // ULID identifier
  sessionId: string;     // Parent session
  agent: string;         // Agent that performed the action
  action: string;        // Action name
  input: Record<string, unknown>;
  output?: Record<string, unknown>;
  timestamp: number;     // Unix ms
  hash: string;          // SHA-256 of canonical data
  previousHash: string;  // Previous receipt hash ("0" for first)
  signature: string | null; // Ed25519 signature
}
  • id — A ULID that provides time-ordered, unique identification.
  • sessionId — Links the receipt to its parent session and chain.
  • agent — The identity of the agent that performed the action.
  • action — A human-readable name for the action (e.g., "tool_call", "a2a_send").
  • input / output — Arbitrary JSON payloads capturing the action's data.
  • hash — SHA-256 computed over the canonical (sorted-key) JSON of the receipt data plus previousHash.
  • previousHash — The hash of the preceding receipt, or "0" for the first receipt in a session.
  • signature — The Ed25519 signature over the hash, proving authorship.

Sessions

Sessions group receipts into ordered chains. Each session tracks the full lifecycle of an agent interaction.

session.tstypescript
// Lazy creation — session is created on first receipt
const session = inv.session();

// Awaited creation — session exists immediately
const session = await inv.createSession();

// Record actions within the session
await session.record('tool_call', {
  input: { tool: 'search', query: 'latest news' },
  output: { results: ['...'] },
});

// Close the session when done
await session.close();

Sessions have three possible statuses:

  • open — Actively accepting new receipts.
  • closed — Finalized with a close hash computed over the entire chain.
  • tampered — Verification detected a broken hash or invalid signature.

Closing a session computes a close hash over the entire receipt chain, sealing the session against further modification.

Tamper Evidence

Verification recomputes every hash from the raw action data and checks each Ed25519 signature. If any receipt has been modified — even a single character — the chain breaks and verification fails.

verify.tstypescript
import { verifyChain } from '@invariance/sdk';

const result = await verifyChain(receipts, publicKey);

if (result.valid) {
  console.log('Chain is intact — all hashes and signatures verified');
} else {
  console.error('Tamper detected at receipt:', result.brokenAt);
  console.error('Reason:', result.reason);
}

The verifyChain() function walks the receipt array in order, recomputes each hash from the canonical action data plus the previous hash, and verifies the Ed25519 signature against the agent's public key. A single failure anywhere in the chain marks the entire session as tampered.

Bilateral A2A Proof

When two agents communicate, Invariance creates bilateral proof — both the sender and receiver sign the same message, proving the communication happened and neither party can deny it.

The process works as follows:

  1. Agent A signs the payload hash + metadata, creating a sender signature.
  2. Agent A records an a2a_send receipt in its own session.
  3. Agent B receives the envelope and verifies Agent A's signature.
  4. Agent B counter-signs the message, creating a counter-signature.
  5. Agent B records an a2a_receive receipt with both signatures.
a2a.tstypescript
// Agent A: send a signed message
const envelope = await agentA.a2a.send({
  to: 'acme/agent-b',
  payload: { task: 'review-contract', contractId: 'c-123' },
});
// envelope contains: payload hash, sender signature, metadata

// Agent B: receive and counter-sign
const received = await agentB.a2a.receive(envelope);
// received contains: both sender and receiver signatures
// Both agents now have cryptographic proof of the exchange

This dual-signature model ensures that neither party can deny sending or receiving a message. The a2a_send and a2a_receive receipts are recorded in each agent's respective session chains, creating an independently verifiable record on both sides.

On this page
Hash ChainsEd25519 SigningReceipt StructureSessionsTamper EvidenceBilateral A2A Proof