AI Agent DID:key Implementation

Ed25519 keys generated by the operator, signed Verifiable Credentials issued by aiegis, verified in sub-15ms. 2026-05-25.

The Problem with Agent Identity

An autonomous AI agent is a software process that takes actions on behalf of a human or organisation. The standard answer to "who is this agent" is one of three: a long-lived API key, an OAuth client_id, or a header the agent attaches to itself. None of those are identity in any cryptographic sense. Anyone who exfiltrates the credential becomes the agent. Anyone who forges the header becomes the agent. And none of those constructs let a downstream verifier check "did the operator that registered this agent revoke it three minutes ago?" without an online round-trip to a single trust anchor.

The W3C Decentralized Identifiers specification provides the shape of a fix. A DID is a URI of the form did:<method>:<method-specific-id>, which resolves to a DID document carrying the public keys and service endpoints for that subject. The did:key method is the simplest concrete instantiation: the DID is itself a multibase encoding of the public key, so resolution is offline and deterministic.

Why did:key for Agents (Not did:web, Not did:ion)

did:web requires an HTTPS-resolvable domain per agent. Fine for organisations; impractical when an operator spawns 10,000 short-lived agents an hour. did:ion requires Sidetree + Bitcoin anchoring; too slow for ephemeral identity. did:key has three properties that match the agent use case:

The trade-off: did:key has no built-in rotation or revocation. aiegis layers a Verifiable Credential on top, signed by the operator's aiegis issuer key, and publishes the revocation list at /registry/revocations. The agent's did:key is the subject of the VC; the aiegis issuer key is the issuer; revocation flips the credential, not the DID.

Step 1 — Generate the Agent Keypair Locally

The operator generates the agent's keypair. The private key never leaves the operator's infrastructure.

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
import base58

sk = Ed25519PrivateKey.generate()
pk_raw = sk.public_key().public_bytes(
    encoding=serialization.Encoding.Raw,
    format=serialization.PublicFormat.Raw,
)
# multicodec prefix for Ed25519 public key is 0xed01
multicodec_pk = b"\xed\x01" + pk_raw
did_key = "did:key:z" + base58.b58encode(multicodec_pk).decode()

pk_pem = sk.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode()
print(did_key)
# did:key:z6MkpTH...

The PEM-encoded public key is what aiegis expects in the issuance call. The did:key string is the agent's stable identifier across every system that consumes the passport.

Step 2 — Get a Challenge for Proof-of-Possession

Before aiegis will sign a passport, it requires the operator to prove they hold the agent's private key. This blocks the class of attack where an attacker submits a public key they don't control and requests issuance against it. /api/agent/issue/challenge returns a nonce; the operator signs the nonce with the agent's Ed25519 private key; the signature is attached to the issuance request.

curl -X POST https://aiegis.ie/api/agent/issue/challenge \
  -H "Authorization: Bearer ${AIEGIS_OPERATOR_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"agent_pubkey_pem": "<PEM from step 1>"}'

# returns: {"challenge_id": "ch_abc...", "challenge_nonce_hex": "deadbeef...", ...}

The proof-of-possession check is enforced in /api/agent/issue (per the live handler at api_v2.py:7233). If the caller supplies challenge_id without a valid challenge_signature_hex over the nonce, the call rejects.

Step 3 — Issue the Passport

The issuance call carries the public key, the proof-of-possession signature, the operator id, the agent id, the EU AI Act risk classification, and the governance payload (all five universal pillars). Any pillar set to false rejects at 400 — the AgenticOS Phase 1 Day 3-5 contract is "all five fields must be true/present for passport to pass validation."

curl -X POST https://aiegis.ie/api/agent/issue \
  -H "Authorization: Bearer ${AIEGIS_OPERATOR_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_demo_01",
    "operator_id": "your_operator_id",
    "agent_pubkey_pem": "<PEM>",
    "credentials": {},
    "risk_classification": "limited",
    "governance_payload": {
      "pillars_version": "v1.0",
      "accountability_enforced": true,
      "transparency_enforced": true,
      "audit_trail_enabled": true,
      "intervention_capable": true
    },
    "challenge_id": "ch_abc...",
    "challenge_signature_hex": "<sig over nonce>"
  }'

The response is a W3C Verifiable Credential whose credentialSubject.id is the agent's did:key. The proof block is an Ed25519 signature over the canonicalised credential, produced by the per-operator aiegis issuer key. The verifier needs only the credential plus /registry/keys plus /registry/revocations to make a decision.

Step 4 — Verify

curl -X POST https://aiegis.ie/api/agent/verify \
  -H "Content-Type: application/json" \
  -d '{"passport": <VC from step 3>}'

# returns: {"valid": true, "revoked": false, "issuer_kid": "...", ...}

Verify is unauthenticated, on purpose. Anyone can verify a passport — that is the point of public-key identity. aiegis measures sub-15ms p95 verification on customer-co-located infrastructure (loopback). Public HTTPS round-trip to aiegis.ie is sub-300ms.

Mapping to EU AI Act Article 26

Article 26 of Regulation (EU) 2024/1689 imposes obligations on deployers of high-risk AI systems: human oversight, technical robustness, and (per the implementing acts) attestation of capability tier. The agent passport surfaces each of these as a verifiable field. The five universal pillars in governance_payload map one-to-one to Article 26 sub-paragraphs; the per-paragraph mapping is at /article-26-walkthrough.

How This Composes with Grid

Once an agent has a did:key plus a signed aiegis passport, it can join the Grid agent-to-agent marketplace. The Grid session token (X-AIEGIS-Tag) is a JWT (EdDSA, RFC 8037) whose sub claim is the agent's did:key and whose act.sub claim is the principal_ref (RFC 8693). Live JWKS at /grid/.well-known/jwks.json; current kid at the time of writing per /grid/manifest.json: 0tyrGMFykIxD4Orn2782_Q.

What This Doesn't Solve