Cryptographic identity verification for AI agents
Get from zero to a verified, signed message in five requests. All examples use curl — replace YOUR_API_KEY with an eld_-prefixed key.
# Node.js — generate an Ed25519 keypair
node -e "
const { ed25519 } = require('@noble/curves/ed25519');
const priv = ed25519.utils.randomPrivateKey();
const pub = Buffer.from(ed25519.getPublicKey(priv)).toString('hex');
console.log('PRIVATE_KEY=' + Buffer.from(priv).toString('hex'));
console.log('PUBLIC_KEY=' + pub);
"
curl -X POST https://api.eleidon.com/v1/agents/register \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"public_key": "PUBLIC_KEY_HEX",
"claimed_inbox": "agent@yourcompany.com",
"display_name": "Support Bot"
}'
# Response:
# {
# "agent_id": "a1b2c3d4-...",
# "challenge_sent": true,
# "message": "Verification email sent to agent@yourcompany.com"
# }
# Use the 6-character code from the verification email
curl -X POST https://api.eleidon.com/v1/agents/verify-inbox \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "a1b2c3d4-...",
"challenge_code": "AB3K7M"
}'
# Response:
# {
# "inbox_verified": true,
# "verified_at": "2026-03-09T12:00:00Z"
# }
# First, build the canonical message and hash + sign it client-side,
# then record the signature:
curl -X POST https://api.eleidon.com/v1/sign \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "a1b2c3d4-...",
"message_hash": "e3b0c44298fc1c149afbf4c8996fb924...",
"signature": "SIGNATURE_HEX"
}'
# Response:
# {
# "signature_id": "s5e6f7g8-...",
# "eleidon_header": "v1:a1b2c3d4-...:s5e6f7g8-...:SIGNATURE_HEX",
# "status": "recorded"
# }
#
# Attach the header to your outgoing email:
# X-Eleidon-Signature: v1:a1b2c3d4-...:s5e6f7g8-...:SIGNATURE_HEX
curl -X POST https://api.eleidon.com/v1/verify \
-H "Content-Type: application/json" \
-d '{
"sender_inbox": "agent@yourcompany.com",
"message_hash": "e3b0c44298fc1c149afbf4c8996fb924...",
"eleidon_header": "v1:a1b2c3d4-...:s5e6f7g8-...:SIGNATURE_HEX"
}'
# Response:
# {
# "result": "verified",
# "confidence": 1.0,
# "agent_id": "a1b2c3d4-...",
# "display_name": "Support Bot",
# "inbox_verified": true
# }
That's it. Your agent's outgoing emails are now cryptographically signed, and anyone can verify them without an API key.
Eleidon provides a way for AI agents to prove they authored a message. It uses Ed25519 digital signatures, a public key registry, and inbox verification to create a chain of trust from agent to message.
X-Eleidon-Signature header to outgoing messages.Messages are normalized before hashing to ensure identical hashes across platforms. The canonical format is:
ELEIDON-CANONICAL-MESSAGE-V1
FROM:sender@example.com
TO:recipient1@example.com,recipient2@example.com
SUBJECT:Hello World
TIMESTAMP:2026-01-15T10:30:00Z
BODY-HASH:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
| Field | Rule |
|---|---|
FROM | Lowercase, trimmed |
TO | Each address lowercase + trimmed, then sorted alphabetically, comma-separated |
SUBJECT | Trimmed, internal whitespace collapsed to single spaces |
TIMESTAMP | ISO 8601 UTC (e.g. 2026-01-15T10:30:00Z) |
BODY-HASH | SHA-256 hex digest of the raw body text |
The final message hash is the SHA-256 hex digest of the entire canonical string.
import { Eleidon } from '@eleidon/sdk';
const client = new Eleidon({ apiKey: 'eld_...' });
// Generate a keypair
const { publicKey, privateKey } = Eleidon.generateKeypair();
// Register the agent
const agent = await client.agents.register({
public_key: publicKey,
claimed_inbox: 'agent@company.com',
display_name: 'Support Bot',
});
// After verifying inbox via email code...
await client.agents.verifyInbox({
agent_id: agent.agent_id,
challenge_code: 'AB3K7M',
});
// Sign a message
const result = await client.signEmail({
agentId: agent.agent_id,
privateKey,
from: 'agent@company.com',
to: ['user@example.com'],
subject: 'Your support ticket update',
timestamp: new Date().toISOString(),
body: 'Hello, your ticket has been resolved.',
});
// result.eleidon_header → attach to the outgoing message
from eleidon import Eleidon
from eleidon.crypto import generate_keypair, hash_message, sign_message
client = Eleidon(api_key="eld_...")
# Generate keypair
public_key, private_key = generate_keypair()
# Register agent
agent = client.agents.register(
public_key=public_key,
claimed_inbox="agent@company.com",
)
# Sign a message
msg_hash = hash_message(
from_addr="agent@company.com",
to=["user@example.com"],
subject="Your support ticket update",
timestamp="2026-01-15T10:30:00Z",
body="Hello, your ticket has been resolved.",
)
signature = sign_message(private_key, msg_hash)
result = client.sign.message(
agent_id=agent["agent_id"],
message_hash=msg_hash,
signature=signature,
)
# result["eleidon_header"] → attach to the outgoing message
The MCP server lets AI agents use Eleidon tools directly through the Model Context Protocol.
Add to your MCP client configuration (e.g. Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"eleidon": {
"command": "npx",
"args": ["@eleidon/mcp"],
"env": {
"ELEIDON_API_KEY": "eld_...",
"ELEIDON_PRIVATE_KEY": "your_private_key_hex"
}
}
}
}
| Tool | Description |
|---|---|
generate_keypair | Generate a new Ed25519 keypair |
register_agent | Register an agent with a public key + inbox |
verify_inbox | Submit a verification challenge code |
lookup_agent | Look up an agent by inbox email |
sign_email | Hash + sign + record a message in one step |
verify_email | Verify a message's Eleidon signature |
hash_message | Hash a message using the canonical format |
sign_hash | Sign a pre-computed message hash |
# Generate a keypair
npx @eleidon/cli keygen
# Register an agent
npx @eleidon/cli register --public-key <hex> --inbox agent@company.com
# Verify inbox
npx @eleidon/cli verify-inbox --agent-id <uuid> --code AB3K7M
# Sign a message
npx @eleidon/cli sign --agent-id <uuid> --private-key <hex> \
--from agent@company.com --to user@example.com \
--subject "Hello" --body "Message body" --timestamp 2026-01-15T10:30:00Z
# Verify a message
npx @eleidon/cli verify --sender agent@company.com \
--hash <message_hash> --header "v1:..."
# Look up an agent
npx @eleidon/cli lookup --inbox agent@company.com
Private keys are never sent to the API. All signing happens client-side. The API only receives the public key during registration and verifies signatures against it.
ELEIDON_PRIVATE_KEY from the environmentThe signature header attached to messages follows this format:
X-Eleidon-Signature: v1:<agent_id>:<signature_id>:<signature_hex>
| Part | Description |
|---|---|
v1 | Protocol version |
agent_id | UUID of the registered agent |
signature_id | UUID of the recorded signature |
signature_hex | 128-char hex Ed25519 signature |
| Result | Confidence | Meaning |
|---|---|---|
| verified | 1.0 | Valid signature from agent with verified inbox |
| verified | 0.7 | Valid signature but inbox not yet verified |
| unknown | 0.0 | No registered agent found |
| fraudulent | 1.0 | Signature invalid, sender mismatch, or header malformed |
There are two ways to look up an agent, and they behave differently:
| Endpoint | Returns | Use case |
|---|---|---|
GET /v1/agents/:id |
Any non-revoked agent, even if inbox is not yet verified | Check registration status, see if inbox verification is still pending |
GET /v1/agents?inbox=... |
Only agents with a verified claimed inbox | Look up the trusted agent for a given email address before verifying a message |
If you look up by inbox and get a 404, it means either no agent has claimed that inbox, the agent hasn't completed inbox verification yet, or the agent has been revoked.