|
3 | 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)
|
4 | 4 |
|
5 | 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 |
| - |
| 6 | + |
7 | 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.
|
8 | 8 |
|
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. |
10 | 10 |
|
11 | 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`).
|
12 | 12 |
|
@@ -53,9 +53,10 @@ val keys_manager = KeysManager.of(
|
53 | 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.
|
54 | 54 |
|
55 | 55 | 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. |
57 | 57 | 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 | 58 | 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)). |
59 | 60 |
|
60 | 61 | <CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}">
|
61 | 62 | <template v-slot:rust>
|
@@ -93,7 +94,7 @@ DescriptorSecretKey bip32RootKey = new DescriptorSecretKey(Network.TESTNET, mnem
|
93 | 94 |
|
94 | 95 | DerivationPath ldkDerivationPath = new DerivationPath("m/535h");
|
95 | 96 | DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath);
|
96 |
| - |
| 97 | + |
97 | 98 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
98 | 99 | ObjectOutputStream oos = new ObjectOutputStream(bos);
|
99 | 100 | oos.writeObject(ldkChild.secretBytes());
|
@@ -147,3 +148,98 @@ If you're using `KeysManager` directly, a utility method is provided which can g
|
147 | 148 | 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 | 149 |
|
149 | 150 | 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