Skip to content

Commit 154b4a6

Browse files
committed
Add BDK-based KeysManager example
1 parent 586a417 commit 154b4a6

File tree

1 file changed

+100
-4
lines changed

1 file changed

+100
-4
lines changed

docs/key_management.md

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
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)
44

55
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-
6+
77
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.
88

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.
9+
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.
1010

1111
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`).
1212

@@ -53,9 +53,10 @@ val keys_manager = KeysManager.of(
5353
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.
5454

5555
Using a [BDK](https://bitcoindevkit.org/)-based wallet the steps would be as follows:
56-
1) Generate a mnemonic/entropy
56+
1) Generate a mnemonic/entropy source.
5757
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.
5858
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.
59+
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)).
5960

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

9495
DerivationPath ldkDerivationPath = new DerivationPath("m/535h");
9596
DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath);
96-
97+
9798
ByteArrayOutputStream bos = new ByteArrayOutputStream();
9899
ObjectOutputStream oos = new ObjectOutputStream(bos);
99100
oos.writeObject(ldkChild.secretBytes());
@@ -147,3 +148,98 @@ If you're using `KeysManager` directly, a utility method is provided which can g
147148
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).
148149

149150
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.
151+
152+
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.
153+
154+
For example, a wrapper based on BDK's [`Wallet`](https://docs.rs/bdk/*/bdk/wallet/struct.Wallet.html) could look like this:
155+
<CodeSwitcher :languages="{rust:'Rust'}">
156+
<template v-slot:rust>
157+
158+
```rust
159+
pub struct BDKKeysManager<D>
160+
where
161+
D: bdk::database::BatchDatabase,
162+
{
163+
inner: KeysManager,
164+
wallet: Arc<Mutex<bdk::Wallet<D>>>,
165+
}
166+
167+
impl<D> BDKKeysManager<D>
168+
where
169+
D: bdk::database::BatchDatabase,
170+
{
171+
pub fn new(
172+
seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, wallet: Arc<Mutex<bdk::Wallet<D>>>,
173+
) -> Self {
174+
let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos);
175+
Self { inner, wallet }
176+
}
177+
178+
// We drop all occurences of `SpendableOutputDescriptor::StaticOutput` (since they will be
179+
// spendable by the BDK wallet) and forward any other descriptors to
180+
// `KeysManager::spend_spendable_outputs`.
181+
pub fn spend_spendable_outputs<C: Signing>(
182+
&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>,
183+
change_destination_script: Script, feerate_sat_per_1000_weight: u32,
184+
secp_ctx: &Secp256k1<C>,
185+
) -> Result<Transaction, ()> {
186+
let only_non_static = &descriptors
187+
.iter()
188+
.filter(|desc| {
189+
if let SpendableOutputDescriptor::StaticOutput { .. } = desc {
190+
false
191+
} else {
192+
true
193+
}
194+
})
195+
.cloned()
196+
.collect::<Vec<_>>();
197+
self.inner.spend_spendable_outputs(
198+
only_non_static,
199+
outputs,
200+
change_destination_script,
201+
feerate_sat_per_1000_weight,
202+
secp_ctx,
203+
)
204+
}
205+
}
206+
207+
impl<D> KeysInterface for BDKKeysManager<D>
208+
where
209+
D: bdk::database::BatchDatabase,
210+
{
211+
type Signer = InMemorySigner;
212+
213+
// We return the destination and shutdown scripts derived by the BDK wallet.
214+
fn get_destination_script(&self) -> Script {
215+
let address = self.wallet.lock().unwrap()
216+
.get_address(bdk::wallet::AddressIndex::New)
217+
.expect("Failed to retrieve new address from wallet.");
218+
address.script_pubkey()
219+
}
220+
221+
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
222+
let address = self.wallet.lock().unwrap()
223+
.get_address(bdk::wallet::AddressIndex::New)
224+
.expect("Failed to retrieve new address from wallet.");
225+
226+
match address.payload {
227+
bitcoin::util::address::Payload::WitnessProgram { version, program } => {
228+
return ShutdownScript::new_witness_program(version, &program)
229+
.expect("Invalid shutdown script.");
230+
}
231+
_ => panic!("Tried to use a non-witness address. This must not ever happen."),
232+
}
233+
}
234+
235+
// We redirect all other trait method implementations to the `inner` `KeysManager`.
236+
fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
237+
self.inner.get_node_secret(recipient)
238+
}
239+
240+
// ... snip
241+
}
242+
```
243+
244+
</template>
245+
</CodeSwitcher>

0 commit comments

Comments
 (0)