Skip to content

Verifiable AI-Agent Identity with Cross-Service Unlinkability

AI agents increasingly act across multiple services — booking appointments, fetching data, executing trades, coordinating with other agents. Each service needs to authenticate the agent: is this really the org's approved agent, authorized for this action? But the standard solution — a single API key or signed identity — creates a cross-service tracking identifier. Every service the agent touches sees the same key, and collusion or compromise links the agent's entire activity footprint. Tessera gives agents verifiable identity with per-service unlinkability: each service authenticates the agent under a fresh, blinded pseudonym, and no service (nor an observer) can link the agent's activity across services.

The problem: agents need identity without being tracked

Current agent authentication patterns fall into two camps, both flawed for privacy:

  • Shared API key / bearer token. Every service sees the same credential. A single compromise or colluding service set reconstructs the agent's full cross-service activity graph.
  • Per-service keypairs. Better, but the agent's operator must manage N keys, and there is no cryptographic relationship proving they all belong to the same root agent identity. Services cannot verify "this is the same agent that enrolled with me, in a new session."

Worse, the timing of agent calls is itself identifying: a service that sees a request at a characteristic time can correlate it with other services' logs. Without cover traffic, even unlinkable keys leak via metadata.

How Tessera solves it

Tessera composes ZK authentication, per-service blinded pseudonyms, and DP cover traffic to give agents all three properties at once.

  • ZK proof authenticates the agent. The agent holds a long-term keypair. Each service, at enrolment, receives a shared_seed. The agent presents a Schnorr / Fiat–Shamir proof under a blinded pseudonym; the service's verifier accepts or rejects — 0% forgery in testing.
  • Per-service blinded pseudonyms prevent cross-service linking. The agent presents Y'_service = Y + H(seed_service ‖ session_id)·G. Each service sees a different pseudonym; the same agent looks like N unrelated identities to N services. Colluding services cannot link them without breaking discrete log.
  • DP cover traffic hides when/where the agent operates. The (ε,δ)-DP cover-traffic budget bounds an observer's ability to correlate agent calls across services by timing. Activity patterns that would be obvious in a plaintext schedule become indistinguishable from cover.
  • Session-bound proofs. Each proof binds a session_id, so a service cannot replay it for a different action.

Architecture

The agent holds one long-term keypair. Each service it interacts with holds a per-agent shared_seed from a one-time enrolment. For each call, the agent derives a service-specific, session-specific blinded pseudonym and proves identity under it.

Agent (holds long-term key Y_agent)         Service A (holds seed_A)
                                            Service B (holds seed_B)
                                            Service C (holds seed_C)
  │
  per call to service X, session s:
  1. seed_X  = enrolled seed for service X
  2. Y'_X,s  = Y_agent + H(seed_X ‖ s)·G    ← per-service, per-session pseudonym
  3. π       = ZKProver.prove(Y'_X,s, action)   ← Schnorr FS proof
  4. request = { action, proof: π, pseudonym: Y'_X,s, session_id: }
                                            │
                                            5. BlindedVerifier.verify(π, Y'_X,s, action)
                                               → accept: this is the enrolled agent, this session
                                               → reject: forged / replay / wrong scope

Service A's Y'_A,s  ≠  Service B's Y'_B,s'  (different seeds → unlinkable)
Even if A and B collude, they cannot link without solving DLog on Y.

Use cases

Use caseDescription
Agent-to-service authenticationAn agent proves it is the org-approved agent to a service, without revealing which agent instance or org it belongs to globally.
Verifiable agent actionsA service receives a request with a ZK proof that a specific, pre-enrolled agent authorized it — auditable without exposing the agent graph.
Scope-bound delegationAn agent acts on behalf of a user; the proof binds scope and session so the service can enforce least-privilege without tracking the agent across services.

Scope-bound delegation is especially powerful: an agent acting on behalf of a user can produce a proof binding (agent, scope, session), so a service enforces least-privilege per call without tracking the agent's identity across services or sessions.

Code example

from tessera.sdk import Sender, Verifier
from tessera.crypto.blinding import BlindedSender, BlindedVerifier

# agent enrols once per service (out of band)
seeds = dict(
    booking=b"...seed from booking service...",
    calendar=b"...seed from calendar service...",
    payroll=b"...seed from payroll service...",
)

agent = Sender(secret_key=b"...agent long-term sk...")

# --- agent calls the booking service, session s7 ---
service = "booking"
session_id = "s7"
bs = BlindedSender(agent.public_key, seeds[service], session_id=session_id)
y_prime = bs.pseudonym()                          # unique to booking@s7
proof   = bs.prove(b"book:resource=conf_room_3;window=14:00-15:00")

request = {
    "action": "book",
    "params": {"resource": "conf_room_3", "window": "14:00-15:00"},
    "pseudonym": y_prime.hex(),
    "proof": proof.hex(),
    "session_id": session_id,
}

# --- booking service verifies ---
bver = BlindedVerifier(
    Verifier(expected_pubkey=agent.public_key),
    seeds[service], session_id=session_id,
)
ok = bver.verify(proof, y_prime, b"book:resource=conf_room_3;window=14:00-15:00")
assert ok   # enrolled agent, this session, this action

# Calling the calendar service next uses a *different* pseudonym — unlinkable.

Why this matters for agent frameworks

Agent frameworks (LangGraph, CrewAI, AutoGen) currently authenticate tool calls with shared secrets or per-run tokens. Neither scales to cross-service privacy. As agents act on behalf of users across sensitive domains — healthcare booking, financial actions, internal enterprise APIs — the ability to prove authorization without being tracked becomes a framework-level requirement. Tessera's blinded-pseudonym model is a drop-in identity layer for agent runtimes: one long-term key, N service seeds, unlinkable by construction.

Frequently asked questions

How is this different from per-service API keys?

Per-service keys are unlinkable but unauthenticated — service B cannot verify the key belongs to the same root agent as service A's key, because there is no cryptographic relationship. Tessera's blinded pseudonyms are all derived from one long-term key under a per-service seed, so each service verifies 'this is the agent I enrolled with' cryptographically, while the pseudonyms remain unlinkable across services. The authentication and unlinkability properties come from the same construction.

Does the agent need to run Tessera's full relay network?

No. For agent-to-service authentication the cover-traffic and relay overlay are optional — they matter when you also want timing privacy across services. The core identity layer (BlindedSender + BlindedVerifier + ZK proof) is a library call with no network dependency. Use the full transport when you need to hide when the agent acted, not just who it is.

Give your agents verifiable, unlinkable identity

Integrate Tessera's blinded-pseudonym identity layer into your agent runtime.

pip install tessera