Decentralized identity
W3C DIDs with Ed25519 cryptography, automatic registration, and AES-256-GCM keystore
Every agent gets a cryptographic identity rooted in W3C DIDs -- no central certificate authority required.
AgentField assigns each agent and its functions a did:key identifier backed by Ed25519 key pairs. DIDs are generated deterministically on registration, organized in a 3-tier hierarchy (platform, agent, function), and stored in an AES-256-GCM encrypted keystore. Every execution can be cryptographically tied to the agent that performed it.
When you publish selected capabilities through external agent discovery, the public entry can include trust metadata derived from the same identity system. External clients can discover a reasoner, see the publisher identity, and decide whether to import it before any runtime call is allowed.
from agentfield import Agent
# Enable DID — agent + each function gets a did:key identity
app = Agent(
node_id="treasury",
version="1.0.0",
enable_did=True, # Ed25519 key pair, AES-256-GCM encrypted keystore
)
@app.reasoner()
async def secure_transfer(amount: float, execution_context=None) -> dict:
# Every request carries the caller's cryptographic identity
caller = execution_context.caller_did # did:key:z6MkpTHR...
my_did = app.did_manager.get_agent_did() # did:key:z6MkhaXg...
app.note(f"Transfer ${amount} authorized by {caller}", ["audit", "transfers"])
# Function-level DIDs let you trace exactly which reasoner ran
fn_did = app.did_manager.get_function_did("secure_transfer")
return await app.ai(system="Process this transfer.", user=str(amount))What just happened
The page example registered an agent, exposed its DID package, and showed that both the agent and its functions can be resolved through the control plane. The important takeaway is not just that identities exist, but that identity becomes inspectable infrastructure instead of hidden SDK state.
{
"agent_did": "did:key:z6Mk...",
"function_dids": [
"did:key:z6Mk...#audit"
],
"resolution_endpoints": [
"/api/ui/v1/nodes/secure-agent/did",
"/api/v1/registered-dids"
]
}
What you get
- W3C DID identifiers -- agents and their functions each receive a
did:keyidentifier backed by Ed25519 key pairs - Web-resolvable DIDs --
did:webdocuments served at standard paths for cross-network resolution - Automatic registration -- DIDs are generated deterministically when agents register with the control plane
- 3-tier hierarchy -- platform root, agent node, and individual function DIDs. Agents that host sessions register and sign the same way agents that host reasoners and skills do.
- Encrypted keystore -- private keys stored with AES-256-GCM encryption
- Non-repudiation -- every execution can be cryptographically tied to the agent that performed it
Enabling DIDs
from agentfield import Agent
app = Agent(
node_id="auditor",
version="1.0.0",
enable_did=True, # Enabled by default
)
@app.reasoner()
async def audit(report: str) -> dict:
# This execution will be signed with the agent's DID
return await app.ai(system="Audit this report.", user=report)
app.run()How it works
DID Hierarchy
AgentField creates a 3-tier DID hierarchy using deterministic key derivation:
| Level | Derivation Path | Purpose |
|---|---|---|
| Platform root | m/44'/0' | Signs agent registration VCs; root of trust |
| Agent node | m/44'/1'/{node_index} | Signs execution VCs; identifies the agent |
| Function | m/44'/1'/{node_index}/{function_index} | Identifies specific reasoners and skills |
Key Generation
Keys are derived deterministically from a master seed using HKDF (HMAC-based Key Derivation Function) with SHA-256:
- The control plane generates a 256-bit master seed on first initialization
- The root DID is derived from the master seed using path
m/44'/0' - Agent DIDs are derived using HKDF with the agent's node ID as context
- Function DIDs are derived from the agent DID with the function name as context
This means the same master seed always produces the same DID hierarchy -- deterministic and reproducible.
DID Formats
AgentField supports two DID methods:
did:key -- self-certifying identifier encoding the Ed25519 public key directly:
did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
Resolvable by anyone who can decode the identifier -- no network call required.
did:web -- web-resolvable identifier tied to the control plane's domain:
did:web:agentfield.example.com:agents:auditor
Resolved by fetching the DID document from the agent's well-known path.
did:web Resolution
The control plane serves DID documents at standard paths:
GET /.well-known/did.json # Platform DID document
GET /agents/{agentID}/did.json # Agent-specific DID document
These documents follow the W3C DID Document specification and include the agent's public key, verification methods, and service endpoints.
Keystore
Private keys are encrypted at rest using AES-256-GCM:
| Property | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key size | 256 bits |
| Nonce | Generated fresh per encryption operation |
| Storage | Local filesystem at ./data/keys/ (relative to working directory) |
| Directory permissions | 0700 (owner only) |
The keystore currently supports local filesystem storage. The service interface is designed for future HSM and cloud KMS integration.
Configuration
Enable or disable DIDs in agentfield.yaml:
features:
did:
enabled: true
keystore:
type: local
path: ./data/keys
authorization:
domain: agentfield.example.comDID Authentication
Use DIDs for authenticating outbound HTTP requests between agents. The SDK signs requests with the agent's Ed25519 private key and the receiving agent verifies the signature using the sender's public DID.
Python -- DIDAuthenticator
from agentfield.did_auth import DIDAuthenticator
import json
# Create an authenticator with DID and private key
authenticator = DIDAuthenticator(
did=app.did_manager.get_agent_did(),
private_key_jwk=private_key_jwk, # JWK-formatted Ed25519 private key
)
# Sign an outbound HTTP request body
body = json.dumps({"query": "portfolio risk"}).encode()
headers = authenticator.sign_headers(body)
# headers includes:
# X-Caller-DID: did:key:z6Mk...
# X-DID-Signature: <base64-encoded Ed25519 signature>
# X-DID-Timestamp: <unix timestamp>
# X-DID-Nonce: <hex-encoded random nonce>
import httpx
resp = await httpx.AsyncClient().post(
"https://partner-api.example.com/data",
content=body,
headers={**headers, "Content-Type": "application/json"},
)DIDAuthenticator methods:
| Method | Description |
|---|---|
sign_headers(body) | Generate signed DID auth headers for a request body |
set_credentials(did, private_key_jwk) | Set or update DID credentials |
is_configured | Property: whether DID and private key are loaded |
did | Property: the agent's DID identifier |
TypeScript
import { DIDAuthenticator } from '@agentfield/sdk';
const auth = new DIDAuthenticator(agentDid, privateKeyJwk);
// Sign outbound request body
const body = Buffer.from(JSON.stringify({ query: 'portfolio risk' }));
const headers = auth.signRequest(body);
// headers includes: X-Caller-DID, X-DID-Signature, X-DID-Timestamp, X-DID-NonceGo -- client.SignHTTPRequest()
import "github.com/Agent-Field/agentfield/sdk/go/client"
c, _ := client.New("http://localhost:8080", client.WithDIDAuth(agentDID, privateKeyJWK))
// SignHTTPRequest signs the request in-place with DID auth headers
body := []byte(`{"query":"portfolio risk"}`)
req, _ := http.NewRequest("POST", "https://partner-api.example.com/data", bytes.NewReader(body))
c.SignHTTPRequest(req, body)
// req now has X-Caller-DID, X-DID-Signature, X-DID-Timestamp, X-DID-Nonce headersEnabling DID Auth on All Outbound Calls
Set RequireOriginAuth in Go agent config to validate that incoming execution requests include proper authorization. When EnableDID is set, outbound calls via Call() are automatically signed with DID auth headers.
app = Agent(
node_id="secure-agent",
enable_did=True, # outbound calls are DID-signed automatically
)Patterns
Verifying Agent Identity
Use the DID to verify that an execution was performed by a specific agent:
# Fetch the agent's DID
import requests
resp = requests.get("http://localhost:8080/api/v1/registered-dids")
dids = resp.json()
# Find the agent's public key
for did_info in dids:
if did_info["agent_node_id"] == "auditor":
public_key = did_info["public_key_jwk"]
# Use this to verify VC signaturesCross-Network Identity
Use did:web when agents need to verify each other across networks:
# Agent A resolves Agent B's identity
GET https://other-platform.com/agents/data-provider/did.json
# The DID document contains Agent B's public key
# Agent A can now verify VCs signed by Agent B
See Credentials for how DIDs are used to sign execution verifiable credentials.
ARD Trust Metadata
Public discovery entries can include a trust manifest with the publisher identity. In AgentField, that means an Agentic Resource Discovery entry can advertise the DID-backed identity of the control plane or agent function while still requiring a separate import and callable-binding decision on the consuming side.
{
"displayName": "Contract Review Reasoner",
"type": "application/openapi+json",
"trustManifest": {
"identity": "did:web:example.com",
"identityType": "did"
}
}Use this as discovery-time trust context, not as a replacement for access policies or credential management.
API reference
Platform DID
GET /api/v1/did/agentfield-server
Returns the control plane's root DID and public key.
Issuer Public Key
GET /api/v1/did/issuer-public-key
Returns the Ed25519 public key used to sign verifiable credentials, in JWK format.
Registered DIDs
GET /api/v1/registered-dids
Lists all registered DIDs across the platform -- agents, reasoners, and skills.
Agent DID Resolution
Through the UI API:
GET /api/ui/v1/nodes/{nodeId}/did
Returns the DID identity package for a specific agent node, including the agent DID and all function DIDs.
DID Resolution Bundle
GET /api/ui/v1/did/{did}/resolution-bundle
GET /api/ui/v1/did/{did}/resolution-bundle/download
Returns a complete resolution bundle for offline verification: the DID document, the public key, and any associated verifiable credentials.