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.