ROOTVIELROOTVIEL
← Academy

Module 1 · Foundations of agents & security

Agent identity and authentication

An agent's identity is a key pair, not a name string: the private key proves authorship of messages and the public key lets any peer verify them, so identity becomes something an agent demonstrates by signing rather than something it claims — the unforgeable foundation that authorization and trust are built on.

Learning objective

Understand why each agent needs an unforgeable cryptographic identity, how a key pair lets an agent authenticate its messages and verify its peers, and why "who is this agent?" must be answerable with proof rather than with a claimed name.

Track
Fundamentals
Module
1 · Foundations of agents & security
Prerequisites
Deterministic vs probabilistic agents · Digital signatures

Conceptual introduction

In a multi-agent system, every security decision eventually reduces to a question: who is this? Before you can decide what an agent may do, you must know which agent you are talking to — and you must know it with certainty an adversary cannot fake. A name, a username, a field in a JSON payload that says "from: billing-agent" proves nothing; anyone can type that string. Identity that can be asserted but not proven is not identity, it is a label.

The fix is to make identity something an agent can only demonstrate by possessing a secret. An agent's identity is a cryptographic key pair: a private key it holds and never reveals, and a public key it publishes. The agent proves it is itself by signing messages with the private key; any peer confirms the claim by verifying with the public key (the sign/verify machinery of the digital-signatures lesson). "Who is this agent?" becomes "which public key verifies its signatures?" — a question with an unforgeable answer.

Note

Authentication answers "who sent this and was it altered?" — nothing more. It does not say the sender is honest, competent, or authorized. A perfectly authenticated message can still be a terrible instruction from a compromised agent.

Technical foundation

Identity (as a key pair)
An agent's durable identity is its public key (or a stable identifier bound to it). The matching private key is the secret that makes the identity unforgeable; whoever holds it can act as that identity.
Authentication
Verifying that a message was produced by the holder of a specific private key and not modified in transit — authorship plus integrity.
Key ID (kid)
A short label that lets a verifier select the correct public key from its own trusted store. Crucially, the key is fetched by kid from a trust store, never taken from the message itself.
Trust store / registry
The authoritative mapping from agent identifiers to their public keys. Authentication is only as trustworthy as this binding — poison the registry and signatures verify against the attacker's key.

The essential asymmetry: verification needs no secret. A peer can confirm a message came from billing-agent while holding only billing-agent's public key — it can check authorship but could never impersonate it. This is what makes the model scale to many mutually-suspicious agents and across organizations: you hand out public keys freely and keep private keys locked away, ideally in a KMS or HSM where signing is a call and the key never leaves.

Mathematical model: authenticated messaging

An authenticated message binds the sender's identity, the content, and enough context to stop the message being reused elsewhere. Using Ed25519 (RFC 8032) as the signature scheme, a message is the canonical bytes of a structured envelope, signed by the sender's private key.

authenticated agent message (schematic)
# each agent has a key pair; public keys live in a trusted registry registry[agent_id] -> public_key # the authoritative binding # sender (billing-agent) constructs and signs an envelope: env = { "from": "billing-agent", "to": "ledger-agent", "body": {...}, "nonce": N, "exp": T, "aud": "ledger-agent" } sig = Ed25519_sign(privkey_billing, canonical(env)) send({ env, sig, kid: "billing-agent" }) # receiver (ledger-agent) authenticates: pk = registry[env.kid] # fetch key by kid from REGISTRY assert Ed25519_verify(pk, canonical(env), sig) # authorship + integrity assert env.aud == "ledger-agent" and env.exp > now() and fresh(env.nonce) # only now is it known WHO sent this; WHAT they may do is a separate question

Three bindings earn their place. The nonce and expiry stop a captured-and-replayed message from authenticating again later. The audience (aud) stops a message meant for one peer being replayed to another. And the key is fetched from the registry by kid, never trusted from the envelope — otherwise an attacker simply ships their own public key alongside their forgery and it "verifies". Omit any of these and authentication has holes even though every signature checks out.

Step by step: establishing and using agent identity

  1. 1Provision: generate a key pair per agent; store the private key in a KMS/HSM so it is used by reference and never exported. The public key becomes the agent's identity.
  2. 2Register: record the identifier→public-key binding in a trusted registry. This binding is the root of trust — protect its integrity as carefully as the private keys.
  3. 3Sign: to send a message, canonicalize an envelope (sender, audience, nonce, expiry, body) to exact bytes and sign it with the private key.
  4. 4Verify: the receiver fetches the sender's public key from the registry by kid, re-canonicalizes the envelope, and checks the signature, audience, expiry and nonce.
  5. 5Separate: treat a passed verification strictly as "this is genuinely from agent X" — then, and only then, ask the independent question of what agent X is permitted to do.

Practical example (synthetic)

A synthetic trading desk runs a strategy-agent that proposes trades and an execution-agent that places them. The execution-agent must act only on orders genuinely authored by the strategy-agent. Each has a key pair; the execution-agent holds only the strategy-agent's public key. A rogue process on the network tries to inject an order claiming "from: strategy-agent".

synthetic — only genuine orders are acted on
# legitimate order from strategy-agent: order = { "from":"strategy-agent","to":"exec-agent","symbol":"ACME", "side":"buy","qty":100,"nonce":"9f..","exp":1699999999,"aud":"exec-agent" } sig = Ed25519_sign(strategy_privkey, canonical(order)) # key stays in KMS # exec-agent authenticates before doing anything: pk = registry["strategy-agent"] # NOT from the message ok = Ed25519_verify(pk, canonical(order), sig) \ and order.aud == "exec-agent" and order.exp > now() and fresh(order.nonce) # rogue injection "from: strategy-agent" has NO valid signature under pk -> ok is False if ok: route_to_authorization(order) # who is settled; what-is-allowed comes next

The attacker can copy the name, the format, even an old captured order — but cannot produce a signature that verifies under the strategy-agent's public key, because that requires the private key held in the KMS. A replayed old order fails on the nonce/expiry check; a forged new one fails on the signature. The execution-agent never needed to trust the network; it trusted mathematics and its own registry.

Application to agents

Unforgeable identity is the hinge the rest of the security model turns on. Authorization (next lesson) is meaningless without it: a permission attached to "billing-agent" is only as strong as your ability to prove a message really is from billing-agent. Audit logs are only trustworthy if each recorded action is tied to a verified signer. Delegation — one agent granting a narrowed capability to another — is literally a signed statement from one identity about another, so it presupposes that identities are keys. In every case, authenticated identity is the thing that makes a security claim bind to a real, specific actor.

It is equally important to be clear about what authentication does not buy you. Verifying that a message is genuinely from agent X says nothing about whether X is behaving correctly, whether X has been compromised, or whether X should be allowed to do what it is asking. A compromised agent signs malicious messages with a perfectly valid key. So authentication is necessary but never sufficient: it tells you who, and hands off to authorization and trust-boundary analysis to decide what that who may do.

Risks & limitations

  • ▸Private-key custody is the whole game: whoever holds an agent's private key is that agent. Keep keys in a KMS/HSM; a leaked key means total impersonation and demands revocation plus rotation.
  • ▸Trusting the public key supplied in the message instead of fetching it from the registry by kid defeats authentication entirely — the attacker just ships their own key with their forgery.
  • ▸Registry integrity is a root of trust: if an attacker can alter the identifier→key binding, every signature verifies against their key. Protect and, ideally, sign the registry itself.
  • ▸Authentication without freshness (nonce/expiry/audience) is replayable: a genuine captured message can be re-sent to re-authenticate. Bind each message to one use, one time window and one recipient.
  • ▸Authentication is not authorization and not trust: a valid signature from a compromised or misbehaving agent is still a valid signature. Knowing who sent a message does not mean obeying it.

Exercise

Note

Design the identity layer for a two-agent system where agent A sends commands to agent B. Specify what each agent holds (keys), where public keys come from, and the exact fields B checks before accepting a command. Then describe three forgery/replay attacks (wrong sender, replayed old message, attacker-supplied key) and show precisely which check stops each one.

Expected direction: A correct answer gives each agent a key pair with private keys in a KMS, has B fetch A's public key from a trusted registry by kid (not from the message), and verifies signature + audience + expiry + nonce. It stops impersonation via the signature check, replay via nonce/expiry, and the attacker-supplied-key attack by fetching the key from the registry rather than the envelope.

Check your understanding

Why is an agent's name or a "from" field not an identity?
Because anyone can write that string; it can be claimed but not proven. Real identity is a key pair, demonstrated by signing with the private key and verified against a public key, which an impersonator cannot fake.
Why must the verifier fetch the sender's public key from a trusted registry rather than from the message?
If the key came from the message, an attacker would ship their own public key alongside their forgery and it would "verify"; fetching by kid from a trusted registry binds the signature to the real identity.
Does authenticating a message mean the agent should act on it?
No. Authentication only establishes who sent it and that it was not altered. Whether to act depends on authorization and on whether that agent is trusted — a compromised agent signs malicious messages with a valid key.

References

  1. [1]RFC 8032 — EdDSA / Ed25519
  2. [2]NIST SP 800-63 — Digital Identity Guidelines
  3. [3]RFC 7515 — JSON Web Signature (JWS), on signed envelopes and key identifiers (kid)

References point to public, well-known technical sources for further study.

Related articles