# Key Management

LDK provides a simple interface that takes a 32-byte seed for use as a BIP 32 extended key and derives keys from that. Check out the Rust docs (opens new window)

LDK Private Key Information is primarily provided through the chain::keysinterface::KeysInterface trait. It includes a few basic methods to get public and private key information, as well as a method to get an instance of a second trait which provides per-channel information - chain::keysinterface::ChannelKeys.

While a custom KeysInterface implementation allows simple flexibility to control derivation of private keys, ChannelKeys focuses on signing lightning transactions and is primarily useful if you want to store private key material on a separate device which enforces lightning protocol details.

A simple implementation of KeysInterface is provided in the form of chain::keysinterface::KeysManager, see its documentation for more details on its key derivation. It uses chain::keysinterface::InMemoryChannelKeys for channel signing, which is likely an appropriate signer for custom KeysInterface implementations as well.

A KeysManager can be constructed simply with only a 32-byte seed and some integers which ensure uniqueness across restarts (defined as starting_time_secs and starting_time_nanos).

  • Rust
  • Java
  • Kotlin
let mut random_32_bytes = [0; 32];
// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk.
let start_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let keys_interface_impl = lightning::chain::keysinterface::KeysManager::new(&random_32_bytes, start_time.as_secs(), start_time.subsec_nanos());

# Creating a Unified Wallet

LDK makes it simple to combine an on-chain and off-chain wallet in the same app. This means users don’t need to worry about storing 2 different recovery phrases. For apps containing a hierarchical deterministic wallet (or “HD Wallet”) we recommend using the entropy from a hardened child key derivation (opens new window) path for your LDK seed.

Using a BDK (opens new window)-based wallet the steps would be as follows:

  1. Generate a mnemonic/entropy
  2. Build an HD wallet from that. That's now your on-chain wallet, and you can derive any BIP-compliant on-chain wallet/path for it from there.
  3. Derive the private key at m/535h (or some other custom path). That's 32 bytes and is your starting entropy for your LDK wallet.
  • Rust
  • Java
  • Kotlin
// Use BDK to create and build the HD wallet
let mnemonic = Mnemonic::parse_in_normalized(
        "sock lyrics village put galaxy famous pass act ship second diagram pull"
let seed: [u8; 64] = mnemonic.to_seed_normalized("");
// Other supported networks include mainnet (Bitcoin), Regtest, Signet
let master_xprv = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap();
let secp = Secp256k1::new();
let xprv: ExtendedPrivKey = master_xprv.ckd_priv(&secp, ChildNumber::Hardened { index: 535 }).unwrap();
let ldk_seed: [u8; 32] = xprv.private_key.secret_bytes();

// Seed the LDK KeysManager with the private key at m/535h
let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let keys_manager = KeysManager::new(&ldk_seed, cur.as_secs(), cur.subsec_nanos());

Protection for on-chain wallet

An advantage to this approach is that the LDK entropy is contained within your initial mnemonic and a user only has one master private key to backup and secure. Another added benefit is that if your lightning keys were to be leaked we reduce the exposure to those funds and not the rest of the on-chain wallet.

# Spending On-Chain Funds

When a channel has been closed and some outputs on chain are spendable only by us, LDK provides a util::events::Event::SpendableOutputs event in return from ChannelMonitor::get_and_clear_pending_events(). It contains a list of chain::keysinterface::SpendableOutputDescriptor objects which describe the output and provide all necessary information to spend it.

If you're using KeysManager directly, a utility method is provided which can generate a signed transaction given a list of SpendableOutputDescriptor objects. KeysManager::spend_spendable_outputs can be called any time after receiving the SpendableOutputDescriptor objects to build a spending transaction, including delaying until sending funds to an external destination or opening a new channel. Note that if you open new channels directly with SpendableOutputDescriptor objects, you must ensure all closing/destination scripts provided to LDK are SegWit (either native or P2SH-wrapped).

If you are not using KeysManager for keys generation, you must re-derive the private keys yourself. Any BaseSign object must provide a unique id via the channel_keys_id function, whose value is provided back to you in the SpendableOutputs objects. A SpendableOutputDescriptor::StaticOutput element does not have this information as the output is sent to an output which used only KeysInterface data, not per-channel data.

Last Updated: 11/23/2022, 3:45:10 PM