Skip to content

Commit

Permalink
Add BDK-based KeysManager example
Browse files Browse the repository at this point in the history
  • Loading branch information
tnull committed Dec 19, 2022
1 parent 586a417 commit 154b4a6
Showing 1 changed file with 100 additions and 4 deletions.
104 changes: 100 additions & 4 deletions docs/key_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
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](https://docs.rs/lightning/*/lightning/chain/keysinterface/struct.KeysManager.html)

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 simple default 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`).

Expand Down Expand Up @@ -53,9 +53,10 @@ val keys_manager = KeysManager.of(
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](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch05.asciidoc#hardened-child-key-derivation) path for your LDK seed.

Using a [BDK](https://bitcoindevkit.org/)-based wallet the steps would be as follows:
1) Generate a mnemonic/entropy
1) Generate a mnemonic/entropy source.
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.
4) Optional: use a custom `KeysInterface` implementation to have the BDK wallet provide the destination and shutdown scripts (see [Spending On-Chain Funds](#spending-on-chain-funds)).

<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}">
<template v-slot:rust>
Expand Down Expand Up @@ -93,7 +94,7 @@ DescriptorSecretKey bip32RootKey = new DescriptorSecretKey(Network.TESTNET, mnem

DerivationPath ldkDerivationPath = new DerivationPath("m/535h");
DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(ldkChild.secretBytes());
Expand Down Expand Up @@ -147,3 +148,98 @@ If you're using `KeysManager` directly, a utility method is provided which can g
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.

In order to make the outputs from channel closing spendable by a third-party wallet, a middleground between using the default `KeysManager` and implementing a entirely custom `KeysInterface` could be to implement a wrapper around `KeysManager`. Such a wrapper would need to override the respective methods returning the destination and shutdown scripts while simply dropping any instances of `SpendableOutputDescriptor::StaticOutput`, as these then could be spent by the third-party wallet from which the scripts had been derived.

For example, a wrapper based on BDK's [`Wallet`](https://docs.rs/bdk/*/bdk/wallet/struct.Wallet.html) could look like this:
<CodeSwitcher :languages="{rust:'Rust'}">
<template v-slot:rust>

```rust
pub struct BDKKeysManager<D>
where
D: bdk::database::BatchDatabase,
{
inner: KeysManager,
wallet: Arc<Mutex<bdk::Wallet<D>>>,
}

impl<D> BDKKeysManager<D>
where
D: bdk::database::BatchDatabase,
{
pub fn new(
seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, wallet: Arc<Mutex<bdk::Wallet<D>>>,
) -> Self {
let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos);
Self { inner, wallet }
}

// We drop all occurences of `SpendableOutputDescriptor::StaticOutput` (since they will be
// spendable by the BDK wallet) and forward any other descriptors to
// `KeysManager::spend_spendable_outputs`.
pub fn spend_spendable_outputs<C: Signing>(
&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>,
change_destination_script: Script, feerate_sat_per_1000_weight: u32,
secp_ctx: &Secp256k1<C>,
) -> Result<Transaction, ()> {
let only_non_static = &descriptors
.iter()
.filter(|desc| {
if let SpendableOutputDescriptor::StaticOutput { .. } = desc {
false
} else {
true
}
})
.cloned()
.collect::<Vec<_>>();
self.inner.spend_spendable_outputs(
only_non_static,
outputs,
change_destination_script,
feerate_sat_per_1000_weight,
secp_ctx,
)
}
}

impl<D> KeysInterface for BDKKeysManager<D>
where
D: bdk::database::BatchDatabase,
{
type Signer = InMemorySigner;

// We return the destination and shutdown scripts derived by the BDK wallet.
fn get_destination_script(&self) -> Script {
let address = self.wallet.lock().unwrap()
.get_address(bdk::wallet::AddressIndex::New)
.expect("Failed to retrieve new address from wallet.");
address.script_pubkey()
}

fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
let address = self.wallet.lock().unwrap()
.get_address(bdk::wallet::AddressIndex::New)
.expect("Failed to retrieve new address from wallet.");

match address.payload {
bitcoin::util::address::Payload::WitnessProgram { version, program } => {
return ShutdownScript::new_witness_program(version, &program)
.expect("Invalid shutdown script.");
}
_ => panic!("Tried to use a non-witness address. This must not ever happen."),
}
}

// We redirect all other trait method implementations to the `inner` `KeysManager`.
fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
self.inner.get_node_secret(recipient)
}

// ... snip
}
```

</template>
</CodeSwitcher>

0 comments on commit 154b4a6

Please sign in to comment.