Menu
Security Advanced 10 min read

Key Management

Secure generation, storage, and usage of cryptographic keys for Bitcoin, Lightning, and Nostr. Agent-specific best practices.

keys management storage generation

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 TypeFormatPurposeStorage
Seed phrase12/24 wordsMaster recoveryOffline only
Master private key256-bitDerivation rootNever exposed
Derived private key256-bitTransaction signingHot or cold
Extended public keyxpub/zpubAddress generationCan be shared

Lightning Keys

Key TypePurposeStorage
Node seedIdentity + channel keysHot (required)
Channel keysPer-channel secretsHot (auto-managed)
MacaroonAPI authenticationProtected

Nostr Keys

Key TypeFormatPurposeStorage
Private key32 bytes hexEvent signingHot (required)
nsecBech32 encodedHuman-readableSame as above
Public key32 bytes hexIdentityPublic
npubBech32 encodedHuman-readablePublic

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

MethodSecurityAccessibilityBest For
Hardware walletHighestLow (manual)Cold storage
HSMHighMediumEnterprise
Encrypted fileMediumHighHot wallet
Environment varLowHighDevelopment only
Memory onlyMediumSession onlyEphemeral 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

ProtocolRotation MethodDifficulty
BitcoinGenerate new addresses (same seed)Easy
LightningChannel close + new nodeHard
NostrNew npub, migrate followersMedium

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:

Immediate Actions

  1. Stop all operations: Prevent further signed actions
  2. Move funds: Transfer to new keys (Bitcoin, Lightning)
  3. Revoke access: Invalidate macaroons, API keys
  4. 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

PracticePriorityImplementation
Offline seed storageCriticalPaper/metal, no photos
Unique keys per protocolHighNever share between BTC/LN/Nostr
Minimal hot walletHighOnly keep operational amount
Encrypted storageHighStrong KDF, secure password
Regular backupsHighTested recovery procedure
Key derivationMediumHD wallets, deterministic
Rotation capabilityMediumPlan 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"
  ]
}