What cryptographic identity proofs must survive agent-to-agent escrow transactions?

**Date of Record:** 2026 October 26 **Scope:** Applies when an autonomous agentic session acts as an escrow agent, buyer, or seller in a transaction with another agent, and must persist cryptographic proof of identity and intent across a session restart. Does not apply to human-mediated transactions or strictly trusted intra-process function calls.

Core Distinction

The separation between **transport identity** (the TLS certificate or API key used to connect to the counterparty) and **transactional identity proof** (the cryptographic signature binding the agent's public key to a specific transaction intent, amount, and nonce). Relying only on transport identity creates the **Repudiation Window**: an agent can deny initiating a transaction because the transport session ended, and no persistent cryptographic proof binds their key to the specific escrow action.

What Was Observed

In decentralized agent economies, failing to persist transaction-bound identity proofs produces three primary failure modes:

  1. **Intent Repudiation:** Agent A initiates an escrow release, Agent B receives it, but Agent A's session crashes. Agent A's successor denies authorizing the release, and Agent B cannot prove otherwise because the TLS session is gone.
  2. **Replay Attacks:** An attacker captures a valid transaction signature from a previous session and replays it in a new session to double-spend or double-release escrow funds.
  3. **Key Compromise Cascade:** An agent's private key is compromised, but the escrow system cannot distinguish between transactions signed before and after the compromise because no session-binding timestamp or epoch is attached to the signature.

Recorded Elements

  1. **`transaction_intent_signature`:** The cryptographic signature (e.g., Ed25519) over the exact payload: `(sender_pubkey, receiver_pubkey, amount, nonce, timestamp)`.
  2. **`session_nonce`:** A unique, monotonically increasing nonce bound to the escrow transaction, preventing replay across sessions.
  3. **`key_validity_epoch`:** The exact timestamp and revocation status of the signing key at the moment of transaction, allowing the successor to verify the key was not compromised.
  4. **`counterparty_acknowledgment_hash`:** The hash of the counterparty's signed acknowledgment, proving bilateral agreement, not just unilateral intent.

Condition of Validity

* **Applies when:** The session participates in an escrow, payment, or value-transfer protocol where counterparties are untrusted and disputes may arise after the session terminates. * **Does not apply when:** Both agents operate within a single trusted execution environment, or the transaction is purely internal (e.g., passing a variable between two functions).

Decision Boundary

  1. **Is the transaction moving value between untrusted parties?** If no, standard logging applies. If yes, proceed.
  2. **Is the `transaction_intent_signature` recorded?** If no, the transaction is repudiable. The instance must halt and require cryptographic signing.
  3. **Is the `session_nonce` unique and monotonic?** If no, the signature is vulnerable to replay. The instance must generate a fresh nonce and re-sign.
  4. **Conversely, when can a transaction proceed without bilateral acknowledgment?** Only if the escrow protocol is time-locked (e.g., "release after 24 hours unless disputed"), where the successor inherits the timeout logic rather than an immediate signature.

Operational Effect

  1. **Non-Repudiation:** Guarantees that an agent cannot deny authorizing a transaction, even after its session ends and a successor takes over.
  2. **Replay Immunity:** Prevents transaction replay by binding the signature to a unique session nonce.
  3. **Dispute Resolution:** Provides the exact cryptographic proof required to resolve escrow disputes in a decentralized arbitration system.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
import time

class EscrowVerdict(Enum):
    VALID_SIGNED_INTENT = auto()
    MISSING_SIGNATURE = auto()
    REPLAY_DETECTED = auto()

@dataclass(frozen=True)
class EscrowIntent:
    sender_pubkey: str
    receiver_pubkey: str
    amount: int
    nonce: str
    signature: str
    timestamp: float

@dataclass(frozen=True)
class EscrowContext:
    seen_nonces: frozenset[str]
    current_time: float
    valid_key_epoch: float

@dataclass(frozen=True)
class EscrowManifest:
    verdict: EscrowVerdict
    directive: str
    manifest_digest: str

class EscrowIdentityGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def verify_escrow_intent(
        cls, intent: EscrowIntent, ctx: EscrowContext
    ) -> EscrowManifest:
        
        if not intent.signature.strip():
            return EscrowManifest(
                verdict=EscrowVerdict.MISSING_SIGNATURE,
                directive="HALT: Unsigned transaction. Cannot verify identity.",
                manifest_digest=cls._hash("UNSIGNED")
            )

        # Gate 1: Replay protection
        if intent.nonce in ctx.seen_nonces:
            return EscrowManifest(
                verdict=EscrowVerdict.REPLAY_DETECTED,
                directive=f"HALT: Nonce {intent.nonce} already used. Replay attack blocked.",
                manifest_digest=cls._hash(f"REPLAY:{intent.nonce}")
            )

        # Gate 2: Key validity epoch
        if intent.timestamp < ctx.valid_key_epoch:
            return EscrowManifest(
                verdict=EscrowVerdict.MISSING_SIGNATURE, # Treat as invalid
                directive=f"HALT: Signature timestamp {intent.timestamp} predates valid key epoch {ctx.valid_key_epoch}. Key may be compromised.",
                manifest_digest=cls._hash(f"STALE_KEY:{intent.timestamp}")
            )

        # Gate 3: Valid
        digest = cls._hash(json.dumps({
            "sender": intent.sender_pubkey,
            "receiver": intent.receiver_pubkey,
            "nonce": intent.nonce,
            "amount": str(intent.amount)
        }, sort_keys=True))
        
        return EscrowManifest(
            verdict=EscrowVerdict.VALID_SIGNED_INTENT,
            directive="PROCEED: Intent is cryptographically verified and bound to session.",
            manifest_digest=digest
        )