|
2 | 2 |
|
3 | 3 | Relevant reference: [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`. 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 | +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. |
6 | 8 |
|
7 | 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.
|
8 | 10 |
|
9 | 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`).
|
10 | 12 |
|
| 13 | +<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}"> |
| 14 | + <template v-slot:rust> |
| 15 | + |
11 | 16 | ```rust
|
12 | 17 | let mut random_32_bytes = [0; 32];
|
13 | 18 | // Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk.
|
14 | 19 | let start_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
|
15 | 20 | let keys_interface_impl = lightning::chain::keysinterface::KeysManager::new(&random_32_bytes, start_time.as_secs(), start_time.subsec_nanos());
|
16 | 21 | ```
|
17 | 22 |
|
| 23 | + </template> |
| 24 | + |
| 25 | + <template v-slot:java> |
| 26 | + |
| 27 | +```java |
| 28 | +byte[] key_seed = new byte[32]; |
| 29 | +// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk. |
| 30 | +KeysManager keys_manager = KeysManager.of(key_seed, |
| 31 | + System.currentTimeMillis() / 1000, |
| 32 | + (int) (System.currentTimeMillis() * 1000) |
| 33 | +); |
| 34 | +``` |
| 35 | + |
| 36 | + </template> |
| 37 | + |
| 38 | + <template v-slot:kotlin> |
| 39 | + |
| 40 | +```kotlin |
| 41 | +val key_seed = ByteArray(32) |
| 42 | +// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk. |
| 43 | +val keys_manager = KeysManager.of( |
| 44 | + key_seed, |
| 45 | + System.currentTimeMillis() / 1000, (System.currentTimeMillis() * 1000).toInt() |
| 46 | +) |
| 47 | +``` |
| 48 | + |
| 49 | + </template> |
| 50 | +</CodeSwitcher> |
| 51 | + |
| 52 | +# 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. |
| 54 | + |
| 55 | +Using a [BDK](https://bitcoindevkit.org/)-based wallet the steps would be as follows: |
| 56 | + 1) Generate a mnemonic/entropy |
| 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 | + 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 | + |
| 60 | +<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}"> |
| 61 | + <template v-slot:rust> |
| 62 | + |
| 63 | +```rust |
| 64 | +// Use BDK to create and build the HD wallet |
| 65 | +let mnemonic = Mnemonic::parse_in_normalized( |
| 66 | + Language::English, |
| 67 | + "sock lyrics village put galaxy famous pass act ship second diagram pull" |
| 68 | + ).unwrap(); |
| 69 | +let seed: [u8; 64] = mnemonic.to_seed_normalized(""); |
| 70 | +// Other supported networks include mainnet (Bitcoin), Regtest, Signet |
| 71 | +let master_xprv = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap(); |
| 72 | +let secp = Secp256k1::new(); |
| 73 | +let xprv: ExtendedPrivKey = master_xprv.ckd_priv(&secp, ChildNumber::Hardened { index: 535 }).unwrap(); |
| 74 | +let ldk_seed: [u8; 32] = xprv.private_key.secret_bytes(); |
| 75 | + |
| 76 | +// Seed the LDK KeysManager with the private key at m/535h |
| 77 | +let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); |
| 78 | +let keys_manager = KeysManager::new(&ldk_seed, cur.as_secs(), cur.subsec_nanos()); |
| 79 | +``` |
| 80 | + |
| 81 | + </template> |
| 82 | + |
| 83 | + <template v-slot:java> |
| 84 | + |
| 85 | +```java |
| 86 | +// Use BDK to create and build the HD wallet |
| 87 | +Mnemonic mnemonic = Mnemonic.Companion.fromString("sock lyrics " + |
| 88 | + "village put galaxy " + |
| 89 | + "famous pass act ship second diagram pull"); |
| 90 | + |
| 91 | +// Other supported networks include mainnet (Bitcoin), Regtest, Signet |
| 92 | +DescriptorSecretKey bip32RootKey = new DescriptorSecretKey(Network.TESTNET, mnemonic, null); |
| 93 | + |
| 94 | +DerivationPath ldkDerivationPath = new DerivationPath("m/535h"); |
| 95 | +DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath); |
| 96 | + |
| 97 | +ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| 98 | +ObjectOutputStream oos = new ObjectOutputStream(bos); |
| 99 | +oos.writeObject(ldkChild.secretBytes()); |
| 100 | +byte[] entropy = bos.toByteArray(); |
| 101 | + |
| 102 | +// Seed the LDK KeysManager with the private key at m/535h |
| 103 | +var startupTime = System.currentTimeMillis(); |
| 104 | +KeysManager keysManager = KeysManager.of( |
| 105 | + entropy, |
| 106 | + startupTime / 1000, |
| 107 | + (int) (startupTime * 1000) |
| 108 | +); |
| 109 | +``` |
| 110 | + |
| 111 | + </template> |
| 112 | + |
| 113 | + <template v-slot:kotlin> |
| 114 | + |
| 115 | +```kotlin |
| 116 | +// Use BDK to create and build the HD wallet |
| 117 | +val mnemonic = Mnemonic.fromString("sock lyrics village put galaxy famous pass act ship second diagram pull") |
| 118 | +val bip32RootKey = DescriptorSecretKey(network = Network.TESTNET, mnemonic = mnemonic, password = null) |
| 119 | +val ldkDerivationPath = DerivationPath("m/535h") |
| 120 | +val ldkChild: DescriptorSecretKey = bip32RootKey.derive(ldkDerivationPath) |
| 121 | + |
| 122 | +@OptIn(kotlin.ExperimentalUnsignedTypes::class) |
| 123 | +val entropy: ByteArray = ldkChild.secretBytes().toUByteArray().toByteArray() |
| 124 | + |
| 125 | +// Seed the LDK KeysManager with the private key at m/535h |
| 126 | +val keysManager = KeysManager.of( |
| 127 | + entropy, |
| 128 | + System.currentTimeMillis() / 1000, |
| 129 | + (System.currentTimeMillis() * 1000).toInt() |
| 130 | +); |
| 131 | +``` |
| 132 | + |
| 133 | + </template> |
| 134 | +</CodeSwitcher> |
| 135 | + |
| 136 | +::: tip Protection for on-chain wallet |
| 137 | + |
| 138 | +An advantage to this approach is that the LDK entropy is contained within your initial mnemonic and a user only has one master private key to backup and secure. Another added benefit is that if your lightning keys were to be leaked we reduce the exposure to those funds and not the rest of the on-chain wallet. |
| 139 | + |
| 140 | +::: |
| 141 | + |
18 | 142 | Spending On-Chain Funds
|
19 | 143 | =======================
|
20 | 144 | When a channel has been closed and some outputs on chain are spendable only by us, LDK provides a `util::events::Event::SpendableOutputs` event in return from `ChannelMonitor::get_and_clear_pending_events()`. It contains a list of `chain::keysinterface::SpendableOutputDescriptor` objects which describe the output and provide all necessary information to spend it.
|
|
0 commit comments