Agent Identity Quickstart
Every Kanoniv agent starts with an Ed25519 keypair. From the keypair you derive a did:agent: decentralized identifier - a permanent, globally unique name for the agent. No registry, no server, no DNS lookup. The DID exists the moment the key is generated.
This guide covers: install, generate a keypair, get the DID, sign a message, verify it. Complete runnable code in Rust, TypeScript, and Python.
1. Install
cargo add kanoniv-agent-authnpm install @kanoniv/agent-authpip install kanoniv-agent-auth2. Generate a keypair and get the DID
A keypair is a 32-byte Ed25519 secret key and the corresponding public key. The DID is derived deterministically: did:agent:{hex(sha256(public_key)[0..16])} - 32 lowercase hex characters. The same public key always produces the same DID.
use kanoniv_agent_auth::AgentKeyPair;
fn main() {
let keypair = AgentKeyPair::generate();
let identity = keypair.identity();
println!("DID: {}", identity.did);
// Output: DID: did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
// The secret key is 32 bytes - store it securely
let secret = keypair.secret_bytes();
println!("Secret key length: {} bytes", secret.len());
// Reconstruct later from the secret key
let restored = AgentKeyPair::from_bytes(&secret);
assert_eq!(restored.identity().did, identity.did);
}import { generateKeyPair, keyPairFromBytes } from "@kanoniv/agent-auth";
const keypair = generateKeyPair();
const identity = keypair.identity;
console.log("DID:", identity.did);
// Output: DID: did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
// The secret key is 32 bytes - store it securely
const secret = keypair.secretKey;
console.log("Secret key length:", secret.length, "bytes");
// Reconstruct later from the secret key
const restored = keyPairFromBytes(secret);
console.log("Same DID:", restored.identity.did === identity.did);from kanoniv_agent_auth import AgentKeyPair
keypair = AgentKeyPair.generate()
identity = keypair.identity()
print(f"DID: {identity.did}")
# Output: DID: did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
# The secret key is 32 bytes - store it securely
secret = keypair.secret_bytes()
print(f"Secret key length: {len(secret)} bytes")
# Reconstruct later from the secret key
restored = AgentKeyPair.from_bytes(secret)
assert restored.identity().did == identity.did3. Sign a message
Signing creates a tamper-proof envelope around any JSON payload. The envelope includes a UUID nonce (replay protection), a timestamp, the signer's DID, and the Ed25519 signature over the canonical representation.
use kanoniv_agent_auth::{AgentKeyPair, SignedMessage};
fn main() {
let keypair = AgentKeyPair::generate();
let payload = serde_json::json!({
"action": "resolve",
"entity_id": "customer-123",
"source": "crm"
});
let signed = SignedMessage::sign(&keypair, payload).unwrap();
println!("Signer: {}", signed.signer_did);
println!("Nonce: {}", signed.nonce);
println!("Timestamp: {}", signed.timestamp);
println!("Signature: {}...", &signed.signature[..32]);
println!("Content hash: {}", signed.content_hash());
}import { generateKeyPair, signMessage, contentHash } from "@kanoniv/agent-auth";
const keypair = generateKeyPair();
const payload = {
action: "resolve",
entity_id: "customer-123",
source: "crm",
};
const signed = signMessage(keypair, payload);
console.log("Signer:", signed.signer_did);
console.log("Nonce:", signed.nonce);
console.log("Timestamp:", signed.timestamp);
console.log("Signature:", signed.signature.slice(0, 32) + "...");
console.log("Content hash:", contentHash(signed));import json
from kanoniv_agent_auth import AgentKeyPair
keypair = AgentKeyPair.generate()
payload = json.dumps({
"action": "resolve",
"entity_id": "customer-123",
"source": "crm"
})
signed = keypair.sign(payload)
print(f"Signer: {signed.signer_did}")
print(f"Nonce: {signed.nonce}")
print(f"Timestamp: {signed.timestamp}")
print(f"Signature: {signed.signature[:32]}...")
print(f"Content hash: {signed.content_hash()}")4. Verify the signature
Verification checks that the signature is valid for the claimed signer. It recomputes the canonical bytes from the envelope fields and verifies the Ed25519 signature against the signer's public key.
use kanoniv_agent_auth::{AgentKeyPair, SignedMessage};
fn main() {
let keypair = AgentKeyPair::generate();
let identity = keypair.identity();
let payload = serde_json::json!({"action": "resolve"});
let signed = SignedMessage::sign(&keypair, payload).unwrap();
// Verify - succeeds
match signed.verify(&identity) {
Ok(()) => println!("Signature valid"),
Err(e) => println!("Verification failed: {}", e),
}
// Tamper with the payload - verification fails
let mut tampered = signed.clone();
tampered.payload = serde_json::json!({"action": "merge"});
match tampered.verify(&identity) {
Ok(()) => println!("This should not happen"),
Err(e) => println!("Tamper detected: {}", e),
}
}import { generateKeyPair, signMessage, verifyMessage } from "@kanoniv/agent-auth";
const keypair = generateKeyPair();
const identity = keypair.identity;
const payload = { action: "resolve" };
const signed = signMessage(keypair, payload);
// Verify - succeeds
try {
verifyMessage(signed, identity);
console.log("Signature valid");
} catch (e) {
console.log("Verification failed:", e);
}
// Tamper with the payload - verification fails
const tampered = { ...signed, payload: { action: "merge" } };
try {
verifyMessage(tampered, identity);
console.log("This should not happen");
} catch (e) {
console.log("Tamper detected:", (e as Error).message);
}import json
from kanoniv_agent_auth import AgentKeyPair
keypair = AgentKeyPair.generate()
identity = keypair.identity()
payload = json.dumps({"action": "resolve"})
signed = keypair.sign(payload)
# Verify - succeeds
try:
signed.verify(identity)
print("Signature valid")
except ValueError as e:
print(f"Verification failed: {e}")
# Verify with a different agent's identity - fails
other_keypair = AgentKeyPair.generate()
other_identity = other_keypair.identity()
try:
signed.verify(other_identity)
print("This should not happen")
except ValueError as e:
print(f"Wrong signer detected: {e}")5. Get the DID Document
Every agent identity can produce a W3C-compatible DID Document. This is useful for publishing the agent's public key and service endpoints.
use kanoniv_agent_auth::AgentKeyPair;
fn main() {
let keypair = AgentKeyPair::generate();
let identity = keypair.identity();
let doc = identity.did_document();
println!("{}", serde_json::to_string_pretty(&doc).unwrap());
}import { generateKeyPair, didDocument } from "@kanoniv/agent-auth";
const keypair = generateKeyPair();
const doc = didDocument(keypair.identity);
console.log(JSON.stringify(doc, null, 2));import json
from kanoniv_agent_auth import AgentKeyPair
keypair = AgentKeyPair.generate()
identity = keypair.identity()
doc = identity.did_document()
print(json.dumps(json.loads(doc), indent=2))The output looks like this:
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"verificationMethod": [{
"id": "did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6#key-1",
"type": "Ed25519VerificationKey2020",
"controller": "did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"publicKeyMultibase": "z6Mkf5rGMoatrSj1f..."
}],
"authentication": ["did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6#key-1"],
"assertionMethod": ["did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6#key-1"]
}What you now have
- A keypair - 32-byte Ed25519 secret key that you store securely, and a public key you share freely.
- A DID -
did:agent:{32 hex chars}- a permanent, globally unique identifier for your agent. No registration needed. - Signed messages - Tamper-proof envelopes with nonce-based replay protection and content hashes for chaining.
- A DID Document - W3C-compatible public key publication.
All three language implementations produce byte-identical DIDs, signatures, and content hashes for the same inputs. A message signed in Rust can be verified in Python or TypeScript.
Next steps
- Delegation - Grant another agent a subset of your authority with constraints.
- MCP Server Auth - Add proof verification to your MCP server in 5 lines.
- Specification - The formal
did:agent:specification with all the details.
