Skip to content

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

rust
use kanoniv_agent_auth::AgentKeyPair;

let keypair = AgentKeyPair::generate();
let identity = keypair.identity();

println!("DID: {}", identity.did);
// did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
typescript
import { generateKeyPair } from "@kanoniv/agent-auth";

const keypair = generateKeyPair();

console.log("DID:", keypair.identity.did);
// did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
python
from kanoniv_agent_auth import AgentKeyPair

keypair = AgentKeyPair.generate()
identity = keypair.identity()

print(f"DID: {identity.did}")
# did:agent:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

Generation 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:

rust
let secret: [u8; 32] = keypair.secret_bytes();
// Store `secret` securely - this is the private key
typescript
const secret: Uint8Array = keypair.secretKey;
// secret.length === 32
// Store securely
python
secret: bytes = keypair.secret_bytes()
# len(secret) == 32
# Store securely

WARNING

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:

rust
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);
typescript
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);
python
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:

rust
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:

rust
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);
typescript
import { identityFromBytes } from "@kanoniv/agent-auth";

const publicKeyBytes = new Uint8Array(receivedPublicKey); // 32 bytes
const identity = identityFromBytes(publicKeyBytes);

console.log("DID:", identity.did);
python
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:

rust
let doc = identity.did_document();
println!("{}", serde_json::to_string_pretty(&doc).unwrap());
typescript
import { didDocument } from "@kanoniv/agent-auth";

const doc = didDocument(keypair.identity);
console.log(JSON.stringify(doc, null, 2));
python
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:

bash
# Generate and save (your application writes this)
chmod 600 ~/.kanoniv/agent.key

Production

For production deployments, use your platform's secret management:

PlatformStorage
AWSSecrets Manager or SSM Parameter Store (SecureString)
GCPSecret Manager
AzureKey Vault
KubernetesSecrets (encrypted at rest with KMS)
DockerDocker secrets or mounted volumes with restricted access

Environment variables

For quick setup, pass the hex-encoded secret key as an environment variable:

bash
export KANONIV_AGENT_SECRET=4a2f8c...  # 64 hex chars = 32 bytes

Then reconstruct in code:

rust
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);
typescript
import { keyPairFromBytes, hexToBytes } from "@kanoniv/agent-auth";

const hexSecret = process.env.KANONIV_AGENT_SECRET!;
const keypair = keyPairFromBytes(hexToBytes(hexSecret));
python
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:

  1. Generate a new keypair
  2. Have the old keypair sign a delegation to the new identity (optional, for continuity)
  3. Update the stored secret bytes
  4. 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.

The identity and delegation layer for AI agents.