Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cbfbd6e

Browse files
authored
Add BDK-based KeysManager example (#183)
1 parent fb21a28 commit cbfbd6e

File tree

1 file changed

+137
-10
lines changed

1 file changed

+137
-10
lines changed

‎docs/key_management.md‎

Lines changed: 137 additions & 10 deletions
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,132 @@ 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+
//
180+
// Note you should set `locktime` to the current block height to mitigate fee sniping.
181+
// See https://bitcoinops.org/en/topics/fee-sniping/ for more information.
182+
pub fn spend_spendable_outputs<C: Signing>(
183+
&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>,
184+
change_destination_script: Script, feerate_sat_per_1000_weight: u32,
185+
locktime: Option<PackedLockTime>, secp_ctx: &Secp256k1<C>,
186+
) -> Result<Transaction, ()> {
187+
let only_non_static = &descriptors
188+
.iter()
189+
.filter(|desc| {
190+
if let SpendableOutputDescriptor::StaticOutput { .. } = desc {
191+
false
192+
} else {
193+
true
194+
}
195+
})
196+
.copied()
197+
.collect::<Vec<_>>();
198+
self.inner.spend_spendable_outputs(
199+
only_non_static,
200+
outputs,
201+
change_destination_script,
202+
feerate_sat_per_1000_weight,
203+
locktime,
204+
secp_ctx,
205+
)
206+
}
207+
}
208+
209+
impl<D> SignerProvider for BDKKeysManager<D>
210+
where
211+
D: bdk::database::BatchDatabase,
212+
{
213+
type Signer = InMemorySigner;
214+
215+
// We return the destination and shutdown scripts derived by the BDK wallet.
216+
fn get_destination_script(&self) -> Result<Script, ()> {
217+
let address = self.wallet.lock().unwrap()
218+
.get_address(bdk::wallet::AddressIndex::New)
219+
.map_err(|e| {
220+
eprintln!("Failed to retrieve new address from wallet: {:?}", e);
221+
})?;
222+
Ok(address.script_pubkey())
223+
}
224+
225+
fn get_shutdown_scriptpubkey(&self) -> Result<ShutdownScript, ()> {
226+
let address = self.wallet.lock().unwrap()
227+
.get_address(bdk::wallet::AddressIndex::New)
228+
.map_err(|e| {
229+
eprintln!("Failed to retrieve new address from wallet: {:?}", e);
230+
})?;
231+
match address.payload {
232+
bitcoin::util::address::Payload::WitnessProgram { version, program } => {
233+
ShutdownScript::new_witness_program(version, &program).map_err(|e| {
234+
eprintln!("Invalid shutdown script: {:?}", e);
235+
})
236+
}
237+
_ => panic!("Tried to use a non-witness address. This must not ever happen."),
238+
}
239+
}
240+
241+
// ... and redirect all other trait method implementations to the `inner` `KeysManager`.
242+
fn generate_channel_keys_id(
243+
&self, inbound: bool, channel_value_satoshis: u64, user_channel_id: u128,
244+
) -> [u8; 32] {
245+
self.inner.generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id)
246+
}
247+
248+
fn derive_channel_signer(
249+
&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32],
250+
) -> Self::Signer {
251+
self.inner.derive_channel_signer(channel_value_satoshis, channel_keys_id)
252+
}
253+
254+
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
255+
self.inner.read_chan_signer(reader)
256+
}
257+
}
258+
259+
impl<D> NodeSigner for BDKKeysManager<D>
260+
where
261+
D: bdk::database::BatchDatabase,
262+
{
263+
// ... snip
264+
}
265+
266+
impl<D> EntropySource for BDKKeysManager<D>
267+
where
268+
D: bdk::database::BatchDatabase,
269+
{
270+
// ... snip
271+
}
272+
273+
```
274+
275+
</template>
276+
</CodeSwitcher>

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /