Skip to content

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

bash
cargo add kanoniv-agent-auth
bash
npm install @kanoniv/agent-auth
bash
pip install kanoniv-agent-auth

2. 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.

rust
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);
}
typescript
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);
python
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.did

3. 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.

rust
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());
}
typescript
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));
python
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.

rust
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),
    }
}
typescript
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);
}
python
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.

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

const keypair = generateKeyPair();
const doc = didDocument(keypair.identity);

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

json
{
  "@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.

The identity and delegation layer for AI agents.