|
1 | 1 | # Key Management
|
2 | 2 |
|
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). |
4 | 4 |
|
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. |
8 | 6 |
|
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. |
10 | 7 |
|
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`): |
12 | 9 |
|
13 | 10 | <CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}">
|
14 | 11 | <template v-slot:rust>
|
@@ -50,12 +47,14 @@ val keys_manager = KeysManager.of(
|
50 | 47 | </CodeSwitcher>
|
51 | 48 |
|
52 | 49 | # 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. |
54 | 51 |
|
55 | 52 | 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. |
57 | 55 | 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.
|
58 | 56 | 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)). |
59 | 58 |
|
60 | 59 | <CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}">
|
61 | 60 | <template v-slot:rust>
|
@@ -93,7 +92,7 @@ DescriptorSecretKey bip32RootKey = new DescriptorSecretKey(Network.TESTNET, mnem
|
93 | 92 |
|
94 | 93 | DerivationPath ldkDerivationPath = new DerivationPath("m/535h");
|
95 | 94 | DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath);
|
96 |
| - |
| 95 | + |
97 | 96 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
98 | 97 | ObjectOutputStream oos = new ObjectOutputStream(bos);
|
99 | 98 | oos.writeObject(ldkChild.secretBytes());
|
@@ -146,4 +145,124 @@ When a channel has been closed and some outputs on chain are spendable only by u
|
146 | 145 | If you're using `KeysManager` directly, a utility method is provided which can generate a signed transaction given a list of `
|
147 | 146 | 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).
|
148 | 147 |
|
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