Keypairs
Every agent identity starts with an Ed25519 keypair: a 32-byte secret key and a 32-byte public key. The secret key signs messages. The public key verifies them. The DID is derived from the public key.
Generate a keypair
use kanoniv_agent_auth::AgentKeyPair;
let keypair = AgentKeyPair::generate();
let identity = keypair.identity();
println!("DID: {}", identity.did);
// did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6import { generateKeyPair } from "@kanoniv/agent-auth";
const keypair = generateKeyPair();
console.log("DID:", keypair.identity.did);
// did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6from kanoniv_agent_auth import AgentKeyPair
keypair = AgentKeyPair.generate()
identity = keypair.identity()
print(f"DID: {identity.did}")
# did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6Generation uses the OS cryptographic random number generator (OsRng in Rust, crypto.getRandomValues in browsers, /dev/urandom on Linux). The keypair includes a creation timestamp in RFC 3339 format.
Export secret bytes
To persist a keypair, export the 32-byte secret key:
let secret: [u8; 32] = keypair.secret_bytes();
// Store `secret` securely - this is the private keyconst secret: Uint8Array = keypair.secretKey;
// secret.length === 32
// Store securelysecret: bytes = keypair.secret_bytes()
# len(secret) == 32
# Store securelyWARNING
The secret key is the agent's identity. Anyone with these 32 bytes can impersonate the agent - sign messages, create delegations, and act on its behalf. Treat it like a private SSH key.
Reconstruct from bytes
Reconstruct the full keypair from persisted secret bytes. The public key and DID are re-derived deterministically:
use kanoniv_agent_auth::AgentKeyPair;
// Load from storage
let secret: [u8; 32] = load_from_keyfile();
let keypair = AgentKeyPair::from_bytes(&secret);
// DID is identical to the original
assert_eq!(keypair.identity().did, original_did);import { keyPairFromBytes } from "@kanoniv/agent-auth";
// Load from storage
const secret = new Uint8Array(loadFromKeyfile());
const keypair = keyPairFromBytes(secret);
// DID is identical to the original
console.log(keypair.identity.did);from kanoniv_agent_auth import AgentKeyPair
# Load from storage
secret = load_from_keyfile() # bytes, length 32
keypair = AgentKeyPair.from_bytes(secret)
# DID is identical to the original
print(keypair.identity().did)The Rust API also supports preserving the original creation timestamp:
use chrono::Utc;
let keypair = AgentKeyPair::from_bytes_with_created_at(&secret, original_created_at);Derive identity from public key
If you only have the public key (not the secret), you can still derive the identity for verification:
use kanoniv_agent_auth::AgentIdentity;
let public_key_bytes: &[u8] = &received_public_key; // 32 bytes
let identity = AgentIdentity::from_bytes(public_key_bytes).unwrap();
println!("DID: {}", identity.did);import { identityFromBytes } from "@kanoniv/agent-auth";
const publicKeyBytes = new Uint8Array(receivedPublicKey); // 32 bytes
const identity = identityFromBytes(publicKeyBytes);
console.log("DID:", identity.did);from kanoniv_agent_auth import AgentIdentity
public_key_bytes = received_public_key # bytes, length 32
identity = AgentIdentity.from_bytes(public_key_bytes)
print(f"DID: {identity.did}")This is how verification works: receive a public key, derive the identity, confirm the DID matches the claimed signer, then use the public key to check the signature.
DID Document generation
From any identity (with or without the secret key), you can generate a W3C DID Document:
let doc = identity.did_document();
println!("{}", serde_json::to_string_pretty(&doc).unwrap());import { didDocument } from "@kanoniv/agent-auth";
const doc = didDocument(keypair.identity);
console.log(JSON.stringify(doc, null, 2));doc_json = identity.did_document()
print(doc_json)See The did:agent: Method for the full document structure and service endpoints.
Key storage best practices
Development and testing
For local development, store the secret bytes in a file with restricted permissions:
# Generate and save (your application writes this)
chmod 600 ~/.kanoniv/agent.keyProduction
For production deployments, use your platform's secret management:
| Platform | Storage |
|---|---|
| AWS | Secrets Manager or SSM Parameter Store (SecureString) |
| GCP | Secret Manager |
| Azure | Key Vault |
| Kubernetes | Secrets (encrypted at rest with KMS) |
| Docker | Docker secrets or mounted volumes with restricted access |
Environment variables
For quick setup, pass the hex-encoded secret key as an environment variable:
export KANONIV_AGENT_SECRET=4a2f8c... # 64 hex chars = 32 bytesThen reconstruct in code:
let hex_secret = std::env::var("KANONIV_AGENT_SECRET").unwrap();
let secret_bytes: Vec<u8> = hex::decode(&hex_secret).unwrap();
let secret: [u8; 32] = secret_bytes.try_into().unwrap();
let keypair = AgentKeyPair::from_bytes(&secret);import { keyPairFromBytes, hexToBytes } from "@kanoniv/agent-auth";
const hexSecret = process.env.KANONIV_AGENT_SECRET!;
const keypair = keyPairFromBytes(hexToBytes(hexSecret));import os
from kanoniv_agent_auth import AgentKeyPair
hex_secret = os.environ["KANONIV_AGENT_SECRET"]
keypair = AgentKeyPair.from_bytes(bytes.fromhex(hex_secret))TIP
Never log or print secret key bytes. If you need to identify a keypair in logs, use the DID - it is derived from the public key and safe to share.
Key rotation
To rotate an agent's identity:
- Generate a new keypair
- Have the old keypair sign a delegation to the new identity (optional, for continuity)
- Update the stored secret bytes
- Re-register the agent with the new DID
There is no revocation registry for did:agent: DIDs. If a secret key is compromised, stop trusting the old DID and switch to the new one. Delegation revocation is handled separately at the application layer - see the Delegation docs.
