Skip to content

Commit 878f196

Browse files
committed
Add BDK-based KeysManager example
1 parent 586a417 commit 878f196

File tree

1 file changed

+129
-10
lines changed

1 file changed

+129
-10
lines changed

docs/key_management.md

+129-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
# Key Management
22

3-
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)
3+
LDK provides a simple default `KeysManager` implementation 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).
44

5-
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`.
6-
7-
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.
5+
However, LDK also allows to customize the way key material and entropy are sourced through custom implementations of the `NodeSigner`, `SignerProvider`, and `EntropySource` traits located in `chain::keysinterface`. These traits include basic methods to provide public and private key material, as well as pseudorandom numbers.
86

9-
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.
107

11-
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`).
8+
A `KeysManager` can be constructed simply with only a 32-byte seed and some random integers which ensure uniqueness across restarts (defined as `starting_time_secs` and `starting_time_nanos`):
129

1310
<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}">
1411
<template v-slot:rust>
@@ -50,12 +47,14 @@ val keys_manager = KeysManager.of(
5047
</CodeSwitcher>
5148

5249
# Creating a Unified Wallet
53-
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.
50+
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 two 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.
5451

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

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

9493
DerivationPath ldkDerivationPath = new DerivationPath("m/535h");
9594
DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath);
96-
95+
9796
ByteArrayOutputStream bos = new ByteArrayOutputStream();
9897
ObjectOutputStream oos = new ObjectOutputStream(bos);
9998
oos.writeObject(ldkChild.secretBytes());
@@ -146,4 +145,124 @@ When a channel has been closed and some outputs on chain are spendable only by u
146145
If you're using `KeysManager` directly, a utility method is provided which can generate a signed transaction given a list of `
147146
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).
148147

149-
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.
148+
If you are not using `KeysManager` for keys generation, you must re-derive the private keys yourself. Any `ChannelSigner` 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.
149+
150+
In order to make the outputs from channel closing spendable by a third-party wallet, a middleground between using the default `KeysManager` and an entirely custom implementation of `SignerProvider`/`NodeSigner`/`EntropySource` 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.
151+
152+
For example, a wrapper based on BDK's [`Wallet`](https://docs.rs/bdk/*/bdk/wallet/struct.Wallet.html) could look like this:
153+
<CodeSwitcher :languages="{rust:'Rust'}">
154+
<template v-slot:rust>
155+
156+
```rust
157+
pub struct BDKKeysManager<D>
158+
where
159+
D: bdk::database::BatchDatabase,
160+
{
161+
inner: KeysManager,
162+
wallet: Arc<Mutex<bdk::Wallet<D>>>,
163+
}
164+
165+
impl<D> BDKKeysManager<D>
166+
where
167+
D: bdk::database::BatchDatabase,
168+
{
169+
pub fn new(
170+
seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, wallet: Arc<Mutex<bdk::Wallet<D>>>,
171+
) -> Self {
172+
let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos);
173+
Self { inner, wallet }
174+
}
175+
176+
// We drop all occurences of `SpendableOutputDescriptor::StaticOutput` (since they will be
177+
// spendable by the BDK wallet) and forward any other descriptors to
178+
// `KeysManager::spend_spendable_outputs`.
179+
pub fn spend_spendable_outputs<C: Signing>(
180+
&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>,
181+
change_destination_script: Script, feerate_sat_per_1000_weight: u32,
182+
secp_ctx: &Secp256k1<C>,
183+
) -> Result<Transaction, ()> {
184+
let only_non_static = &descriptors
185+
.iter()
186+
.filter(|desc| {
187+
if let SpendableOutputDescriptor::StaticOutput { .. } = desc {
188+
false
189+
} else {
190+
true
191+
}
192+
})
193+
.copied()
194+
.collect::<Vec<_>>();
195+
self.inner.spend_spendable_outputs(
196+
only_non_static,
197+
outputs,
198+
change_destination_script,
199+
feerate_sat_per_1000_weight,
200+
secp_ctx,
201+
)
202+
}
203+
}
204+
205+
impl<D> SignerProvider for BDKKeysManager<D>
206+
where
207+
D: bdk::database::BatchDatabase,
208+
{
209+
type Signer = InMemorySigner;
210+
211+
// We return the destination and shutdown scripts derived by the BDK wallet.
212+
fn get_destination_script(&self) -> Script {
213+
let address = self.wallet.lock().unwrap()
214+
.get_address(bdk::wallet::AddressIndex::New)
215+
.expect("Failed to retrieve new address from wallet.");
216+
address.script_pubkey()
217+
}
218+
219+
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
220+
let address =
221+
self.wallet.lock().unwrap()
222+
.get_address(bdk::wallet::AddressIndex::New)
223+
.expect("Failed to retrieve new address from wallet.");
224+
match address.payload {
225+
bitcoin::util::address::Payload::WitnessProgram { version, program } => {
226+
return ShutdownScript::new_witness_program(version, &program)
227+
.expect("Invalid shutdown script.");
228+
}
229+
_ => panic!("Tried to use a non-witness address. This must not ever happen."),
230+
}
231+
}
232+
233+
// ... and redirect all other trait method implementations to the `inner` `KeysManager`.
234+
fn generate_channel_keys_id(
235+
&self, inbound: bool, channel_value_satoshis: u64, user_channel_id: u128,
236+
) -> [u8; 32] {
237+
self.inner.generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id)
238+
}
239+
240+
fn derive_channel_signer(
241+
&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32],
242+
) -> Self::Signer {
243+
self.inner.derive_channel_signer(channel_value_satoshis, channel_keys_id)
244+
}
245+
246+
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
247+
self.inner.read_chan_signer(reader)
248+
}
249+
}
250+
251+
impl<D> NodeSigner for BDKKeysManager<D>
252+
where
253+
D: bdk::database::BatchDatabase,
254+
{
255+
// ... snip
256+
}
257+
258+
impl<D> EntropySource for BDKKeysManager<D>
259+
where
260+
D: bdk::database::BatchDatabase,
261+
{
262+
// ... snip
263+
}
264+
265+
```
266+
267+
</template>
268+
</CodeSwitcher>

0 commit comments

Comments
 (0)