Key Management
Cryptographic keys are the foundation of agent sovereignty. Compromise of keys means total loss of funds and identity. This guide covers secure key generation, storage, and operational use.
Key Types by Protocol
Bitcoin Keys
| Key Type | Format | Purpose | Storage |
|---|
| Seed phrase | 12/24 words | Master recovery | Offline only |
| Master private key | 256-bit | Derivation root | Never exposed |
| Derived private key | 256-bit | Transaction signing | Hot or cold |
| Extended public key | xpub/zpub | Address generation | Can be shared |
Lightning Keys
| Key Type | Purpose | Storage |
|---|
| Node seed | Identity + channel keys | Hot (required) |
| Channel keys | Per-channel secrets | Hot (auto-managed) |
| Macaroon | API authentication | Protected |
Nostr Keys
| Key Type | Format | Purpose | Storage |
|---|
| Private key | 32 bytes hex | Event signing | Hot (required) |
| nsec | Bech32 encoded | Human-readable | Same as above |
| Public key | 32 bytes hex | Identity | Public |
| npub | Bech32 encoded | Human-readable | Public |
Key Generation
Secure Entropy Sources
import os
import hashlib
import secrets
def generate_secure_entropy(bits: int = 256) -> bytes:
"""
Generate cryptographically secure entropy.
Uses OS random source (urandom on Unix, CryptGenRandom on Windows).
"""
if bits not in [128, 160, 192, 224, 256]:
raise ValueError("Invalid entropy bits")
return secrets.token_bytes(bits // 8)
def generate_seed_phrase(strength: int = 256) -> list[str]:
"""
Generate BIP-39 mnemonic seed phrase.
Args:
strength: 128 (12 words), 256 (24 words)
"""
from bip_utils import Bip39MnemonicGenerator
mnemonic = Bip39MnemonicGenerator().FromEntropyBits(strength)
return mnemonic.ToStr().split()
def generate_nostr_keypair() -> dict:
"""Generate Nostr key pair."""
from secp256k1 import PrivateKey
sk = PrivateKey()
pk = sk.pubkey
return {
"private_key_hex": sk.serialize(),
"public_key_hex": pk.serialize().hex()[2:], # Remove 02/03 prefix
"nsec": bech32_encode("nsec", sk.serialize()),
"npub": bech32_encode("npub", pk.serialize()[1:]) # x-only
}
Never Do This
# WRONG: Predictable entropy
import random
random.seed(time.time()) # Predictable!
key = bytes([random.randint(0, 255) for _ in range(32)])
# WRONG: Weak entropy mixing
key = hashlib.sha256(b"my secret password").digest()
# WRONG: Reusing keys across protocols
nostr_key = bitcoin_private_key # Links identities!
Key Storage
Storage Options
| Method | Security | Accessibility | Best For |
|---|
| Hardware wallet | Highest | Low (manual) | Cold storage |
| HSM | High | Medium | Enterprise |
| Encrypted file | Medium | High | Hot wallet |
| Environment var | Low | High | Development only |
| Memory only | Medium | Session only | Ephemeral ops |
Encrypted File Storage
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import os
class SecureKeyStore:
"""Encrypted key storage with password derivation."""
def __init__(self, keyfile_path: str):
self.keyfile_path = keyfile_path
def _derive_key(self, password: str, salt: bytes) -> bytes:
"""Derive encryption key from password."""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=600_000, # OWASP recommended minimum
)
return base64.urlsafe_b64encode(kdf.derive(password.encode()))
def store_key(self, key_name: str, key_data: bytes, password: str):
"""Store encrypted key."""
salt = os.urandom(16)
fernet_key = self._derive_key(password, salt)
f = Fernet(fernet_key)
encrypted = f.encrypt(key_data)
# Store salt + encrypted data
with open(f"{self.keyfile_path}/{key_name}.key", "wb") as file:
file.write(salt + encrypted)
def load_key(self, key_name: str, password: str) -> bytes:
"""Load and decrypt key."""
with open(f"{self.keyfile_path}/{key_name}.key", "rb") as file:
data = file.read()
salt = data[:16]
encrypted = data[16:]
fernet_key = self._derive_key(password, salt)
f = Fernet(fernet_key)
return f.decrypt(encrypted)
Hardware Security Module (HSM) Integration
class HSMSigner:
"""Sign transactions using HSM."""
def __init__(self, hsm_config: dict):
self.hsm = connect_to_hsm(hsm_config)
self.key_id = hsm_config["key_id"]
async def sign_transaction(self, tx_hash: bytes) -> bytes:
"""
Sign transaction hash using HSM.
The private key never leaves the HSM.
"""
signature = await self.hsm.sign(
key_id=self.key_id,
data=tx_hash,
algorithm="ECDSA_SHA256"
)
return signature
async def sign_nostr_event(self, event_hash: bytes) -> bytes:
"""Sign Nostr event using HSM."""
# Nostr uses Schnorr signatures
signature = await self.hsm.sign(
key_id=self.key_id,
data=event_hash,
algorithm="SCHNORR_BIP340"
)
return signature
Key Derivation
HD Wallet Structure
Master Seed
│
├── m/84'/0'/0' (Bitcoin SegWit Account 0)
│ ├── m/84'/0'/0'/0 (External chain)
│ │ ├── m/84'/0'/0'/0/0 (Address 0)
│ │ ├── m/84'/0'/0'/0/1 (Address 1)
│ │ └── ...
│ └── m/84'/0'/0'/1 (Internal/change chain)
│ ├── m/84'/0'/0'/1/0 (Change 0)
│ └── ...
│
├── m/84'/0'/1' (Bitcoin SegWit Account 1)
│ └── (Separate operations)
│
└── m/44'/1237'/0' (Nostr, NIP-06)
├── m/44'/1237'/0'/0/0 (Identity 0)
└── m/44'/1237'/0'/0/1 (Identity 1)
Derivation Code
from bip_utils import (
Bip39SeedGenerator,
Bip84, Bip84Coins,
Bip44, Bip44Coins
)
class HDKeyManager:
"""Hierarchical Deterministic key management."""
def __init__(self, mnemonic: str, passphrase: str = ""):
seed = Bip39SeedGenerator(mnemonic).Generate(passphrase)
self.btc_master = Bip84.FromSeed(seed, Bip84Coins.BITCOIN)
def get_bitcoin_address(self, account: int, index: int) -> dict:
"""Derive Bitcoin address."""
account_key = self.btc_master.Purpose().Coin().Account(account)
address_key = account_key.Change(False).AddressIndex(index)
return {
"address": address_key.PublicKey().ToAddress(),
"path": f"m/84'/0'/{account}'/0/{index}",
# Never expose private key unless signing
}
def sign_with_key(
self,
account: int,
index: int,
message_hash: bytes
) -> bytes:
"""Sign with derived key."""
account_key = self.btc_master.Purpose().Coin().Account(account)
signing_key = account_key.Change(False).AddressIndex(index)
private_key = signing_key.PrivateKey().Raw().ToBytes()
# Sign message_hash with private_key
# Immediately clear private_key from memory after use
return signature
Key Rotation
Rotation Strategies
| Protocol | Rotation Method | Difficulty |
|---|
| Bitcoin | Generate new addresses (same seed) | Easy |
| Lightning | Channel close + new node | Hard |
| Nostr | New npub, migrate followers | Medium |
Nostr Key Rotation
async def rotate_nostr_identity(
old_keypair: dict,
new_keypair: dict,
relays: list[str]
) -> dict:
"""
Rotate Nostr identity with migration announcement.
1. Create new keypair
2. Post migration notice from old key
3. Post introduction from new key
4. Optionally maintain old key for verification
"""
# Migration notice from old identity
migration_event = create_event(
kind=1,
content=f"Rotating to new key: {new_keypair['npub']}",
private_key=old_keypair["nsec"]
)
await publish_to_relays(migration_event, relays)
# Introduction from new identity
intro_event = create_event(
kind=0, # Profile metadata
content=json.dumps({
"name": "My Agent",
"about": "Migrated from " + old_keypair["npub"][:20]
}),
private_key=new_keypair["nsec"]
)
await publish_to_relays(intro_event, relays)
return {
"old_npub": old_keypair["npub"],
"new_npub": new_keypair["npub"],
"migration_event": migration_event["id"]
}
Key Compromise Response
If you suspect key compromise:
- Stop all operations: Prevent further signed actions
- Move funds: Transfer to new keys (Bitcoin, Lightning)
- Revoke access: Invalidate macaroons, API keys
- Notify stakeholders: Alert counterparties if relevant
Recovery Procedure
async def emergency_key_compromise_response(
compromised_key_type: str
) -> dict:
"""Emergency response to key compromise."""
actions_taken = []
if compromised_key_type == "bitcoin":
# Generate new wallet from new seed
new_seed = generate_seed_phrase()
new_address = derive_address(new_seed, 0, 0)
# Sweep all UTXOs to new wallet
utxos = await get_all_utxos()
sweep_tx = create_sweep_transaction(utxos, new_address)
await broadcast(sweep_tx)
actions_taken.append("swept_funds")
elif compromised_key_type == "lightning":
# Force close all channels
channels = await lightning.list_channels()
for channel in channels:
await lightning.force_close(channel["channel_id"])
actions_taken.append("force_closed_channels")
elif compromised_key_type == "nostr":
# Post compromise notice
await nostr.post(
"KEY COMPROMISED. This identity is no longer secure. "
"New identity will be announced separately."
)
actions_taken.append("posted_warning")
return {
"compromised_key": compromised_key_type,
"actions": actions_taken,
"timestamp": datetime.utcnow().isoformat()
}
Best Practices Summary
| Practice | Priority | Implementation |
|---|
| Offline seed storage | Critical | Paper/metal, no photos |
| Unique keys per protocol | High | Never share between BTC/LN/Nostr |
| Minimal hot wallet | High | Only keep operational amount |
| Encrypted storage | High | Strong KDF, secure password |
| Regular backups | High | Tested recovery procedure |
| Key derivation | Medium | HD wallets, deterministic |
| Rotation capability | Medium | Plan for emergency rotation |
Machine-Readable Summary
{
"topic": "key-management",
"audience": "ai-agents",
"key_types": {
"bitcoin": ["seed", "master", "derived", "xpub"],
"lightning": ["node_seed", "channel_keys", "macaroon"],
"nostr": ["private_key", "nsec", "npub"]
},
"storage_methods": [
"hardware_wallet",
"hsm",
"encrypted_file",
"memory_only"
],
"critical_practices": [
"offline_seed_storage",
"unique_keys_per_protocol",
"minimal_hot_balance",
"tested_backup_recovery"
]
}